diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/regulator | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
141 files changed, 72903 insertions, 0 deletions
diff --git a/drivers/regulator/88pg86x.c b/drivers/regulator/88pg86x.c new file mode 100644 index 000000000..d5ef55c81 --- /dev/null +++ b/drivers/regulator/88pg86x.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/regulator/driver.h> +#include <linux/regmap.h> + +static const struct regulator_ops pg86x_ops = { + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, +}; + +static const struct regulator_linear_range pg86x_buck1_ranges[] = { + REGULATOR_LINEAR_RANGE( 0, 0, 10, 0), + REGULATOR_LINEAR_RANGE(1000000, 11, 34, 25000), + REGULATOR_LINEAR_RANGE(1600000, 35, 47, 50000), +}; + +static const struct regulator_linear_range pg86x_buck2_ranges[] = { + REGULATOR_LINEAR_RANGE( 0, 0, 15, 0), + REGULATOR_LINEAR_RANGE(1000000, 16, 39, 25000), + REGULATOR_LINEAR_RANGE(1600000, 40, 52, 50000), +}; + +static const struct regulator_desc pg86x_regulators[] = { + { + .id = 0, + .type = REGULATOR_VOLTAGE, + .name = "buck1", + .of_match = of_match_ptr("buck1"), + .n_voltages = 11 + 24 + 13, + .linear_ranges = pg86x_buck1_ranges, + .n_linear_ranges = 3, + .vsel_reg = 0x24, + .vsel_mask = 0xff, + .ops = &pg86x_ops, + .owner = THIS_MODULE + }, + { + .id = 1, + .type = REGULATOR_VOLTAGE, + .name = "buck2", + .of_match = of_match_ptr("buck2"), + .n_voltages = 16 + 24 + 13, + .linear_ranges = pg86x_buck2_ranges, + .n_linear_ranges = 3, + .vsel_reg = 0x13, + .vsel_mask = 0xff, + .ops = &pg86x_ops, + .owner = THIS_MODULE + }, +}; + +static const struct regmap_config pg86x_regmap = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int pg86x_i2c_probe(struct i2c_client *i2c) +{ + int id, ret; + struct regulator_config config = {.dev = &i2c->dev}; + struct regmap *regmap = devm_regmap_init_i2c(i2c, &pg86x_regmap); + + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&i2c->dev, "regmap init failed: %d\n", ret); + return ret; + } + + for (id = 0; id < ARRAY_SIZE(pg86x_regulators); id++) { + struct regulator_dev *rdev; + rdev = devm_regulator_register(&i2c->dev, + &pg86x_regulators[id], + &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(&i2c->dev, "failed to register %s: %d\n", + pg86x_regulators[id].name, ret); + return ret; + } + } + return 0; +} + +static const struct of_device_id pg86x_dt_ids [] = { + { .compatible = "marvell,88pg867" }, + { .compatible = "marvell,88pg868" }, + { } +}; +MODULE_DEVICE_TABLE(of, pg86x_dt_ids); + +static const struct i2c_device_id pg86x_i2c_id[] = { + { "88pg867", }, + { "88pg868", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pg86x_i2c_id); + +static struct i2c_driver pg86x_regulator_driver = { + .driver = { + .name = "88pg86x", + .of_match_table = of_match_ptr(pg86x_dt_ids), + }, + .probe_new = pg86x_i2c_probe, + .id_table = pg86x_i2c_id, +}; + +module_i2c_driver(pg86x_regulator_driver); + +MODULE_DESCRIPTION("Marvell 88PG86X voltage regulator"); +MODULE_AUTHOR("Alexander Monakov <amonakov@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/88pm800-regulator.c b/drivers/regulator/88pm800-regulator.c new file mode 100644 index 000000000..89bbd6e8b --- /dev/null +++ b/drivers/regulator/88pm800-regulator.c @@ -0,0 +1,305 @@ +/* + * Regulators driver for Marvell 88PM800 + * + * Copyright (C) 2012 Marvell International Ltd. + * Joseph(Yossi) Hanin <yhanin@marvell.com> + * Yi Zhang <yizhang@marvell.com> + * + * 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/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/88pm80x.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/regulator/of_regulator.h> + +/* LDO1 with DVC[0..3] */ +#define PM800_LDO1_VOUT (0x08) /* VOUT1 */ +#define PM800_LDO1_VOUT_2 (0x09) +#define PM800_LDO1_VOUT_3 (0x0A) +#define PM800_LDO2_VOUT (0x0B) +#define PM800_LDO3_VOUT (0x0C) +#define PM800_LDO4_VOUT (0x0D) +#define PM800_LDO5_VOUT (0x0E) +#define PM800_LDO6_VOUT (0x0F) +#define PM800_LDO7_VOUT (0x10) +#define PM800_LDO8_VOUT (0x11) +#define PM800_LDO9_VOUT (0x12) +#define PM800_LDO10_VOUT (0x13) +#define PM800_LDO11_VOUT (0x14) +#define PM800_LDO12_VOUT (0x15) +#define PM800_LDO13_VOUT (0x16) +#define PM800_LDO14_VOUT (0x17) +#define PM800_LDO15_VOUT (0x18) +#define PM800_LDO16_VOUT (0x19) +#define PM800_LDO17_VOUT (0x1A) +#define PM800_LDO18_VOUT (0x1B) +#define PM800_LDO19_VOUT (0x1C) + +/* BUCK1 with DVC[0..3] */ +#define PM800_BUCK1 (0x3C) +#define PM800_BUCK1_1 (0x3D) +#define PM800_BUCK1_2 (0x3E) +#define PM800_BUCK1_3 (0x3F) +#define PM800_BUCK2 (0x40) +#define PM800_BUCK3 (0x41) +#define PM800_BUCK4 (0x42) +#define PM800_BUCK4_1 (0x43) +#define PM800_BUCK4_2 (0x44) +#define PM800_BUCK4_3 (0x45) +#define PM800_BUCK5 (0x46) + +#define PM800_BUCK_ENA (0x50) +#define PM800_LDO_ENA1_1 (0x51) +#define PM800_LDO_ENA1_2 (0x52) +#define PM800_LDO_ENA1_3 (0x53) + +#define PM800_LDO_ENA2_1 (0x56) +#define PM800_LDO_ENA2_2 (0x57) +#define PM800_LDO_ENA2_3 (0x58) + +#define PM800_BUCK1_MISC1 (0x78) +#define PM800_BUCK3_MISC1 (0x7E) +#define PM800_BUCK4_MISC1 (0x81) +#define PM800_BUCK5_MISC1 (0x84) + +struct pm800_regulator_info { + struct regulator_desc desc; + int max_ua; +}; + +struct pm800_regulators { + struct pm80x_chip *chip; + struct regmap *map; +}; + +/* + * vreg - the buck regs string. + * ereg - the string for the enable register. + * ebit - the bit number in the enable register. + * amax - the current + * Buck has 2 kinds of voltage steps. It is easy to find voltage by ranges, + * not the constant voltage table. + * n_volt - Number of available selectors + */ +#define PM800_BUCK(match, vreg, ereg, ebit, amax, volt_ranges, n_volt) \ +{ \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(#match), \ + .regulators_node = of_match_ptr("regulators"), \ + .ops = &pm800_volt_range_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = PM800_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = n_volt, \ + .linear_ranges = volt_ranges, \ + .n_linear_ranges = ARRAY_SIZE(volt_ranges), \ + .vsel_reg = PM800_##vreg, \ + .vsel_mask = 0x7f, \ + .enable_reg = PM800_##ereg, \ + .enable_mask = 1 << (ebit), \ + }, \ + .max_ua = (amax), \ +} + +/* + * vreg - the LDO regs string + * ereg - the string for the enable register. + * ebit - the bit number in the enable register. + * amax - the current + * volt_table - the LDO voltage table + * For all the LDOes, there are too many ranges. Using volt_table will be + * simpler and faster. + */ +#define PM800_LDO(match, vreg, ereg, ebit, amax, ldo_volt_table) \ +{ \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(#match), \ + .regulators_node = of_match_ptr("regulators"), \ + .ops = &pm800_volt_table_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = PM800_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(ldo_volt_table), \ + .vsel_reg = PM800_##vreg##_VOUT, \ + .vsel_mask = 0xf, \ + .enable_reg = PM800_##ereg, \ + .enable_mask = 1 << (ebit), \ + .volt_table = ldo_volt_table, \ + }, \ + .max_ua = (amax), \ +} + +/* Ranges are sorted in ascending order. */ +static const struct regulator_linear_range buck1_volt_range[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 0x4f, 12500), + REGULATOR_LINEAR_RANGE(1600000, 0x50, 0x54, 50000), +}; + +/* BUCK 2~5 have same ranges. */ +static const struct regulator_linear_range buck2_5_volt_range[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 0x4f, 12500), + REGULATOR_LINEAR_RANGE(1600000, 0x50, 0x72, 50000), +}; + +static const unsigned int ldo1_volt_table[] = { + 600000, 650000, 700000, 750000, 800000, 850000, 900000, 950000, + 1000000, 1050000, 1100000, 1150000, 1200000, 1300000, 1400000, 1500000, +}; + +static const unsigned int ldo2_volt_table[] = { + 1700000, 1800000, 1900000, 2000000, 2100000, 2500000, 2700000, 2800000, +}; + +/* LDO 3~17 have same voltage table. */ +static const unsigned int ldo3_17_volt_table[] = { + 1200000, 1250000, 1700000, 1800000, 1850000, 1900000, 2500000, 2600000, + 2700000, 2750000, 2800000, 2850000, 2900000, 3000000, 3100000, 3300000, +}; + +/* LDO 18~19 have same voltage table. */ +static const unsigned int ldo18_19_volt_table[] = { + 1700000, 1800000, 1900000, 2500000, 2800000, 2900000, 3100000, 3300000, +}; + +static int pm800_get_current_limit(struct regulator_dev *rdev) +{ + struct pm800_regulator_info *info = rdev_get_drvdata(rdev); + + return info->max_ua; +} + +static const struct regulator_ops pm800_volt_range_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_current_limit = pm800_get_current_limit, +}; + +static const struct regulator_ops pm800_volt_table_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_current_limit = pm800_get_current_limit, +}; + +/* The array is indexed by id(PM800_ID_XXX) */ +static struct pm800_regulator_info pm800_regulator_info[] = { + PM800_BUCK(buck1, BUCK1, BUCK_ENA, 0, 3000000, buck1_volt_range, 0x55), + PM800_BUCK(buck2, BUCK2, BUCK_ENA, 1, 1200000, buck2_5_volt_range, 0x73), + PM800_BUCK(buck3, BUCK3, BUCK_ENA, 2, 1200000, buck2_5_volt_range, 0x73), + PM800_BUCK(buck4, BUCK4, BUCK_ENA, 3, 1200000, buck2_5_volt_range, 0x73), + PM800_BUCK(buck5, BUCK5, BUCK_ENA, 4, 1200000, buck2_5_volt_range, 0x73), + + PM800_LDO(ldo1, LDO1, LDO_ENA1_1, 0, 200000, ldo1_volt_table), + PM800_LDO(ldo2, LDO2, LDO_ENA1_1, 1, 10000, ldo2_volt_table), + PM800_LDO(ldo3, LDO3, LDO_ENA1_1, 2, 300000, ldo3_17_volt_table), + PM800_LDO(ldo4, LDO4, LDO_ENA1_1, 3, 300000, ldo3_17_volt_table), + PM800_LDO(ldo5, LDO5, LDO_ENA1_1, 4, 300000, ldo3_17_volt_table), + PM800_LDO(ldo6, LDO6, LDO_ENA1_1, 5, 300000, ldo3_17_volt_table), + PM800_LDO(ldo7, LDO7, LDO_ENA1_1, 6, 300000, ldo3_17_volt_table), + PM800_LDO(ldo8, LDO8, LDO_ENA1_1, 7, 300000, ldo3_17_volt_table), + PM800_LDO(ldo9, LDO9, LDO_ENA1_2, 0, 300000, ldo3_17_volt_table), + PM800_LDO(ldo10, LDO10, LDO_ENA1_2, 1, 300000, ldo3_17_volt_table), + PM800_LDO(ldo11, LDO11, LDO_ENA1_2, 2, 300000, ldo3_17_volt_table), + PM800_LDO(ldo12, LDO12, LDO_ENA1_2, 3, 300000, ldo3_17_volt_table), + PM800_LDO(ldo13, LDO13, LDO_ENA1_2, 4, 300000, ldo3_17_volt_table), + PM800_LDO(ldo14, LDO14, LDO_ENA1_2, 5, 300000, ldo3_17_volt_table), + PM800_LDO(ldo15, LDO15, LDO_ENA1_2, 6, 300000, ldo3_17_volt_table), + PM800_LDO(ldo16, LDO16, LDO_ENA1_2, 7, 300000, ldo3_17_volt_table), + PM800_LDO(ldo17, LDO17, LDO_ENA1_3, 0, 300000, ldo3_17_volt_table), + PM800_LDO(ldo18, LDO18, LDO_ENA1_3, 1, 200000, ldo18_19_volt_table), + PM800_LDO(ldo19, LDO19, LDO_ENA1_3, 2, 200000, ldo18_19_volt_table), +}; + +static int pm800_regulator_probe(struct platform_device *pdev) +{ + struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm80x_platform_data *pdata = dev_get_platdata(pdev->dev.parent); + struct pm800_regulators *pm800_data; + struct regulator_config config = { }; + struct regulator_init_data *init_data; + int i, ret; + + if (pdata && pdata->num_regulators) { + unsigned int count = 0; + + /* Check whether num_regulator is valid. */ + for (i = 0; i < ARRAY_SIZE(pdata->regulators); i++) { + if (pdata->regulators[i]) + count++; + } + if (count != pdata->num_regulators) + return -EINVAL; + } + + pm800_data = devm_kzalloc(&pdev->dev, sizeof(*pm800_data), + GFP_KERNEL); + if (!pm800_data) + return -ENOMEM; + + pm800_data->map = chip->subchip->regmap_power; + pm800_data->chip = chip; + + platform_set_drvdata(pdev, pm800_data); + + config.dev = chip->dev; + config.regmap = pm800_data->map; + for (i = 0; i < PM800_ID_RG_MAX; i++) { + struct regulator_dev *regulator; + + if (pdata && pdata->num_regulators) { + init_data = pdata->regulators[i]; + if (!init_data) + continue; + + config.init_data = init_data; + } + + config.driver_data = &pm800_regulator_info[i]; + + regulator = devm_regulator_register(&pdev->dev, + &pm800_regulator_info[i].desc, &config); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + dev_err(&pdev->dev, "Failed to register %s\n", + pm800_regulator_info[i].desc.name); + return ret; + } + } + + return 0; +} + +static struct platform_driver pm800_regulator_driver = { + .driver = { + .name = "88pm80x-regulator", + }, + .probe = pm800_regulator_probe, +}; + +module_platform_driver(pm800_regulator_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Joseph(Yossi) Hanin <yhanin@marvell.com>"); +MODULE_DESCRIPTION("Regulator Driver for Marvell 88PM800 PMIC"); +MODULE_ALIAS("platform:88pm800-regulator"); diff --git a/drivers/regulator/88pm8607.c b/drivers/regulator/88pm8607.c new file mode 100644 index 000000000..fd86446e4 --- /dev/null +++ b/drivers/regulator/88pm8607.c @@ -0,0 +1,442 @@ +/* + * Regulators driver for Marvell 88PM8607 + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * 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/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/regulator/of_regulator.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/88pm860x.h> +#include <linux/module.h> + +struct pm8607_regulator_info { + struct regulator_desc desc; + struct pm860x_chip *chip; + struct regulator_dev *regulator; + struct i2c_client *i2c; + struct i2c_client *i2c_8606; + + unsigned int *vol_table; + unsigned int *vol_suspend; + + int slope_double; +}; + +static const unsigned int BUCK1_table[] = { + 725000, 750000, 775000, 800000, 825000, 850000, 875000, 900000, + 925000, 950000, 975000, 1000000, 1025000, 1050000, 1075000, 1100000, + 1125000, 1150000, 1175000, 1200000, 1225000, 1250000, 1275000, 1300000, + 1325000, 1350000, 1375000, 1400000, 1425000, 1450000, 1475000, 1500000, + 0, 25000, 50000, 75000, 100000, 125000, 150000, 175000, + 200000, 225000, 250000, 275000, 300000, 325000, 350000, 375000, + 400000, 425000, 450000, 475000, 500000, 525000, 550000, 575000, + 600000, 625000, 650000, 675000, 700000, 725000, 750000, 775000, +}; + +static const unsigned int BUCK1_suspend_table[] = { + 0, 25000, 50000, 75000, 100000, 125000, 150000, 175000, + 200000, 225000, 250000, 275000, 300000, 325000, 350000, 375000, + 400000, 425000, 450000, 475000, 500000, 525000, 550000, 575000, + 600000, 625000, 650000, 675000, 700000, 725000, 750000, 775000, + 800000, 825000, 850000, 875000, 900000, 925000, 950000, 975000, + 1000000, 1025000, 1050000, 1075000, 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, 1300000, 1325000, 1350000, 1375000, + 1400000, 1425000, 1450000, 1475000, 1500000, 1500000, 1500000, 1500000, +}; + +static const unsigned int BUCK2_table[] = { + 0, 50000, 100000, 150000, 200000, 250000, 300000, 350000, + 400000, 450000, 500000, 550000, 600000, 650000, 700000, 750000, + 800000, 850000, 900000, 950000, 1000000, 1050000, 1100000, 1150000, + 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, 1550000, + 1600000, 1650000, 1700000, 1750000, 1800000, 1850000, 1900000, 1950000, + 2000000, 2050000, 2100000, 2150000, 2200000, 2250000, 2300000, 2350000, + 2400000, 2450000, 2500000, 2550000, 2600000, 2650000, 2700000, 2750000, + 2800000, 2850000, 2900000, 2950000, 3000000, 3000000, 3000000, 3000000, +}; + +static const unsigned int BUCK2_suspend_table[] = { + 0, 50000, 100000, 150000, 200000, 250000, 300000, 350000, + 400000, 450000, 500000, 550000, 600000, 650000, 700000, 750000, + 800000, 850000, 900000, 950000, 1000000, 1050000, 1100000, 1150000, + 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, 1550000, + 1600000, 1650000, 1700000, 1750000, 1800000, 1850000, 1900000, 1950000, + 2000000, 2050000, 2100000, 2150000, 2200000, 2250000, 2300000, 2350000, + 2400000, 2450000, 2500000, 2550000, 2600000, 2650000, 2700000, 2750000, + 2800000, 2850000, 2900000, 2950000, 3000000, 3000000, 3000000, 3000000, +}; + +static const unsigned int BUCK3_table[] = { + 0, 25000, 50000, 75000, 100000, 125000, 150000, 175000, + 200000, 225000, 250000, 275000, 300000, 325000, 350000, 375000, + 400000, 425000, 450000, 475000, 500000, 525000, 550000, 575000, + 600000, 625000, 650000, 675000, 700000, 725000, 750000, 775000, + 800000, 825000, 850000, 875000, 900000, 925000, 950000, 975000, + 1000000, 1025000, 1050000, 1075000, 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, 1300000, 1325000, 1350000, 1375000, + 1400000, 1425000, 1450000, 1475000, 1500000, 1500000, 1500000, 1500000, +}; + +static const unsigned int BUCK3_suspend_table[] = { + 0, 25000, 50000, 75000, 100000, 125000, 150000, 175000, + 200000, 225000, 250000, 275000, 300000, 325000, 350000, 375000, + 400000, 425000, 450000, 475000, 500000, 525000, 550000, 575000, + 600000, 625000, 650000, 675000, 700000, 725000, 750000, 775000, + 800000, 825000, 850000, 875000, 900000, 925000, 950000, 975000, + 1000000, 1025000, 1050000, 1075000, 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, 1300000, 1325000, 1350000, 1375000, + 1400000, 1425000, 1450000, 1475000, 1500000, 1500000, 1500000, 1500000, +}; + +static const unsigned int LDO1_table[] = { + 1800000, 1200000, 2800000, 0, +}; + +static const unsigned int LDO1_suspend_table[] = { + 1800000, 1200000, 0, 0, +}; + +static const unsigned int LDO2_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 3300000, +}; + +static const unsigned int LDO2_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO3_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 3300000, +}; + +static const unsigned int LDO3_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO4_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2900000, 3300000, +}; + +static const unsigned int LDO4_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2900000, 2900000, +}; + +static const unsigned int LDO5_table[] = { + 2900000, 3000000, 3100000, 3300000, +}; + +static const unsigned int LDO5_suspend_table[] = { + 2900000, 0, 0, 0, +}; + +static const unsigned int LDO6_table[] = { + 1800000, 1850000, 2600000, 2650000, 2700000, 2750000, 2800000, 3300000, +}; + +static const unsigned int LDO6_suspend_table[] = { + 1800000, 1850000, 2600000, 2650000, 2700000, 2750000, 2800000, 2900000, +}; + +static const unsigned int LDO7_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO7_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO8_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO8_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO9_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 3300000, +}; + +static const unsigned int LDO9_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, +}; + +static const unsigned int LDO10_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 3300000, + 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, +}; + +static const unsigned int LDO10_suspend_table[] = { + 1800000, 1850000, 1900000, 2700000, 2750000, 2800000, 2850000, 2900000, + 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, +}; + +static const unsigned int LDO12_table[] = { + 1800000, 1900000, 2700000, 2800000, 2900000, 3000000, 3100000, 3300000, + 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, +}; + +static const unsigned int LDO12_suspend_table[] = { + 1800000, 1900000, 2700000, 2800000, 2900000, 2900000, 2900000, 2900000, + 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, 1200000, +}; + +static const unsigned int LDO13_table[] = { + 1200000, 1300000, 1800000, 2000000, 2500000, 2800000, 3000000, 0, +}; + +static const unsigned int LDO13_suspend_table[] = { + 0, +}; + +static const unsigned int LDO14_table[] = { + 1800000, 1850000, 2700000, 2750000, 2800000, 2850000, 2900000, 3300000, +}; + +static const unsigned int LDO14_suspend_table[] = { + 1800000, 1850000, 2700000, 2750000, 2800000, 2850000, 2900000, 2900000, +}; + +static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index) +{ + struct pm8607_regulator_info *info = rdev_get_drvdata(rdev); + int ret = -EINVAL; + + if (info->vol_table && (index < rdev->desc->n_voltages)) { + ret = info->vol_table[index]; + if (info->slope_double) + ret <<= 1; + } + return ret; +} + +static const struct regulator_ops pm8607_regulator_ops = { + .list_voltage = pm8607_list_voltage, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct regulator_ops pm8606_preg_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +#define PM8606_PREG(ereg, ebit) \ +{ \ + .desc = { \ + .name = "PREG", \ + .ops = &pm8606_preg_ops, \ + .type = REGULATOR_CURRENT, \ + .id = PM8606_ID_PREG, \ + .owner = THIS_MODULE, \ + .enable_reg = PM8606_##ereg, \ + .enable_mask = (ebit), \ + .enable_is_inverted = true, \ + }, \ +} + +#define PM8607_DVC(vreg, ureg, ubit, ereg, ebit) \ +{ \ + .desc = { \ + .name = #vreg, \ + .ops = &pm8607_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = PM8607_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(vreg##_table), \ + .vsel_reg = PM8607_##vreg, \ + .vsel_mask = ARRAY_SIZE(vreg##_table) - 1, \ + .apply_reg = PM8607_##ureg, \ + .apply_bit = (ubit), \ + .enable_reg = PM8607_##ereg, \ + .enable_mask = 1 << (ebit), \ + }, \ + .slope_double = (0), \ + .vol_table = (unsigned int *)&vreg##_table, \ + .vol_suspend = (unsigned int *)&vreg##_suspend_table, \ +} + +#define PM8607_LDO(_id, vreg, shift, ereg, ebit) \ +{ \ + .desc = { \ + .name = "LDO" #_id, \ + .ops = &pm8607_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = PM8607_ID_LDO##_id, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(LDO##_id##_table), \ + .vsel_reg = PM8607_##vreg, \ + .vsel_mask = (ARRAY_SIZE(LDO##_id##_table) - 1) << (shift), \ + .enable_reg = PM8607_##ereg, \ + .enable_mask = 1 << (ebit), \ + }, \ + .slope_double = (0), \ + .vol_table = (unsigned int *)&LDO##_id##_table, \ + .vol_suspend = (unsigned int *)&LDO##_id##_suspend_table, \ +} + +static struct pm8607_regulator_info pm8607_regulator_info[] = { + PM8607_DVC(BUCK1, GO, BIT(0), SUPPLIES_EN11, 0), + PM8607_DVC(BUCK2, GO, BIT(1), SUPPLIES_EN11, 1), + PM8607_DVC(BUCK3, GO, BIT(2), SUPPLIES_EN11, 2), + + PM8607_LDO(1, LDO1, 0, SUPPLIES_EN11, 3), + PM8607_LDO(2, LDO2, 0, SUPPLIES_EN11, 4), + PM8607_LDO(3, LDO3, 0, SUPPLIES_EN11, 5), + PM8607_LDO(4, LDO4, 0, SUPPLIES_EN11, 6), + PM8607_LDO(5, LDO5, 0, SUPPLIES_EN11, 7), + PM8607_LDO(6, LDO6, 0, SUPPLIES_EN12, 0), + PM8607_LDO(7, LDO7, 0, SUPPLIES_EN12, 1), + PM8607_LDO(8, LDO8, 0, SUPPLIES_EN12, 2), + PM8607_LDO(9, LDO9, 0, SUPPLIES_EN12, 3), + PM8607_LDO(10, LDO10, 0, SUPPLIES_EN12, 4), + PM8607_LDO(12, LDO12, 0, SUPPLIES_EN12, 5), + PM8607_LDO(13, VIBRATOR_SET, 1, VIBRATOR_SET, 0), + PM8607_LDO(14, LDO14, 0, SUPPLIES_EN12, 6), +}; + +static struct pm8607_regulator_info pm8606_regulator_info[] = { + PM8606_PREG(PREREGULATORB, 5), +}; + +#ifdef CONFIG_OF +static int pm8607_regulator_dt_init(struct platform_device *pdev, + struct pm8607_regulator_info *info, + struct regulator_config *config) +{ + struct device_node *nproot, *np; + nproot = pdev->dev.parent->of_node; + if (!nproot) + return -ENODEV; + nproot = of_get_child_by_name(nproot, "regulators"); + if (!nproot) { + dev_err(&pdev->dev, "failed to find regulators node\n"); + return -ENODEV; + } + for_each_child_of_node(nproot, np) { + if (!of_node_cmp(np->name, info->desc.name)) { + config->init_data = + of_get_regulator_init_data(&pdev->dev, np, + &info->desc); + config->of_node = np; + break; + } + } + of_node_put(nproot); + return 0; +} +#else +#define pm8607_regulator_dt_init(x, y, z) (-1) +#endif + +static int pm8607_regulator_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm8607_regulator_info *info = NULL; + struct regulator_init_data *pdata = dev_get_platdata(&pdev->dev); + struct regulator_config config = { }; + struct resource *res; + int i; + + res = platform_get_resource(pdev, IORESOURCE_REG, 0); + if (res) { + /* There're resources in 88PM8607 regulator driver */ + for (i = 0; i < ARRAY_SIZE(pm8607_regulator_info); i++) { + info = &pm8607_regulator_info[i]; + if (info->desc.vsel_reg == res->start) + break; + } + if (i == ARRAY_SIZE(pm8607_regulator_info)) { + dev_err(&pdev->dev, "Failed to find regulator %llu\n", + (unsigned long long)res->start); + return -EINVAL; + } + } else { + /* There's no resource in 88PM8606 PREG regulator driver */ + info = &pm8606_regulator_info[0]; + /* i is used to check regulator ID */ + i = -1; + } + info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + info->i2c_8606 = (chip->id == CHIP_PM8607) ? chip->companion : + chip->client; + info->chip = chip; + + /* check DVC ramp slope double */ + if ((i == PM8607_ID_BUCK3) && info->chip->buck3_double) + info->slope_double = 1; + + config.dev = &pdev->dev; + config.driver_data = info; + + if (pm8607_regulator_dt_init(pdev, info, &config)) + if (pdata) + config.init_data = pdata; + + if (chip->id == CHIP_PM8607) + config.regmap = chip->regmap; + else + config.regmap = chip->regmap_companion; + + info->regulator = devm_regulator_register(&pdev->dev, &info->desc, + &config); + if (IS_ERR(info->regulator)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + info->desc.name); + return PTR_ERR(info->regulator); + } + + platform_set_drvdata(pdev, info); + return 0; +} + +static const struct platform_device_id pm8607_regulator_driver_ids[] = { + { + .name = "88pm860x-regulator", + .driver_data = 0, + }, { + .name = "88pm860x-preg", + .driver_data = 0, + }, + { }, +}; +MODULE_DEVICE_TABLE(platform, pm8607_regulator_driver_ids); + +static struct platform_driver pm8607_regulator_driver = { + .driver = { + .name = "88pm860x-regulator", + }, + .probe = pm8607_regulator_probe, + .id_table = pm8607_regulator_driver_ids, +}; + +static int __init pm8607_regulator_init(void) +{ + return platform_driver_register(&pm8607_regulator_driver); +} +subsys_initcall(pm8607_regulator_init); + +static void __exit pm8607_regulator_exit(void) +{ + platform_driver_unregister(&pm8607_regulator_driver); +} +module_exit(pm8607_regulator_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_DESCRIPTION("Regulator Driver for Marvell 88PM8607 PMIC"); +MODULE_ALIAS("platform:88pm8607-regulator"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig new file mode 100644 index 000000000..329cdd33e --- /dev/null +++ b/drivers/regulator/Kconfig @@ -0,0 +1,1013 @@ +menuconfig REGULATOR + bool "Voltage and Current Regulator Support" + help + Generic Voltage and Current Regulator support. + + This framework is designed to provide a generic interface to voltage + and current regulators within the Linux kernel. It's intended to + provide voltage and current control to client or consumer drivers and + also provide status information to user space applications through a + sysfs interface. + + The intention is to allow systems to dynamically control regulator + output in order to save power and prolong battery life. This applies + to both voltage regulators (where voltage output is controllable) and + current sinks (where current output is controllable). + + This framework safely compiles out if not selected so that client + drivers can still be used in systems with no software controllable + regulators. + + If unsure, say no. + + +if REGULATOR + +config REGULATOR_DEBUG + bool "Regulator debug support" + help + Say yes here to enable debugging support. + +config REGULATOR_FIXED_VOLTAGE + tristate "Fixed voltage regulator support" + help + This driver provides support for fixed voltage regulators, + useful for systems which use a combination of software + managed regulators and simple non-configurable regulators. + +config REGULATOR_VIRTUAL_CONSUMER + tristate "Virtual regulator consumer support" + help + This driver provides a virtual consumer for the voltage and + current regulator API which provides sysfs controls for + configuring the supplies requested. This is mainly useful + for test purposes. + + If unsure, say no. + +config REGULATOR_USERSPACE_CONSUMER + tristate "Userspace regulator consumer support" + help + There are some classes of devices that are controlled entirely + from user space. Userspace consumer driver provides ability to + control power supplies for such devices. + + If unsure, say no. + +config REGULATOR_88PG86X + tristate "Marvell 88PG86X voltage regulators" + depends on I2C + select REGMAP_I2C + help + This driver supports Marvell 88PG867 and 88PG868 voltage regulators. + They provide two I2C-controlled DC/DC step-down converters with + sleep mode and separate enable pins. + +config REGULATOR_88PM800 + tristate "Marvell 88PM800 Power regulators" + depends on MFD_88PM800 + help + This driver supports Marvell 88PM800 voltage regulator chips. + It delivers digitally programmable output, + the voltage is programmed via I2C interface. + It's suitable to support PXA988 chips to control VCC_MAIN and + various voltages. + +config REGULATOR_88PM8607 + tristate "Marvell 88PM8607 Power regulators" + depends on MFD_88PM860X=y + help + This driver supports 88PM8607 voltage regulator chips. + +config REGULATOR_ACT8865 + tristate "Active-semi act8865 voltage regulator" + depends on I2C + select REGMAP_I2C + help + This driver controls a active-semi act8865 voltage output + regulator via I2C bus. + +config REGULATOR_ACT8945A + tristate "Active-semi ACT8945A voltage regulator" + depends on MFD_ACT8945A + help + This driver controls a active-semi ACT8945A voltage regulator + via I2C bus. The ACT8945A features three step-down DC/DC converters + and four low-dropout linear regulators, along with a ActivePath + battery charger. + +config REGULATOR_AD5398 + tristate "Analog Devices AD5398/AD5821 regulators" + depends on I2C + help + This driver supports AD5398 and AD5821 current regulator chips. + If building into module, its name is ad5398.ko. + +config REGULATOR_ANATOP + tristate "Freescale i.MX on-chip ANATOP LDO regulators" + depends on MFD_SYSCON + help + Say y here to support Freescale i.MX on-chip ANATOP LDOs + regulators. It is recommended that this option be + enabled on i.MX6 platform. + +config REGULATOR_AAT2870 + tristate "AnalogicTech AAT2870 Regulators" + depends on MFD_AAT2870_CORE + help + If you have a AnalogicTech AAT2870 say Y to enable the + regulator driver. + +config REGULATOR_AB3100 + tristate "ST-Ericsson AB3100 Regulator functions" + depends on AB3100_CORE + default y if AB3100_CORE + help + These regulators correspond to functionality in the + AB3100 analog baseband dealing with power regulators + for the system. + +config REGULATOR_AB8500 + bool "ST-Ericsson AB8500 Power Regulators" + depends on AB8500_CORE + help + This driver supports the regulators found on the ST-Ericsson mixed + signal AB8500 PMIC + +config REGULATOR_ARIZONA_LDO1 + tristate "Wolfson Arizona class devices LDO1" + depends on MFD_ARIZONA + depends on SND_SOC + help + Support for the LDO1 regulators found on Wolfson Arizona class + devices. + +config REGULATOR_ARIZONA_MICSUPP + tristate "Wolfson Arizona class devices MICSUPP" + depends on MFD_ARIZONA + depends on SND_SOC + help + Support for the MICSUPP regulators found on Wolfson Arizona class + devices. + +config REGULATOR_AS3711 + tristate "AS3711 PMIC" + depends on MFD_AS3711 + help + This driver provides support for the voltage regulators on the + AS3711 PMIC + +config REGULATOR_AS3722 + tristate "AMS AS3722 PMIC Regulators" + depends on MFD_AS3722 + help + This driver provides support for the voltage regulators on the + AS3722 PMIC. This will enable support for all the software + controllable DCDC/LDO regulators. + +config REGULATOR_AXP20X + tristate "X-POWERS AXP20X PMIC Regulators" + depends on MFD_AXP20X + help + This driver provides support for the voltage regulators on the + AXP20X PMIC. + +config REGULATOR_BCM590XX + tristate "Broadcom BCM590xx PMU Regulators" + depends on MFD_BCM590XX + help + This driver provides support for the voltage regulators on the + BCM590xx PMUs. This will enable support for the software + controllable LDO/Switching regulators. + +config REGULATOR_BD718XX + tristate "ROHM BD71837 Power Regulator" + depends on MFD_ROHM_BD718XX + help + This driver supports voltage regulators on ROHM BD71837 PMIC. + This will enable support for the software controllable buck + and LDO regulators. + + This driver can also be built as a module. If so, the module + will be called bd71837-regulator. + +config REGULATOR_BD9571MWV + tristate "ROHM BD9571MWV Regulators" + depends on MFD_BD9571MWV + help + This driver provides support for the voltage regulators on the + ROHM BD9571MWV PMIC. This will enable support for the software + controllable regulator and voltage sampling units. + + This driver can also be built as a module. If so, the module + will be called bd9571mwv-regulator. + +config REGULATOR_CPCAP + tristate "Motorola CPCAP regulator" + depends on MFD_CPCAP + help + Say y here for CPCAP regulator found on some Motorola phones + and tablets such as Droid 4. + +config REGULATOR_DA903X + tristate "Dialog Semiconductor DA9030/DA9034 regulators" + depends on PMIC_DA903X + help + Say y here to support the BUCKs and LDOs regulators found on + Dialog Semiconductor DA9030/DA9034 PMIC. + +config REGULATOR_DA9052 + tristate "Dialog Semiconductor DA9052/DA9053 regulators" + depends on PMIC_DA9052 + help + This driver supports the voltage regulators of DA9052-BC and + DA9053-AA/Bx PMIC. + +config REGULATOR_DA9055 + tristate "Dialog Semiconductor DA9055 regulators" + depends on MFD_DA9055 + help + Say y here to support the BUCKs and LDOs regulators found on + Dialog Semiconductor DA9055 PMIC. + + This driver can also be built as a module. If so, the module + will be called da9055-regulator. + +config REGULATOR_DA9062 + tristate "Dialog Semiconductor DA9061/62 regulators" + depends on MFD_DA9062 + help + Say y here to support the BUCKs and LDOs regulators found on + DA9061 and DA9062 PMICs. + + This driver can also be built as a module. If so, the module + will be called da9062-regulator. + +config REGULATOR_DA9063 + tristate "Dialog Semiconductor DA9063 regulators" + depends on MFD_DA9063 + help + Say y here to support the BUCKs and LDOs regulators found on + DA9063 PMICs. + + This driver can also be built as a module. If so, the module + will be called da9063-regulator. + +config REGULATOR_DA9210 + tristate "Dialog Semiconductor DA9210 regulator" + depends on I2C + select REGMAP_I2C + help + Say y here to support for the Dialog Semiconductor DA9210. + The DA9210 is a multi-phase synchronous step down + converter 12A DC-DC Buck controlled through an I2C + interface. + +config REGULATOR_DA9211 + tristate "Dialog Semiconductor DA9211/DA9212/DA9213/DA9223/DA9214/DA9224/DA9215/DA9225 regulator" + depends on I2C + select REGMAP_I2C + help + Say y here to support for the Dialog Semiconductor DA9211/DA9212 + /DA9213/DA9214/DA9215. + The DA9211/DA9212/DA9213/DA9214/DA9215 is a multi-phase synchronous + step down converter 12A or 16A DC-DC Buck controlled through an I2C + interface. + +config REGULATOR_DBX500_PRCMU + bool + +config REGULATOR_DB8500_PRCMU + bool "ST-Ericsson DB8500 Voltage Domain Regulators" + depends on MFD_DB8500_PRCMU + select REGULATOR_DBX500_PRCMU + help + This driver supports the voltage domain regulators controlled by the + DB8500 PRCMU + +config REGULATOR_FAN53555 + tristate "Fairchild FAN53555 Regulator" + depends on I2C + select REGMAP_I2C + help + This driver supports Fairchild FAN53555 Digitally Programmable + TinyBuck Regulator. The FAN53555 is a step-down switching voltage + regulator that delivers a digitally programmable output from an + input voltage supply of 2.5V to 5.5V. The output voltage is + programmed through an I2C interface. + +config REGULATOR_GPIO + tristate "GPIO regulator support" + depends on GPIOLIB || COMPILE_TEST + help + This driver provides support for regulators that can be + controlled via gpios. + It is capable of supporting current and voltage regulators + and the platform has to provide a mapping of GPIO-states + to target volts/amps. + +config REGULATOR_HI6421 + tristate "HiSilicon Hi6421 PMIC voltage regulator support" + depends on MFD_HI6421_PMIC && OF + help + This driver provides support for the voltage regulators on the + HiSilicon Hi6421 PMU / Codec IC. + Hi6421 is a multi-function device which, on regulator part, provides + 21 general purpose LDOs, 3 dedicated LDOs, and 5 BUCKs. All + of them come with support to either ECO (idle) or sleep mode. + +config REGULATOR_HI6421V530 + tristate "HiSilicon Hi6421v530 PMIC voltage regulator support" + depends on MFD_HI6421_PMIC && OF + help + This driver provides support for the voltage regulators on + HiSilicon Hi6421v530 PMU / Codec IC. + Hi6421v530 is a multi-function device which, on regulator part, + provides 5 general purpose LDOs, and all of them come with support + to either ECO (idle) or sleep mode. + +config REGULATOR_HI655X + tristate "Hisilicon HI655X PMIC regulators support" + depends on ARCH_HISI || COMPILE_TEST + depends on MFD_HI655X_PMIC && OF + help + This driver provides support for the voltage regulators of the + Hisilicon Hi655x PMIC device. + +config REGULATOR_ISL9305 + tristate "Intersil ISL9305 regulator" + depends on I2C + select REGMAP_I2C + help + This driver supports ISL9305 voltage regulator chip. + +config REGULATOR_ISL6271A + tristate "Intersil ISL6271A Power regulator" + depends on I2C + help + This driver supports ISL6271A voltage regulator chip. + +config REGULATOR_LM363X + tristate "TI LM363X voltage regulators" + depends on MFD_TI_LMU + help + This driver supports LM3631 and LM3632 voltage regulators for + the LCD bias. + One boost output voltage is configurable and always on. + Other LDOs are used for the display module. + +config REGULATOR_LP3971 + tristate "National Semiconductors LP3971 PMIC regulator driver" + depends on I2C + help + Say Y here to support the voltage regulators and convertors + on National Semiconductors LP3971 PMIC + +config REGULATOR_LP3972 + tristate "National Semiconductors LP3972 PMIC regulator driver" + depends on I2C + help + Say Y here to support the voltage regulators and convertors + on National Semiconductors LP3972 PMIC + +config REGULATOR_LP872X + tristate "TI/National Semiconductor LP8720/LP8725 voltage regulators" + depends on I2C + select REGMAP_I2C + help + This driver supports LP8720/LP8725 PMIC + +config REGULATOR_LP873X + tristate "TI LP873X Power regulators" + depends on MFD_TI_LP873X && OF + help + This driver supports LP873X voltage regulator chips. LP873X + provides two step-down converters and two general-purpose LDO + voltage regulators. It supports software based voltage control + for different voltage domains + +config REGULATOR_LP8755 + tristate "TI LP8755 High Performance PMU driver" + depends on I2C + select REGMAP_I2C + help + This driver supports LP8755 High Performance PMU driver. This + chip contains six step-down DC/DC converters which can support + 9 mode multiphase configuration. + +config REGULATOR_LP87565 + tristate "TI LP87565 Power regulators" + depends on MFD_TI_LP87565 && OF + help + This driver supports LP87565 voltage regulator chips. LP87565 + provides four step-down converters. It supports software based + voltage control for different voltage domains + +config REGULATOR_LP8788 + tristate "TI LP8788 Power Regulators" + depends on MFD_LP8788 + help + This driver supports LP8788 voltage regulator chip. + +config REGULATOR_LTC3589 + tristate "LTC3589 8-output voltage regulator" + depends on I2C + select REGMAP_I2C + help + This enables support for the LTC3589, LTC3589-1, and LTC3589-2 + 8-output regulators controlled via I2C. + +config REGULATOR_LTC3676 + tristate "LTC3676 8-output voltage regulator" + depends on I2C + select REGMAP_I2C + help + This enables support for the LTC3676 + 8-output regulators controlled via I2C. + +config REGULATOR_MAX14577 + tristate "Maxim 14577/77836 regulator" + depends on MFD_MAX14577 + help + This driver controls a Maxim MAX14577/77836 regulator via I2C bus. + The MAX14577 regulators include safeout LDO and charger current + regulator. The MAX77836 has two additional LDOs. + +config REGULATOR_MAX1586 + tristate "Maxim 1586/1587 voltage regulator" + depends on I2C + help + This driver controls a Maxim 1586 or 1587 voltage output + regulator via I2C bus. The provided regulator is suitable + for PXA27x chips to control VCC_CORE and VCC_USIM voltages. + +config REGULATOR_MAX77620 + tristate "Maxim 77620/MAX20024 voltage regulator" + depends on MFD_MAX77620 + help + This driver controls Maxim MAX77620 voltage output regulator + via I2C bus. The provided regulator is suitable for Tegra + chip to control Step-Down DC-DC and LDOs. Say Y here to + enable the regulator driver. + +config REGULATOR_MAX8649 + tristate "Maxim 8649 voltage regulator" + depends on I2C + select REGMAP_I2C + help + This driver controls a Maxim 8649 voltage output regulator via + I2C bus. + +config REGULATOR_MAX8660 + tristate "Maxim 8660/8661 voltage regulator" + depends on I2C + help + This driver controls a Maxim 8660/8661 voltage output + regulator via I2C bus. + +config REGULATOR_MAX8907 + tristate "Maxim 8907 voltage regulator" + depends on MFD_MAX8907 + help + This driver controls a Maxim 8907 voltage output regulator + via I2C bus. The provided regulator is suitable for Tegra + chip to control Step-Down DC-DC and LDOs. + +config REGULATOR_MAX8925 + tristate "Maxim MAX8925 Power Management IC" + depends on MFD_MAX8925 + help + Say y here to support the voltage regulaltor of Maxim MAX8925 PMIC. + +config REGULATOR_MAX8952 + tristate "Maxim MAX8952 Power Management IC" + depends on I2C + help + This driver controls a Maxim 8952 voltage output regulator + via I2C bus. Maxim 8952 has one voltage output and supports 4 DVS + modes ranging from 0.77V to 1.40V by 0.01V steps. + +config REGULATOR_MAX8973 + tristate "Maxim MAX8973 voltage regulator " + depends on I2C + depends on THERMAL && THERMAL_OF + select REGMAP_I2C + help + The MAXIM MAX8973 high-efficiency. three phase, DC-DC step-down + switching regulator delievers up to 9A of output current. Each + phase operates at a 2MHz fixed frequency with a 120 deg shift + from the adjacent phase, allowing the use of small magnetic component. + +config REGULATOR_MAX8997 + tristate "Maxim 8997/8966 regulator" + depends on MFD_MAX8997 + help + This driver controls a Maxim 8997/8966 regulator + via I2C bus. The provided regulator is suitable for S5PC110, + S5PV210, and Exynos-4 chips to control VCC_CORE and + VCC_USIM voltages. + +config REGULATOR_MAX8998 + tristate "Maxim 8998 voltage regulator" + depends on MFD_MAX8998 + help + This driver controls a Maxim 8998 voltage output regulator + via I2C bus. The provided regulator is suitable for S3C6410 + and S5PC1XX chips to control VCC_CORE and VCC_USIM voltages. + +config REGULATOR_MAX77686 + tristate "Maxim 77686 regulator" + depends on MFD_MAX77686 + help + This driver controls a Maxim 77686 regulator + via I2C bus. The provided regulator is suitable for + Exynos-4 chips to control VARM and VINT voltages. + +config REGULATOR_MAX77693 + tristate "Maxim 77693/77843 regulator" + depends on (MFD_MAX77693 || MFD_MAX77843) + help + This driver controls a Maxim 77693/77843 regulators via I2C bus. + The regulators include two LDOs, 'SAFEOUT1', 'SAFEOUT2' + and one current regulator 'CHARGER'. This is suitable for + Exynos-4x12 (MAX77693) or Exynos5433 (MAX77843) SoC chips. + +config REGULATOR_MAX77802 + tristate "Maxim 77802 regulator" + depends on MFD_MAX77686 + help + This driver controls a Maxim 77802 regulator + via I2C bus. The provided regulator is suitable for + Exynos5420/Exynos5800 SoCs to control various voltages. + It includes support for control of voltage and ramp speed. + +config REGULATOR_MC13XXX_CORE + tristate + +config REGULATOR_MC13783 + tristate "Freescale MC13783 regulator driver" + depends on MFD_MC13XXX + select REGULATOR_MC13XXX_CORE + help + Say y here to support the regulators found on the Freescale MC13783 + PMIC. + +config REGULATOR_MC13892 + tristate "Freescale MC13892 regulator driver" + depends on MFD_MC13XXX + select REGULATOR_MC13XXX_CORE + help + Say y here to support the regulators found on the Freescale MC13892 + PMIC. + +config REGULATOR_MT6311 + tristate "MediaTek MT6311 PMIC" + depends on I2C + select REGMAP_I2C + help + Say y here to select this option to enable the power regulator of + MediaTek MT6311 PMIC. + This driver supports the control of different power rails of device + through regulator interface. + +config REGULATOR_MT6323 + tristate "MediaTek MT6323 PMIC" + depends on MFD_MT6397 + help + Say y here to select this option to enable the power regulator of + MediaTek MT6323 PMIC. + This driver supports the control of different power rails of device + through regulator interface. + +config REGULATOR_MT6380 + tristate "MediaTek MT6380 PMIC" + depends on MTK_PMIC_WRAP + help + Say y here to select this option to enable the power regulator of + MediaTek MT6380 PMIC. + This driver supports the control of different power rails of device + through regulator interface. + +config REGULATOR_MT6397 + tristate "MediaTek MT6397 PMIC" + depends on MFD_MT6397 + help + Say y here to select this option to enable the power regulator of + MediaTek MT6397 PMIC. + This driver supports the control of different power rails of device + through regulator interface. + +config REGULATOR_PALMAS + tristate "TI Palmas PMIC Regulators" + depends on MFD_PALMAS + help + If you wish to control the regulators on the Palmas series of + chips say Y here. This will enable support for all the software + controllable SMPS/LDO regulators. + + The regulators available on Palmas series chips vary depending + on the muxing. This is handled automatically in the driver by + reading the mux info from OTP. + +config REGULATOR_PBIAS + tristate "PBIAS OMAP regulator driver" + depends on (ARCH_OMAP || COMPILE_TEST) && MFD_SYSCON + help + Say y here to support pbias regulator for mmc1:SD card i/o + on OMAP SoCs. + This driver provides support for OMAP pbias modelled + regulators. + +config REGULATOR_PCAP + tristate "Motorola PCAP2 regulator driver" + depends on EZX_PCAP + help + This driver provides support for the voltage regulators of the + PCAP2 PMIC. + +config REGULATOR_PCF50633 + tristate "NXP PCF50633 regulator driver" + depends on MFD_PCF50633 + help + Say Y here to support the voltage regulators and convertors + on PCF50633 + +config REGULATOR_PFUZE100 + tristate "Freescale PFUZE100/200/3000/3001 regulator driver" + depends on I2C + select REGMAP_I2C + help + Say y here to support the regulators found on the Freescale + PFUZE100/200/3000/3001 PMIC. + +config REGULATOR_PV88060 + tristate "Powerventure Semiconductor PV88060 regulator" + depends on I2C + select REGMAP_I2C + help + Say y here to support the voltage regulators and convertors + PV88060 + +config REGULATOR_PV88080 + tristate "Powerventure Semiconductor PV88080 regulator" + depends on I2C + select REGMAP_I2C + help + Say y here to support the buck convertors on PV88080 + +config REGULATOR_PV88090 + tristate "Powerventure Semiconductor PV88090 regulator" + depends on I2C + select REGMAP_I2C + help + Say y here to support the voltage regulators and convertors + on PV88090 + +config REGULATOR_PWM + tristate "PWM voltage regulator" + depends on PWM + help + This driver supports PWM controlled voltage regulators. PWM + duty cycle can increase or decrease the voltage. + +config REGULATOR_QCOM_RPM + tristate "Qualcomm RPM regulator driver" + depends on MFD_QCOM_RPM + help + If you say yes to this option, support will be included for the + regulators exposed by the Resource Power Manager found in Qualcomm + 8660, 8960 and 8064 based devices. + + Say M here if you want to include support for the regulators on the + Qualcomm RPM as a module. The module will be named + "qcom_rpm-regulator". + +config REGULATOR_QCOM_RPMH + tristate "Qualcomm Technologies, Inc. RPMh regulator driver" + depends on QCOM_RPMH || COMPILE_TEST + help + This driver supports control of PMIC regulators via the RPMh hardware + block found on Qualcomm Technologies Inc. SoCs. RPMh regulator + control allows for voting on regulator state between multiple + processors within the SoC. + +config REGULATOR_QCOM_SMD_RPM + tristate "Qualcomm SMD based RPM regulator driver" + depends on QCOM_SMD_RPM + help + If you say yes to this option, support will be included for the + regulators exposed by the Resource Power Manager found in Qualcomm + 8974 based devices. + + Say M here if you want to include support for the regulators on the + Qualcomm RPM as a module. The module will be named + "qcom_smd-regulator". + +config REGULATOR_QCOM_SPMI + tristate "Qualcomm SPMI regulator driver" + depends on SPMI || COMPILE_TEST + help + If you say yes to this option, support will be included for the + regulators found in Qualcomm SPMI PMICs. + + Say M here if you want to include support for the regulators on the + Qualcomm SPMI PMICs as a module. The module will be named + "qcom_spmi-regulator". + +config REGULATOR_RC5T583 + tristate "RICOH RC5T583 Power regulators" + depends on MFD_RC5T583 + help + Select this option to enable the power regulator of RICOH + PMIC RC5T583. + This driver supports the control of different power rails of device + through regulator interface. The device supports multiple DCDC/LDO + outputs which can be controlled by i2c communication. + +config REGULATOR_RK808 + tristate "Rockchip RK805/RK808/RK818 Power regulators" + depends on MFD_RK808 + help + Select this option to enable the power regulator of ROCKCHIP + PMIC RK805,RK808 and RK818. + This driver supports the control of different power rails of device + through regulator interface. The device supports multiple DCDC/LDO + outputs which can be controlled by i2c communication. + +config REGULATOR_RN5T618 + tristate "Ricoh RN5T567/618 voltage regulators" + depends on MFD_RN5T618 + help + Say y here to support the regulators found on Ricoh RN5T567, + RN5T618 or RC5T619 PMIC. + +config REGULATOR_RT5033 + tristate "Richtek RT5033 Regulators" + depends on MFD_RT5033 + help + This adds support for voltage and current regulators in Richtek + RT5033 PMIC. The device supports multiple regulators like + current source, LDO and Buck. + +config REGULATOR_S2MPA01 + tristate "Samsung S2MPA01 voltage regulator" + depends on MFD_SEC_CORE + help + This driver controls Samsung S2MPA01 voltage output regulator + via I2C bus. S2MPA01 has 10 Bucks and 26 LDO outputs. + +config REGULATOR_S2MPS11 + tristate "Samsung S2MPS11/13/14/15/S2MPU02 voltage regulator" + depends on MFD_SEC_CORE + help + This driver supports a Samsung S2MPS11/13/14/15/S2MPU02 voltage + output regulator via I2C bus. The chip is comprised of high efficient + Buck converters including Dual-Phase Buck converter, Buck-Boost + converter, various LDOs. + +config REGULATOR_S5M8767 + tristate "Samsung S5M8767A voltage regulator" + depends on MFD_SEC_CORE + help + This driver supports a Samsung S5M8767A voltage output regulator + via I2C bus. S5M8767A have 9 Bucks and 28 LDOs output and + supports DVS mode with 8bits of output voltage control. + +config REGULATOR_SC2731 + tristate "Spreadtrum SC2731 power regulator driver" + depends on MFD_SC27XX_PMIC || COMPILE_TEST + help + This driver provides support for the voltage regulators on the + SC2731 PMIC. + +config REGULATOR_SKY81452 + tristate "Skyworks Solutions SKY81452 voltage regulator" + depends on MFD_SKY81452 + help + This driver supports Skyworks SKY81452 voltage output regulator + via I2C bus. SKY81452 has one voltage linear regulator can be + programmed from 4.5V to 20V. + + This driver can also be built as a module. If so, the module + will be called sky81452-regulator. + +config REGULATOR_STM32_VREFBUF + tristate "STMicroelectronics STM32 VREFBUF" + depends on ARCH_STM32 || COMPILE_TEST + help + This driver supports STMicroelectronics STM32 VREFBUF (voltage + reference buffer) which can be used as voltage reference for + internal ADCs, DACs and also for external components through + dedicated Vref+ pin. + + This driver can also be built as a module. If so, the module + will be called stm32-vrefbuf. + +config REGULATOR_TI_ABB + tristate "TI Adaptive Body Bias on-chip LDO" + depends on ARCH_OMAP + help + Select this option to support Texas Instruments' on-chip Adaptive Body + Bias (ABB) LDO regulators. It is recommended that this option be + enabled on required TI SoC. Certain Operating Performance Points + on TI SoCs may be unstable without enabling this as it provides + device specific optimized bias to allow/optimize functionality. + +config REGULATOR_STW481X_VMMC + bool "ST Microelectronics STW481X VMMC regulator" + depends on MFD_STW481X || COMPILE_TEST + default y if MFD_STW481X + help + This driver supports the internal VMMC regulator in the STw481x + PMIC chips. + +config REGULATOR_SY8106A + tristate "Silergy SY8106A regulator" + depends on I2C && (OF || COMPILE_TEST) + select REGMAP_I2C + help + This driver supports SY8106A single output regulator. + +config REGULATOR_TPS51632 + tristate "TI TPS51632 Power Regulator" + depends on I2C + select REGMAP_I2C + help + This driver supports TPS51632 voltage regulator chip. + The TPS51632 is 3-2-1 Phase D-Cap+ Step Down Driverless Controller + with Serial VID control and DVFS. + The voltage output can be configure through I2C interface or PWM + interface. + +config REGULATOR_TPS6105X + tristate "TI TPS6105X Power regulators" + depends on TPS6105X + default y if TPS6105X + help + This driver supports TPS61050/TPS61052 voltage regulator chips. + It is a single boost converter primarily for white LEDs and + audio amplifiers. + +config REGULATOR_TPS62360 + tristate "TI TPS6236x Power Regulator" + depends on I2C + select REGMAP_I2C + help + This driver supports TPS6236x voltage regulator chip. This + regulator is meant for processor core supply. This chip is + high-frequency synchronous step down dc-dc converter optimized + for battery-powered portable applications. + +config REGULATOR_TPS65023 + tristate "TI TPS65023 Power regulators" + depends on I2C + select REGMAP_I2C + help + This driver supports TPS65023 voltage regulator chips. TPS65023 provides + three step-down converters and two general-purpose LDO voltage regulators. + It supports TI's software based Class-2 SmartReflex implementation. + +config REGULATOR_TPS6507X + tristate "TI TPS6507X Power regulators" + depends on I2C + help + This driver supports TPS6507X voltage regulator chips. TPS6507X provides + three step-down converters and two general-purpose LDO voltage regulators. + It supports TI's software based Class-2 SmartReflex implementation. + +config REGULATOR_TPS65086 + tristate "TI TPS65086 Power regulators" + depends on MFD_TPS65086 + help + This driver provides support for the voltage regulators on + TI TPS65086 PMICs. + +config REGULATOR_TPS65090 + tristate "TI TPS65090 Power regulator" + depends on MFD_TPS65090 + help + This driver provides support for the voltage regulators on the + TI TPS65090 PMIC. + +config REGULATOR_TPS65132 + tristate "TI TPS65132 Dual Output Power regulators" + depends on I2C && GPIOLIB + select REGMAP_I2C + help + This driver supports TPS65132 single inductor - dual output + power supply specifcally designed for display panels. + +config REGULATOR_TPS65217 + tristate "TI TPS65217 Power regulators" + depends on MFD_TPS65217 + help + This driver supports TPS65217 voltage regulator chips. TPS65217 + provides three step-down converters and four general-purpose LDO + voltage regulators. It supports software based voltage control + for different voltage domains + +config REGULATOR_TPS65218 + tristate "TI TPS65218 Power regulators" + depends on MFD_TPS65218 && OF + help + This driver supports TPS65218 voltage regulator chips. TPS65218 + provides six step-down converters and one general-purpose LDO + voltage regulators. It supports software based voltage control + for different voltage domains + +config REGULATOR_TPS6524X + tristate "TI TPS6524X Power regulators" + depends on SPI + help + This driver supports TPS6524X voltage regulator chips. TPS6524X + provides three step-down converters and two general-purpose LDO + voltage regulators. This device is interfaced using a customized + serial interface currently supported on the sequencer serial + port controller. + +config REGULATOR_TPS6586X + tristate "TI TPS6586X Power regulators" + depends on MFD_TPS6586X + help + This driver supports TPS6586X voltage regulator chips. + +config REGULATOR_TPS65910 + tristate "TI TPS65910/TPS65911 Power Regulators" + depends on MFD_TPS65910 + help + This driver supports TPS65910/TPS65911 voltage regulator chips. + +config REGULATOR_TPS65912 + tristate "TI TPS65912 Power regulator" + depends on MFD_TPS65912 + help + This driver supports TPS65912 voltage regulator chip. + +config REGULATOR_TPS80031 + tristate "TI TPS80031/TPS80032 power regulator driver" + depends on MFD_TPS80031 + help + TPS80031/ TPS80032 Fully Integrated Power Management with Power + Path and Battery Charger. It has 5 configurable step-down + converters, 11 general purpose LDOs, VBUS generator and digital + output to control regulators. + +config REGULATOR_TWL4030 + tristate "TI TWL4030/TWL5030/TWL6030/TPS659x0 PMIC" + depends on TWL4030_CORE + help + This driver supports the voltage regulators provided by + this family of companion chips. + +config REGULATOR_UNIPHIER + tristate "UniPhier regulator driver" + depends on ARCH_UNIPHIER || COMPILE_TEST + depends on OF && MFD_SYSCON + default ARCH_UNIPHIER + help + Support for regulators implemented on Socionext UniPhier SoCs. + +config REGULATOR_VCTRL + tristate "Voltage controlled regulators" + depends on OF + help + This driver provides support for voltage regulators whose output + voltage is controlled by the voltage of another regulator. + +config REGULATOR_VEXPRESS + tristate "Versatile Express regulators" + depends on VEXPRESS_CONFIG + help + This driver provides support for voltage regulators available + on the ARM Ltd's Versatile Express platform. + +config REGULATOR_WM831X + tristate "Wolfson Microelectronics WM831x PMIC regulators" + depends on MFD_WM831X + help + Support the voltage and current regulators of the WM831x series + of PMIC devices. + +config REGULATOR_WM8350 + tristate "Wolfson Microelectronics WM8350 AudioPlus PMIC" + depends on MFD_WM8350 + help + This driver provides support for the voltage and current regulators + of the WM8350 AudioPlus PMIC. + +config REGULATOR_WM8400 + tristate "Wolfson Microelectronics WM8400 AudioPlus PMIC" + depends on MFD_WM8400 + help + This driver provides support for the voltage regulators of the + WM8400 AudioPlus PMIC. + +config REGULATOR_WM8994 + tristate "Wolfson Microelectronics WM8994 CODEC" + depends on MFD_WM8994 + help + This driver provides support for the voltage regulators on the + WM8994 CODEC. + +endif + diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile new file mode 100644 index 000000000..bba9c4851 --- /dev/null +++ b/drivers/regulator/Makefile @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for regulator drivers. +# + + +obj-$(CONFIG_REGULATOR) += core.o dummy.o fixed-helper.o helpers.o devres.o +obj-$(CONFIG_OF) += of_regulator.o +obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o +obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o +obj-$(CONFIG_REGULATOR_USERSPACE_CONSUMER) += userspace-consumer.o + +obj-$(CONFIG_REGULATOR_88PG86X) += 88pg86x.o +obj-$(CONFIG_REGULATOR_88PM800) += 88pm800-regulator.o +obj-$(CONFIG_REGULATOR_88PM8607) += 88pm8607.o +obj-$(CONFIG_REGULATOR_CPCAP) += cpcap-regulator.o +obj-$(CONFIG_REGULATOR_AAT2870) += aat2870-regulator.o +obj-$(CONFIG_REGULATOR_AB3100) += ab3100.o +obj-$(CONFIG_REGULATOR_AB8500) += ab8500-ext.o ab8500.o +obj-$(CONFIG_REGULATOR_ACT8865) += act8865-regulator.o +obj-$(CONFIG_REGULATOR_ACT8945A) += act8945a-regulator.o +obj-$(CONFIG_REGULATOR_AD5398) += ad5398.o +obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o +obj-$(CONFIG_REGULATOR_ARIZONA_LDO1) += arizona-ldo1.o +obj-$(CONFIG_REGULATOR_ARIZONA_MICSUPP) += arizona-micsupp.o +obj-$(CONFIG_REGULATOR_AS3711) += as3711-regulator.o +obj-$(CONFIG_REGULATOR_AS3722) += as3722-regulator.o +obj-$(CONFIG_REGULATOR_AXP20X) += axp20x-regulator.o +obj-$(CONFIG_REGULATOR_BCM590XX) += bcm590xx-regulator.o +obj-$(CONFIG_REGULATOR_BD718XX) += bd71837-regulator.o +obj-$(CONFIG_REGULATOR_BD9571MWV) += bd9571mwv-regulator.o +obj-$(CONFIG_REGULATOR_DA903X) += da903x.o +obj-$(CONFIG_REGULATOR_DA9052) += da9052-regulator.o +obj-$(CONFIG_REGULATOR_DA9055) += da9055-regulator.o +obj-$(CONFIG_REGULATOR_DA9062) += da9062-regulator.o +obj-$(CONFIG_REGULATOR_DA9063) += da9063-regulator.o +obj-$(CONFIG_REGULATOR_DA9210) += da9210-regulator.o +obj-$(CONFIG_REGULATOR_DA9211) += da9211-regulator.o +obj-$(CONFIG_REGULATOR_DBX500_PRCMU) += dbx500-prcmu.o +obj-$(CONFIG_REGULATOR_DB8500_PRCMU) += db8500-prcmu.o +obj-$(CONFIG_REGULATOR_FAN53555) += fan53555.o +obj-$(CONFIG_REGULATOR_GPIO) += gpio-regulator.o +obj-$(CONFIG_REGULATOR_HI6421) += hi6421-regulator.o +obj-$(CONFIG_REGULATOR_HI6421V530) += hi6421v530-regulator.o +obj-$(CONFIG_REGULATOR_HI655X) += hi655x-regulator.o +obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o +obj-$(CONFIG_REGULATOR_ISL9305) += isl9305.o +obj-$(CONFIG_REGULATOR_LM363X) += lm363x-regulator.o +obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o +obj-$(CONFIG_REGULATOR_LP3972) += lp3972.o +obj-$(CONFIG_REGULATOR_LP872X) += lp872x.o +obj-$(CONFIG_REGULATOR_LP873X) += lp873x-regulator.o +obj-$(CONFIG_REGULATOR_LP87565) += lp87565-regulator.o +obj-$(CONFIG_REGULATOR_LP8788) += lp8788-buck.o +obj-$(CONFIG_REGULATOR_LP8788) += lp8788-ldo.o +obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o +obj-$(CONFIG_REGULATOR_LTC3589) += ltc3589.o +obj-$(CONFIG_REGULATOR_LTC3676) += ltc3676.o +obj-$(CONFIG_REGULATOR_MAX14577) += max14577-regulator.o +obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o +obj-$(CONFIG_REGULATOR_MAX77620) += max77620-regulator.o +obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o +obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o +obj-$(CONFIG_REGULATOR_MAX8907) += max8907-regulator.o +obj-$(CONFIG_REGULATOR_MAX8925) += max8925-regulator.o +obj-$(CONFIG_REGULATOR_MAX8952) += max8952.o +obj-$(CONFIG_REGULATOR_MAX8973) += max8973-regulator.o +obj-$(CONFIG_REGULATOR_MAX8997) += max8997-regulator.o +obj-$(CONFIG_REGULATOR_MAX8998) += max8998.o +obj-$(CONFIG_REGULATOR_MAX77686) += max77686-regulator.o +obj-$(CONFIG_REGULATOR_MAX77693) += max77693-regulator.o +obj-$(CONFIG_REGULATOR_MAX77802) += max77802-regulator.o +obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o +obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o +obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o +obj-$(CONFIG_REGULATOR_MT6311) += mt6311-regulator.o +obj-$(CONFIG_REGULATOR_MT6323) += mt6323-regulator.o +obj-$(CONFIG_REGULATOR_MT6380) += mt6380-regulator.o +obj-$(CONFIG_REGULATOR_MT6397) += mt6397-regulator.o +obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o +obj-$(CONFIG_REGULATOR_QCOM_RPMH) += qcom-rpmh-regulator.o +obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o +obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o +obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o +obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o +obj-$(CONFIG_REGULATOR_PV88060) += pv88060-regulator.o +obj-$(CONFIG_REGULATOR_PV88080) += pv88080-regulator.o +obj-$(CONFIG_REGULATOR_PV88090) += pv88090-regulator.o +obj-$(CONFIG_REGULATOR_PWM) += pwm-regulator.o +obj-$(CONFIG_REGULATOR_TPS51632) += tps51632-regulator.o +obj-$(CONFIG_REGULATOR_PBIAS) += pbias-regulator.o +obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o +obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o +obj-$(CONFIG_REGULATOR_RC5T583) += rc5t583-regulator.o +obj-$(CONFIG_REGULATOR_RK808) += rk808-regulator.o +obj-$(CONFIG_REGULATOR_RN5T618) += rn5t618-regulator.o +obj-$(CONFIG_REGULATOR_RT5033) += rt5033-regulator.o +obj-$(CONFIG_REGULATOR_S2MPA01) += s2mpa01.o +obj-$(CONFIG_REGULATOR_S2MPS11) += s2mps11.o +obj-$(CONFIG_REGULATOR_S5M8767) += s5m8767.o +obj-$(CONFIG_REGULATOR_SC2731) += sc2731-regulator.o +obj-$(CONFIG_REGULATOR_SKY81452) += sky81452-regulator.o +obj-$(CONFIG_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o +obj-$(CONFIG_REGULATOR_STW481X_VMMC) += stw481x-vmmc.o +obj-$(CONFIG_REGULATOR_SY8106A) += sy8106a-regulator.o +obj-$(CONFIG_REGULATOR_TI_ABB) += ti-abb-regulator.o +obj-$(CONFIG_REGULATOR_TPS6105X) += tps6105x-regulator.o +obj-$(CONFIG_REGULATOR_TPS62360) += tps62360-regulator.o +obj-$(CONFIG_REGULATOR_TPS65023) += tps65023-regulator.o +obj-$(CONFIG_REGULATOR_TPS6507X) += tps6507x-regulator.o +obj-$(CONFIG_REGULATOR_TPS65086) += tps65086-regulator.o +obj-$(CONFIG_REGULATOR_TPS65090) += tps65090-regulator.o +obj-$(CONFIG_REGULATOR_TPS65217) += tps65217-regulator.o +obj-$(CONFIG_REGULATOR_TPS65218) += tps65218-regulator.o +obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o +obj-$(CONFIG_REGULATOR_TPS6586X) += tps6586x-regulator.o +obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o +obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o +obj-$(CONFIG_REGULATOR_TPS80031) += tps80031-regulator.o +obj-$(CONFIG_REGULATOR_TPS65132) += tps65132-regulator.o +obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o twl6030-regulator.o +obj-$(CONFIG_REGULATOR_UNIPHIER) += uniphier-regulator.o +obj-$(CONFIG_REGULATOR_VCTRL) += vctrl-regulator.o +obj-$(CONFIG_REGULATOR_VEXPRESS) += vexpress-regulator.o +obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o +obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o +obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o +obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o +obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o +obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o + +ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/aat2870-regulator.c b/drivers/regulator/aat2870-regulator.c new file mode 100644 index 000000000..afc5b5900 --- /dev/null +++ b/drivers/regulator/aat2870-regulator.c @@ -0,0 +1,213 @@ +/* + * linux/drivers/regulator/aat2870-regulator.c + * + * Copyright (c) 2011, NVIDIA Corporation. + * Author: Jin Park <jinyoungp@nvidia.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/aat2870.h> + +struct aat2870_regulator { + struct aat2870_data *aat2870; + struct regulator_desc desc; + + u8 enable_addr; + u8 enable_shift; + u8 enable_mask; + + u8 voltage_addr; + u8 voltage_shift; + u8 voltage_mask; +}; + +static int aat2870_ldo_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct aat2870_regulator *ri = rdev_get_drvdata(rdev); + struct aat2870_data *aat2870 = ri->aat2870; + + return aat2870->update(aat2870, ri->voltage_addr, ri->voltage_mask, + selector << ri->voltage_shift); +} + +static int aat2870_ldo_get_voltage_sel(struct regulator_dev *rdev) +{ + struct aat2870_regulator *ri = rdev_get_drvdata(rdev); + struct aat2870_data *aat2870 = ri->aat2870; + u8 val; + int ret; + + ret = aat2870->read(aat2870, ri->voltage_addr, &val); + if (ret) + return ret; + + return (val & ri->voltage_mask) >> ri->voltage_shift; +} + +static int aat2870_ldo_enable(struct regulator_dev *rdev) +{ + struct aat2870_regulator *ri = rdev_get_drvdata(rdev); + struct aat2870_data *aat2870 = ri->aat2870; + + return aat2870->update(aat2870, ri->enable_addr, ri->enable_mask, + ri->enable_mask); +} + +static int aat2870_ldo_disable(struct regulator_dev *rdev) +{ + struct aat2870_regulator *ri = rdev_get_drvdata(rdev); + struct aat2870_data *aat2870 = ri->aat2870; + + return aat2870->update(aat2870, ri->enable_addr, ri->enable_mask, 0); +} + +static int aat2870_ldo_is_enabled(struct regulator_dev *rdev) +{ + struct aat2870_regulator *ri = rdev_get_drvdata(rdev); + struct aat2870_data *aat2870 = ri->aat2870; + u8 val; + int ret; + + ret = aat2870->read(aat2870, ri->enable_addr, &val); + if (ret) + return ret; + + return val & ri->enable_mask ? 1 : 0; +} + +static const struct regulator_ops aat2870_ldo_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_voltage_sel = aat2870_ldo_set_voltage_sel, + .get_voltage_sel = aat2870_ldo_get_voltage_sel, + .enable = aat2870_ldo_enable, + .disable = aat2870_ldo_disable, + .is_enabled = aat2870_ldo_is_enabled, +}; + +static const unsigned int aat2870_ldo_voltages[] = { + 1200000, 1300000, 1500000, 1600000, + 1800000, 2000000, 2200000, 2500000, + 2600000, 2700000, 2800000, 2900000, + 3000000, 3100000, 3200000, 3300000, +}; + +#define AAT2870_LDO(ids) \ + { \ + .desc = { \ + .name = #ids, \ + .id = AAT2870_ID_##ids, \ + .n_voltages = ARRAY_SIZE(aat2870_ldo_voltages), \ + .volt_table = aat2870_ldo_voltages, \ + .ops = &aat2870_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + } + +static struct aat2870_regulator aat2870_regulators[] = { + AAT2870_LDO(LDOA), + AAT2870_LDO(LDOB), + AAT2870_LDO(LDOC), + AAT2870_LDO(LDOD), +}; + +static struct aat2870_regulator *aat2870_get_regulator(int id) +{ + struct aat2870_regulator *ri = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(aat2870_regulators); i++) { + ri = &aat2870_regulators[i]; + if (ri->desc.id == id) + break; + } + + if (i == ARRAY_SIZE(aat2870_regulators)) + return NULL; + + ri->enable_addr = AAT2870_LDO_EN; + ri->enable_shift = id - AAT2870_ID_LDOA; + ri->enable_mask = 0x1 << ri->enable_shift; + + ri->voltage_addr = (id - AAT2870_ID_LDOA) / 2 ? + AAT2870_LDO_CD : AAT2870_LDO_AB; + ri->voltage_shift = (id - AAT2870_ID_LDOA) % 2 ? 0 : 4; + ri->voltage_mask = 0xF << ri->voltage_shift; + + return ri; +} + +static int aat2870_regulator_probe(struct platform_device *pdev) +{ + struct aat2870_regulator *ri; + struct regulator_config config = { }; + struct regulator_dev *rdev; + + ri = aat2870_get_regulator(pdev->id); + if (!ri) { + dev_err(&pdev->dev, "Invalid device ID, %d\n", pdev->id); + return -EINVAL; + } + ri->aat2870 = dev_get_drvdata(pdev->dev.parent); + + config.dev = &pdev->dev; + config.driver_data = ri; + config.init_data = dev_get_platdata(&pdev->dev); + + rdev = devm_regulator_register(&pdev->dev, &ri->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "Failed to register regulator %s\n", + ri->desc.name); + return PTR_ERR(rdev); + } + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static struct platform_driver aat2870_regulator_driver = { + .driver = { + .name = "aat2870-regulator", + }, + .probe = aat2870_regulator_probe, +}; + +static int __init aat2870_regulator_init(void) +{ + return platform_driver_register(&aat2870_regulator_driver); +} +subsys_initcall(aat2870_regulator_init); + +static void __exit aat2870_regulator_exit(void) +{ + platform_driver_unregister(&aat2870_regulator_driver); +} +module_exit(aat2870_regulator_exit); + +MODULE_DESCRIPTION("AnalogicTech AAT2870 Regulator"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jin Park <jinyoungp@nvidia.com>"); +MODULE_ALIAS("platform:aat2870-regulator"); diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c new file mode 100644 index 000000000..de2644490 --- /dev/null +++ b/drivers/regulator/ab3100.c @@ -0,0 +1,745 @@ +/* + * drivers/regulator/ab3100.c + * + * Copyright (C) 2008-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Low-level control of the AB3100 IC Low Dropout (LDO) + * regulators, external regulator and buck converter + * Author: Mattias Wallin <mattias.wallin@stericsson.com> + * Author: Linus Walleij <linus.walleij@stericsson.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/ab3100.h> +#include <linux/mfd/abx500.h> +#include <linux/of.h> +#include <linux/regulator/of_regulator.h> + +/* LDO registers and some handy masking definitions for AB3100 */ +#define AB3100_LDO_A 0x40 +#define AB3100_LDO_C 0x41 +#define AB3100_LDO_D 0x42 +#define AB3100_LDO_E 0x43 +#define AB3100_LDO_E_SLEEP 0x44 +#define AB3100_LDO_F 0x45 +#define AB3100_LDO_G 0x46 +#define AB3100_LDO_H 0x47 +#define AB3100_LDO_H_SLEEP_MODE 0 +#define AB3100_LDO_H_SLEEP_EN 2 +#define AB3100_LDO_ON 4 +#define AB3100_LDO_H_VSEL_AC 5 +#define AB3100_LDO_K 0x48 +#define AB3100_LDO_EXT 0x49 +#define AB3100_BUCK 0x4A +#define AB3100_BUCK_SLEEP 0x4B +#define AB3100_REG_ON_MASK 0x10 + +/** + * struct ab3100_regulator + * A struct passed around the individual regulator functions + * @platform_device: platform device holding this regulator + * @dev: handle to the device + * @plfdata: AB3100 platform data passed in at probe time + * @regreg: regulator register number in the AB3100 + */ +struct ab3100_regulator { + struct regulator_dev *rdev; + struct device *dev; + struct ab3100_platform_data *plfdata; + u8 regreg; +}; + +/* The order in which registers are initialized */ +static const u8 ab3100_reg_init_order[AB3100_NUM_REGULATORS+2] = { + AB3100_LDO_A, + AB3100_LDO_C, + AB3100_LDO_E, + AB3100_LDO_E_SLEEP, + AB3100_LDO_F, + AB3100_LDO_G, + AB3100_LDO_H, + AB3100_LDO_K, + AB3100_LDO_EXT, + AB3100_BUCK, + AB3100_BUCK_SLEEP, + AB3100_LDO_D, +}; + +/* Preset (hardware defined) voltages for these regulators */ +#define LDO_A_VOLTAGE 2750000 +#define LDO_C_VOLTAGE 2650000 +#define LDO_D_VOLTAGE 2650000 + +static const unsigned int ldo_e_buck_typ_voltages[] = { + 1800000, + 1400000, + 1300000, + 1200000, + 1100000, + 1050000, + 900000, +}; + +static const unsigned int ldo_f_typ_voltages[] = { + 1800000, + 1400000, + 1300000, + 1200000, + 1100000, + 1050000, + 2500000, + 2650000, +}; + +static const unsigned int ldo_g_typ_voltages[] = { + 2850000, + 2750000, + 1800000, + 1500000, +}; + +static const unsigned int ldo_h_typ_voltages[] = { + 2750000, + 1800000, + 1500000, + 1200000, +}; + +static const unsigned int ldo_k_typ_voltages[] = { + 2750000, + 1800000, +}; + + +/* The regulator devices */ +static struct ab3100_regulator +ab3100_regulators[AB3100_NUM_REGULATORS] = { + { + .regreg = AB3100_LDO_A, + }, + { + .regreg = AB3100_LDO_C, + }, + { + .regreg = AB3100_LDO_D, + }, + { + .regreg = AB3100_LDO_E, + }, + { + .regreg = AB3100_LDO_F, + }, + { + .regreg = AB3100_LDO_G, + }, + { + .regreg = AB3100_LDO_H, + }, + { + .regreg = AB3100_LDO_K, + }, + { + .regreg = AB3100_LDO_EXT, + /* No voltages for the external regulator */ + }, + { + .regreg = AB3100_BUCK, + }, +}; + +/* + * General functions for enable, disable and is_enabled used for + * LDO: A,C,E,F,G,H,K,EXT and BUCK + */ +static int ab3100_enable_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = rdev_get_drvdata(reg); + int err; + u8 regval; + + err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, + ®val); + if (err) { + dev_warn(®->dev, "failed to get regid %d value\n", + abreg->regreg); + return err; + } + + /* The regulator is already on, no reason to go further */ + if (regval & AB3100_REG_ON_MASK) + return 0; + + regval |= AB3100_REG_ON_MASK; + + err = abx500_set_register_interruptible(abreg->dev, 0, abreg->regreg, + regval); + if (err) { + dev_warn(®->dev, "failed to set regid %d value\n", + abreg->regreg); + return err; + } + + return 0; +} + +static int ab3100_disable_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = rdev_get_drvdata(reg); + int err; + u8 regval; + + /* + * LDO D is a special regulator. When it is disabled, the entire + * system is shut down. So this is handled specially. + */ + pr_info("Called ab3100_disable_regulator\n"); + if (abreg->regreg == AB3100_LDO_D) { + dev_info(®->dev, "disabling LDO D - shut down system\n"); + /* Setting LDO D to 0x00 cuts the power to the SoC */ + return abx500_set_register_interruptible(abreg->dev, 0, + AB3100_LDO_D, 0x00U); + } + + /* + * All other regulators are handled here + */ + err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, + ®val); + if (err) { + dev_err(®->dev, "unable to get register 0x%x\n", + abreg->regreg); + return err; + } + regval &= ~AB3100_REG_ON_MASK; + return abx500_set_register_interruptible(abreg->dev, 0, abreg->regreg, + regval); +} + +static int ab3100_is_enabled_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = rdev_get_drvdata(reg); + u8 regval; + int err; + + err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, + ®val); + if (err) { + dev_err(®->dev, "unable to get register 0x%x\n", + abreg->regreg); + return err; + } + + return regval & AB3100_REG_ON_MASK; +} + +static int ab3100_get_voltage_regulator(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = rdev_get_drvdata(reg); + u8 regval; + int err; + + /* + * For variable types, read out setting and index into + * supplied voltage list. + */ + err = abx500_get_register_interruptible(abreg->dev, 0, + abreg->regreg, ®val); + if (err) { + dev_warn(®->dev, + "failed to get regulator value in register %02x\n", + abreg->regreg); + return err; + } + + /* The 3 highest bits index voltages */ + regval &= 0xE0; + regval >>= 5; + + if (regval >= reg->desc->n_voltages) { + dev_err(®->dev, + "regulator register %02x contains an illegal voltage setting\n", + abreg->regreg); + return -EINVAL; + } + + return reg->desc->volt_table[regval]; +} + +static int ab3100_set_voltage_regulator_sel(struct regulator_dev *reg, + unsigned selector) +{ + struct ab3100_regulator *abreg = rdev_get_drvdata(reg); + u8 regval; + int err; + + err = abx500_get_register_interruptible(abreg->dev, 0, + abreg->regreg, ®val); + if (err) { + dev_warn(®->dev, + "failed to get regulator register %02x\n", + abreg->regreg); + return err; + } + + /* The highest three bits control the variable regulators */ + regval &= ~0xE0; + regval |= (selector << 5); + + err = abx500_set_register_interruptible(abreg->dev, 0, + abreg->regreg, regval); + if (err) + dev_warn(®->dev, "failed to set regulator register %02x\n", + abreg->regreg); + + return err; +} + +static int ab3100_set_suspend_voltage_regulator(struct regulator_dev *reg, + int uV) +{ + struct ab3100_regulator *abreg = rdev_get_drvdata(reg); + u8 regval; + int err; + int bestindex; + u8 targetreg; + + if (abreg->regreg == AB3100_LDO_E) + targetreg = AB3100_LDO_E_SLEEP; + else if (abreg->regreg == AB3100_BUCK) + targetreg = AB3100_BUCK_SLEEP; + else + return -EINVAL; + + /* LDO E and BUCK have special suspend voltages you can set */ + bestindex = regulator_map_voltage_iterate(reg, uV, uV); + + err = abx500_get_register_interruptible(abreg->dev, 0, + targetreg, ®val); + if (err) { + dev_warn(®->dev, + "failed to get regulator register %02x\n", + targetreg); + return err; + } + + /* The highest three bits control the variable regulators */ + regval &= ~0xE0; + regval |= (bestindex << 5); + + err = abx500_set_register_interruptible(abreg->dev, 0, + targetreg, regval); + if (err) + dev_warn(®->dev, "failed to set regulator register %02x\n", + abreg->regreg); + + return err; +} + +/* + * The external regulator can just define a fixed voltage. + */ +static int ab3100_get_voltage_regulator_external(struct regulator_dev *reg) +{ + struct ab3100_regulator *abreg = rdev_get_drvdata(reg); + + if (abreg->plfdata) + return abreg->plfdata->external_voltage; + else + /* TODO: encode external voltage into device tree */ + return 0; +} + +static struct regulator_ops regulator_ops_fixed = { + .list_voltage = regulator_list_voltage_linear, + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, +}; + +static struct regulator_ops regulator_ops_variable = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator, + .set_voltage_sel = ab3100_set_voltage_regulator_sel, + .list_voltage = regulator_list_voltage_table, +}; + +static struct regulator_ops regulator_ops_variable_sleepable = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator, + .set_voltage_sel = ab3100_set_voltage_regulator_sel, + .set_suspend_voltage = ab3100_set_suspend_voltage_regulator, + .list_voltage = regulator_list_voltage_table, +}; + +/* + * LDO EXT is an external regulator so it is really + * not possible to set any voltage locally here, AB3100 + * is an on/off switch plain an simple. The external + * voltage is defined in the board set-up if any. + */ +static struct regulator_ops regulator_ops_external = { + .enable = ab3100_enable_regulator, + .disable = ab3100_disable_regulator, + .is_enabled = ab3100_is_enabled_regulator, + .get_voltage = ab3100_get_voltage_regulator_external, +}; + +static struct regulator_desc +ab3100_regulator_desc[AB3100_NUM_REGULATORS] = { + { + .name = "LDO_A", + .id = AB3100_LDO_A, + .ops = ®ulator_ops_fixed, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .min_uV = LDO_A_VOLTAGE, + .enable_time = 200, + }, + { + .name = "LDO_C", + .id = AB3100_LDO_C, + .ops = ®ulator_ops_fixed, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .min_uV = LDO_C_VOLTAGE, + .enable_time = 200, + }, + { + .name = "LDO_D", + .id = AB3100_LDO_D, + .ops = ®ulator_ops_fixed, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .min_uV = LDO_D_VOLTAGE, + .enable_time = 200, + }, + { + .name = "LDO_E", + .id = AB3100_LDO_E, + .ops = ®ulator_ops_variable_sleepable, + .n_voltages = ARRAY_SIZE(ldo_e_buck_typ_voltages), + .volt_table = ldo_e_buck_typ_voltages, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_time = 200, + }, + { + .name = "LDO_F", + .id = AB3100_LDO_F, + .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_f_typ_voltages), + .volt_table = ldo_f_typ_voltages, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_time = 600, + }, + { + .name = "LDO_G", + .id = AB3100_LDO_G, + .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_g_typ_voltages), + .volt_table = ldo_g_typ_voltages, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_time = 400, + }, + { + .name = "LDO_H", + .id = AB3100_LDO_H, + .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_h_typ_voltages), + .volt_table = ldo_h_typ_voltages, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_time = 200, + }, + { + .name = "LDO_K", + .id = AB3100_LDO_K, + .ops = ®ulator_ops_variable, + .n_voltages = ARRAY_SIZE(ldo_k_typ_voltages), + .volt_table = ldo_k_typ_voltages, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_time = 200, + }, + { + .name = "LDO_EXT", + .id = AB3100_LDO_EXT, + .ops = ®ulator_ops_external, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "BUCK", + .id = AB3100_BUCK, + .ops = ®ulator_ops_variable_sleepable, + .n_voltages = ARRAY_SIZE(ldo_e_buck_typ_voltages), + .volt_table = ldo_e_buck_typ_voltages, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_time = 1000, + }, +}; + +static int ab3100_regulator_register(struct platform_device *pdev, + struct ab3100_platform_data *plfdata, + struct regulator_init_data *init_data, + struct device_node *np, + unsigned long id) +{ + struct regulator_desc *desc; + struct ab3100_regulator *reg; + struct regulator_dev *rdev; + struct regulator_config config = { }; + int err, i; + + for (i = 0; i < AB3100_NUM_REGULATORS; i++) { + desc = &ab3100_regulator_desc[i]; + if (desc->id == id) + break; + } + if (desc->id != id) + return -ENODEV; + + /* Same index used for this array */ + reg = &ab3100_regulators[i]; + + /* + * Initialize per-regulator struct. + * Inherit platform data, this comes down from the + * i2c boarddata, from the machine. So if you want to + * see what it looks like for a certain machine, go + * into the machine I2C setup. + */ + reg->dev = &pdev->dev; + if (plfdata) { + reg->plfdata = plfdata; + config.init_data = &plfdata->reg_constraints[i]; + } else if (np) { + config.of_node = np; + config.init_data = init_data; + } + config.dev = &pdev->dev; + config.driver_data = reg; + + rdev = devm_regulator_register(&pdev->dev, desc, &config); + if (IS_ERR(rdev)) { + err = PTR_ERR(rdev); + dev_err(&pdev->dev, + "%s: failed to register regulator %s err %d\n", + __func__, desc->name, + err); + return err; + } + + /* Then set a pointer back to the registered regulator */ + reg->rdev = rdev; + return 0; +} + +static struct of_regulator_match ab3100_regulator_matches[] = { + { .name = "ab3100_ldo_a", .driver_data = (void *) AB3100_LDO_A, }, + { .name = "ab3100_ldo_c", .driver_data = (void *) AB3100_LDO_C, }, + { .name = "ab3100_ldo_d", .driver_data = (void *) AB3100_LDO_D, }, + { .name = "ab3100_ldo_e", .driver_data = (void *) AB3100_LDO_E, }, + { .name = "ab3100_ldo_f", .driver_data = (void *) AB3100_LDO_F }, + { .name = "ab3100_ldo_g", .driver_data = (void *) AB3100_LDO_G }, + { .name = "ab3100_ldo_h", .driver_data = (void *) AB3100_LDO_H }, + { .name = "ab3100_ldo_k", .driver_data = (void *) AB3100_LDO_K }, + { .name = "ab3100_ext", .driver_data = (void *) AB3100_LDO_EXT }, + { .name = "ab3100_buck", .driver_data = (void *) AB3100_BUCK }, +}; + +/* + * Initial settings of ab3100 registers. + * Common for below LDO regulator settings are that + * bit 7-5 controls voltage. Bit 4 turns regulator ON(1) or OFF(0). + * Bit 3-2 controls sleep enable and bit 1-0 controls sleep mode. + */ +/* LDO_A 0x16: 2.75V, ON, SLEEP_A, SLEEP OFF GND */ +#define LDO_A_SETTING 0x16 +/* LDO_C 0x10: 2.65V, ON, SLEEP_A or B, SLEEP full power */ +#define LDO_C_SETTING 0x10 +/* LDO_D 0x10: 2.65V, ON, sleep mode not used */ +#define LDO_D_SETTING 0x10 +/* LDO_E 0x10: 1.8V, ON, SLEEP_A or B, SLEEP full power */ +#define LDO_E_SETTING 0x10 +/* LDO_E SLEEP 0x00: 1.8V, not used, SLEEP_A or B, not used */ +#define LDO_E_SLEEP_SETTING 0x00 +/* LDO_F 0xD0: 2.5V, ON, SLEEP_A or B, SLEEP full power */ +#define LDO_F_SETTING 0xD0 +/* LDO_G 0x00: 2.85V, OFF, SLEEP_A or B, SLEEP full power */ +#define LDO_G_SETTING 0x00 +/* LDO_H 0x18: 2.75V, ON, SLEEP_B, SLEEP full power */ +#define LDO_H_SETTING 0x18 +/* LDO_K 0x00: 2.75V, OFF, SLEEP_A or B, SLEEP full power */ +#define LDO_K_SETTING 0x00 +/* LDO_EXT 0x00: Voltage not set, OFF, not used, not used */ +#define LDO_EXT_SETTING 0x00 +/* BUCK 0x7D: 1.2V, ON, SLEEP_A and B, SLEEP low power */ +#define BUCK_SETTING 0x7D +/* BUCK SLEEP 0xAC: 1.05V, Not used, SLEEP_A and B, Not used */ +#define BUCK_SLEEP_SETTING 0xAC + +static const u8 ab3100_reg_initvals[] = { + LDO_A_SETTING, + LDO_C_SETTING, + LDO_E_SETTING, + LDO_E_SLEEP_SETTING, + LDO_F_SETTING, + LDO_G_SETTING, + LDO_H_SETTING, + LDO_K_SETTING, + LDO_EXT_SETTING, + BUCK_SETTING, + BUCK_SLEEP_SETTING, + LDO_D_SETTING, +}; + +static int ab3100_regulators_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < AB3100_NUM_REGULATORS; i++) { + struct ab3100_regulator *reg = &ab3100_regulators[i]; + + reg->rdev = NULL; + } + return 0; +} + +static int +ab3100_regulator_of_probe(struct platform_device *pdev, struct device_node *np) +{ + int err, i; + + /* + * Set up the regulator registers, as was previously done with + * platform data. + */ + /* Set up regulators */ + for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) { + err = abx500_set_register_interruptible(&pdev->dev, 0, + ab3100_reg_init_order[i], + ab3100_reg_initvals[i]); + if (err) { + dev_err(&pdev->dev, "regulator initialization failed with error %d\n", + err); + return err; + } + } + + for (i = 0; i < ARRAY_SIZE(ab3100_regulator_matches); i++) { + err = ab3100_regulator_register( + pdev, NULL, ab3100_regulator_matches[i].init_data, + ab3100_regulator_matches[i].of_node, + (unsigned long)ab3100_regulator_matches[i].driver_data); + if (err) { + ab3100_regulators_remove(pdev); + return err; + } + } + + return 0; +} + + +static int ab3100_regulators_probe(struct platform_device *pdev) +{ + struct ab3100_platform_data *plfdata = dev_get_platdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; + int err = 0; + u8 data; + int i; + + /* Check chip state */ + err = abx500_get_register_interruptible(&pdev->dev, 0, + AB3100_LDO_D, &data); + if (err) { + dev_err(&pdev->dev, "could not read initial status of LDO_D\n"); + return err; + } + if (data & 0x10) + dev_notice(&pdev->dev, + "chip is already in active mode (Warm start)\n"); + else + dev_notice(&pdev->dev, + "chip is in inactive mode (Cold start)\n"); + + if (np) { + err = of_regulator_match(&pdev->dev, np, + ab3100_regulator_matches, + ARRAY_SIZE(ab3100_regulator_matches)); + if (err < 0) { + dev_err(&pdev->dev, + "Error parsing regulator init data: %d\n", err); + return err; + } + return ab3100_regulator_of_probe(pdev, np); + } + + /* Set up regulators */ + for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) { + err = abx500_set_register_interruptible(&pdev->dev, 0, + ab3100_reg_init_order[i], + plfdata->reg_initvals[i]); + if (err) { + dev_err(&pdev->dev, "regulator initialization failed with error %d\n", + err); + return err; + } + } + + /* Register the regulators */ + for (i = 0; i < AB3100_NUM_REGULATORS; i++) { + struct regulator_desc *desc = &ab3100_regulator_desc[i]; + + err = ab3100_regulator_register(pdev, plfdata, NULL, NULL, + desc->id); + if (err) { + ab3100_regulators_remove(pdev); + return err; + } + } + + return 0; +} + +static struct platform_driver ab3100_regulators_driver = { + .driver = { + .name = "ab3100-regulators", + }, + .probe = ab3100_regulators_probe, + .remove = ab3100_regulators_remove, +}; + +static __init int ab3100_regulators_init(void) +{ + return platform_driver_register(&ab3100_regulators_driver); +} + +static __exit void ab3100_regulators_exit(void) +{ + platform_driver_unregister(&ab3100_regulators_driver); +} + +subsys_initcall(ab3100_regulators_init); +module_exit(ab3100_regulators_exit); + +MODULE_AUTHOR("Mattias Wallin <mattias.wallin@stericsson.com>"); +MODULE_DESCRIPTION("AB3100 Regulator driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ab3100-regulators"); diff --git a/drivers/regulator/ab8500-ext.c b/drivers/regulator/ab8500-ext.c new file mode 100644 index 000000000..2ca00045e --- /dev/null +++ b/drivers/regulator/ab8500-ext.c @@ -0,0 +1,897 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * + * Authors: Bengt Jonsson <bengt.g.jonsson@stericsson.com> + * + * This file is based on drivers/regulator/ab8500.c + * + * AB8500 external regulators + * + * ab8500-ext supports the following regulators: + * - VextSupply3 + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/regulator/ab8500.h> + +static struct regulator_consumer_supply ab8500_vaux1_consumers[] = { + /* Main display, u8500 R3 uib */ + REGULATOR_SUPPLY("vddi", "mcde_disp_sony_acx424akp.0"), + /* Main display, u8500 uib and ST uib */ + REGULATOR_SUPPLY("vdd1", "samsung_s6d16d0.0"), + /* Secondary display, ST uib */ + REGULATOR_SUPPLY("vdd1", "samsung_s6d16d0.1"), + /* SFH7741 proximity sensor */ + REGULATOR_SUPPLY("vcc", "gpio-keys.0"), + /* BH1780GLS ambient light sensor */ + REGULATOR_SUPPLY("vcc", "2-0029"), + /* lsm303dlh accelerometer */ + REGULATOR_SUPPLY("vdd", "2-0018"), + /* lsm303dlhc accelerometer */ + REGULATOR_SUPPLY("vdd", "2-0019"), + /* lsm303dlh magnetometer */ + REGULATOR_SUPPLY("vdd", "2-001e"), + /* Rohm BU21013 Touchscreen devices */ + REGULATOR_SUPPLY("avdd", "3-005c"), + REGULATOR_SUPPLY("avdd", "3-005d"), + /* Synaptics RMI4 Touchscreen device */ + REGULATOR_SUPPLY("vdd", "3-004b"), + /* L3G4200D Gyroscope device */ + REGULATOR_SUPPLY("vdd", "2-0068"), + /* Ambient light sensor device */ + REGULATOR_SUPPLY("vdd", "3-0029"), + /* Pressure sensor device */ + REGULATOR_SUPPLY("vdd", "2-005c"), + /* Cypress TrueTouch Touchscreen device */ + REGULATOR_SUPPLY("vcpin", "spi8.0"), + /* Camera device */ + REGULATOR_SUPPLY("vaux12v5", "mmio_camera"), +}; + +static struct regulator_consumer_supply ab8500_vaux2_consumers[] = { + /* On-board eMMC power */ + REGULATOR_SUPPLY("vmmc", "sdi4"), + /* AB8500 audio codec */ + REGULATOR_SUPPLY("vcc-N2158", "ab8500-codec.0"), + /* AB8500 accessory detect 1 */ + REGULATOR_SUPPLY("vcc-N2158", "ab8500-acc-det.0"), + /* AB8500 Tv-out device */ + REGULATOR_SUPPLY("vcc-N2158", "mcde_tv_ab8500.4"), + /* AV8100 HDMI device */ + REGULATOR_SUPPLY("vcc-N2158", "av8100_hdmi.3"), +}; + +static struct regulator_consumer_supply ab8500_vaux3_consumers[] = { + REGULATOR_SUPPLY("v-SD-STM", "stm"), + /* External MMC slot power */ + REGULATOR_SUPPLY("vmmc", "sdi0"), +}; + +static struct regulator_consumer_supply ab8500_vtvout_consumers[] = { + /* TV-out DENC supply */ + REGULATOR_SUPPLY("vtvout", "ab8500-denc.0"), + /* Internal general-purpose ADC */ + REGULATOR_SUPPLY("vddadc", "ab8500-gpadc.0"), + /* ADC for charger */ + REGULATOR_SUPPLY("vddadc", "ab8500-charger.0"), + /* AB8500 Tv-out device */ + REGULATOR_SUPPLY("vtvout", "mcde_tv_ab8500.4"), +}; + +static struct regulator_consumer_supply ab8500_vaud_consumers[] = { + /* AB8500 audio-codec main supply */ + REGULATOR_SUPPLY("vaud", "ab8500-codec.0"), +}; + +static struct regulator_consumer_supply ab8500_vamic1_consumers[] = { + /* AB8500 audio-codec Mic1 supply */ + REGULATOR_SUPPLY("vamic1", "ab8500-codec.0"), +}; + +static struct regulator_consumer_supply ab8500_vamic2_consumers[] = { + /* AB8500 audio-codec Mic2 supply */ + REGULATOR_SUPPLY("vamic2", "ab8500-codec.0"), +}; + +static struct regulator_consumer_supply ab8500_vdmic_consumers[] = { + /* AB8500 audio-codec DMic supply */ + REGULATOR_SUPPLY("vdmic", "ab8500-codec.0"), +}; + +static struct regulator_consumer_supply ab8500_vintcore_consumers[] = { + /* SoC core supply, no device */ + REGULATOR_SUPPLY("v-intcore", NULL), + /* USB Transceiver */ + REGULATOR_SUPPLY("vddulpivio18", "ab8500-usb.0"), + /* Handled by abx500 clk driver */ + REGULATOR_SUPPLY("v-intcore", "abx500-clk.0"), +}; + +static struct regulator_consumer_supply ab8500_vana_consumers[] = { + /* DB8500 DSI */ + REGULATOR_SUPPLY("vdddsi1v2", "mcde"), + REGULATOR_SUPPLY("vdddsi1v2", "b2r2_core"), + REGULATOR_SUPPLY("vdddsi1v2", "b2r2_1_core"), + REGULATOR_SUPPLY("vdddsi1v2", "dsilink.0"), + REGULATOR_SUPPLY("vdddsi1v2", "dsilink.1"), + REGULATOR_SUPPLY("vdddsi1v2", "dsilink.2"), + /* DB8500 CSI */ + REGULATOR_SUPPLY("vddcsi1v2", "mmio_camera"), +}; + +/* ab8500 regulator register initialization */ +static struct ab8500_regulator_reg_init ab8500_reg_init[] = { + /* + * VanaRequestCtrl = HP/LP depending on VxRequest + * VextSupply1RequestCtrl = HP/LP depending on VxRequest + */ + INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL2, 0xf0, 0x00), + /* + * VextSupply2RequestCtrl = HP/LP depending on VxRequest + * VextSupply3RequestCtrl = HP/LP depending on VxRequest + * Vaux1RequestCtrl = HP/LP depending on VxRequest + * Vaux2RequestCtrl = HP/LP depending on VxRequest + */ + INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL3, 0xff, 0x00), + /* + * Vaux3RequestCtrl = HP/LP depending on VxRequest + * SwHPReq = Control through SWValid disabled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL4, 0x07, 0x00), + /* + * VanaSysClkReq1HPValid = disabled + * Vaux1SysClkReq1HPValid = disabled + * Vaux2SysClkReq1HPValid = disabled + * Vaux3SysClkReq1HPValid = disabled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID1, 0xe8, 0x00), + /* + * VextSupply1SysClkReq1HPValid = disabled + * VextSupply2SysClkReq1HPValid = disabled + * VextSupply3SysClkReq1HPValid = SysClkReq1 controlled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID2, 0x70, 0x40), + /* + * VanaHwHPReq1Valid = disabled + * Vaux1HwHPreq1Valid = disabled + * Vaux2HwHPReq1Valid = disabled + * Vaux3HwHPReqValid = disabled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID1, 0xe8, 0x00), + /* + * VextSupply1HwHPReq1Valid = disabled + * VextSupply2HwHPReq1Valid = disabled + * VextSupply3HwHPReq1Valid = disabled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID2, 0x07, 0x00), + /* + * VanaHwHPReq2Valid = disabled + * Vaux1HwHPReq2Valid = disabled + * Vaux2HwHPReq2Valid = disabled + * Vaux3HwHPReq2Valid = disabled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID1, 0xe8, 0x00), + /* + * VextSupply1HwHPReq2Valid = disabled + * VextSupply2HwHPReq2Valid = disabled + * VextSupply3HwHPReq2Valid = HWReq2 controlled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID2, 0x07, 0x04), + /* + * VanaSwHPReqValid = disabled + * Vaux1SwHPReqValid = disabled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID1, 0xa0, 0x00), + /* + * Vaux2SwHPReqValid = disabled + * Vaux3SwHPReqValid = disabled + * VextSupply1SwHPReqValid = disabled + * VextSupply2SwHPReqValid = disabled + * VextSupply3SwHPReqValid = disabled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID2, 0x1f, 0x00), + /* + * SysClkReq2Valid1 = SysClkReq2 controlled + * SysClkReq3Valid1 = disabled + * SysClkReq4Valid1 = SysClkReq4 controlled + * SysClkReq5Valid1 = disabled + * SysClkReq6Valid1 = SysClkReq6 controlled + * SysClkReq7Valid1 = disabled + * SysClkReq8Valid1 = disabled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID1, 0xfe, 0x2a), + /* + * SysClkReq2Valid2 = disabled + * SysClkReq3Valid2 = disabled + * SysClkReq4Valid2 = disabled + * SysClkReq5Valid2 = disabled + * SysClkReq6Valid2 = SysClkReq6 controlled + * SysClkReq7Valid2 = disabled + * SysClkReq8Valid2 = disabled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID2, 0xfe, 0x20), + /* + * VTVoutEna = disabled + * Vintcore12Ena = disabled + * Vintcore12Sel = 1.25 V + * Vintcore12LP = inactive (HP) + * VTVoutLP = inactive (HP) + */ + INIT_REGULATOR_REGISTER(AB8500_REGUMISC1, 0xfe, 0x10), + /* + * VaudioEna = disabled + * VdmicEna = disabled + * Vamic1Ena = disabled + * Vamic2Ena = disabled + */ + INIT_REGULATOR_REGISTER(AB8500_VAUDIOSUPPLY, 0x1e, 0x00), + /* + * Vamic1_dzout = high-Z when Vamic1 is disabled + * Vamic2_dzout = high-Z when Vamic2 is disabled + */ + INIT_REGULATOR_REGISTER(AB8500_REGUCTRL1VAMIC, 0x03, 0x00), + /* + * VPll = Hw controlled (NOTE! PRCMU bits) + * VanaRegu = force off + */ + INIT_REGULATOR_REGISTER(AB8500_VPLLVANAREGU, 0x0f, 0x02), + /* + * VrefDDREna = disabled + * VrefDDRSleepMode = inactive (no pulldown) + */ + INIT_REGULATOR_REGISTER(AB8500_VREFDDR, 0x03, 0x00), + /* + * VextSupply1Regu = force LP + * VextSupply2Regu = force OFF + * VextSupply3Regu = force HP (-> STBB2=LP and TPS=LP) + * ExtSupply2Bypass = ExtSupply12LPn ball is 0 when Ena is 0 + * ExtSupply3Bypass = ExtSupply3LPn ball is 0 when Ena is 0 + */ + INIT_REGULATOR_REGISTER(AB8500_EXTSUPPLYREGU, 0xff, 0x13), + /* + * Vaux1Regu = force HP + * Vaux2Regu = force off + */ + INIT_REGULATOR_REGISTER(AB8500_VAUX12REGU, 0x0f, 0x01), + /* + * Vaux3Regu = force off + */ + INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3REGU, 0x03, 0x00), + /* + * Vaux1Sel = 2.8 V + */ + INIT_REGULATOR_REGISTER(AB8500_VAUX1SEL, 0x0f, 0x0C), + /* + * Vaux2Sel = 2.9 V + */ + INIT_REGULATOR_REGISTER(AB8500_VAUX2SEL, 0x0f, 0x0d), + /* + * Vaux3Sel = 2.91 V + */ + INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3SEL, 0x07, 0x07), + /* + * VextSupply12LP = disabled (no LP) + */ + INIT_REGULATOR_REGISTER(AB8500_REGUCTRL2SPARE, 0x01, 0x00), + /* + * Vaux1Disch = short discharge time + * Vaux2Disch = short discharge time + * Vaux3Disch = short discharge time + * Vintcore12Disch = short discharge time + * VTVoutDisch = short discharge time + * VaudioDisch = short discharge time + */ + INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH, 0xfc, 0x00), + /* + * VanaDisch = short discharge time + * VdmicPullDownEna = pulldown disabled when Vdmic is disabled + * VdmicDisch = short discharge time + */ + INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH2, 0x16, 0x00), +}; + +/* AB8500 regulators */ +static struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { + /* supplies to the display/camera */ + [AB8500_LDO_AUX1] = { + .supply_regulator = "ab8500-ext-supply3", + .constraints = { + .name = "V-DISPLAY", + .min_uV = 2800000, + .max_uV = 3300000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS, + .boot_on = 1, /* display is on at boot */ + }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux1_consumers), + .consumer_supplies = ab8500_vaux1_consumers, + }, + /* supplies to the on-board eMMC */ + [AB8500_LDO_AUX2] = { + .supply_regulator = "ab8500-ext-supply3", + .constraints = { + .name = "V-eMMC1", + .min_uV = 1100000, + .max_uV = 3300000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, + }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux2_consumers), + .consumer_supplies = ab8500_vaux2_consumers, + }, + /* supply for VAUX3, supplies to SDcard slots */ + [AB8500_LDO_AUX3] = { + .supply_regulator = "ab8500-ext-supply3", + .constraints = { + .name = "V-MMC-SD", + .min_uV = 1100000, + .max_uV = 3300000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, + }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux3_consumers), + .consumer_supplies = ab8500_vaux3_consumers, + }, + /* supply for tvout, gpadc, TVOUT LDO */ + [AB8500_LDO_TVOUT] = { + .constraints = { + .name = "V-TVOUT", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vtvout_consumers), + .consumer_supplies = ab8500_vtvout_consumers, + }, + /* supply for ab8500-vaudio, VAUDIO LDO */ + [AB8500_LDO_AUDIO] = { + .constraints = { + .name = "V-AUD", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vaud_consumers), + .consumer_supplies = ab8500_vaud_consumers, + }, + /* supply for v-anamic1 VAMic1-LDO */ + [AB8500_LDO_ANAMIC1] = { + .constraints = { + .name = "V-AMIC1", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vamic1_consumers), + .consumer_supplies = ab8500_vamic1_consumers, + }, + /* supply for v-amic2, VAMIC2 LDO, reuse constants for AMIC1 */ + [AB8500_LDO_ANAMIC2] = { + .constraints = { + .name = "V-AMIC2", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vamic2_consumers), + .consumer_supplies = ab8500_vamic2_consumers, + }, + /* supply for v-dmic, VDMIC LDO */ + [AB8500_LDO_DMIC] = { + .constraints = { + .name = "V-DMIC", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vdmic_consumers), + .consumer_supplies = ab8500_vdmic_consumers, + }, + /* supply for v-intcore12, VINTCORE12 LDO */ + [AB8500_LDO_INTCORE] = { + .constraints = { + .name = "V-INTCORE", + .min_uV = 1250000, + .max_uV = 1350000, + .input_uV = 1800000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE | + REGULATOR_CHANGE_DRMS, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, + }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vintcore_consumers), + .consumer_supplies = ab8500_vintcore_consumers, + }, + /* supply for U8500 CSI-DSI, VANA LDO */ + [AB8500_LDO_ANA] = { + .constraints = { + .name = "V-CSI-DSI", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vana_consumers), + .consumer_supplies = ab8500_vana_consumers, + }, +}; + +/* supply for VextSupply3 */ +static struct regulator_consumer_supply ab8500_ext_supply3_consumers[] = { + /* SIM supply for 3 V SIM cards */ + REGULATOR_SUPPLY("vinvsim", "sim-detect.0"), +}; + +/* + * AB8500 external regulators + */ +static struct regulator_init_data ab8500_ext_regulators[] = { + /* fixed Vbat supplies VSMPS1_EXT_1V8 */ + [AB8500_EXT_SUPPLY1] = { + .constraints = { + .name = "ab8500-ext-supply1", + .min_uV = 1800000, + .max_uV = 1800000, + .initial_mode = REGULATOR_MODE_IDLE, + .boot_on = 1, + .always_on = 1, + }, + }, + /* fixed Vbat supplies VSMPS2_EXT_1V36 and VSMPS5_EXT_1V15 */ + [AB8500_EXT_SUPPLY2] = { + .constraints = { + .name = "ab8500-ext-supply2", + .min_uV = 1360000, + .max_uV = 1360000, + }, + }, + /* fixed Vbat supplies VSMPS3_EXT_3V4 and VSMPS4_EXT_3V4 */ + [AB8500_EXT_SUPPLY3] = { + .constraints = { + .name = "ab8500-ext-supply3", + .min_uV = 3400000, + .max_uV = 3400000, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + .boot_on = 1, + }, + .num_consumer_supplies = + ARRAY_SIZE(ab8500_ext_supply3_consumers), + .consumer_supplies = ab8500_ext_supply3_consumers, + }, +}; + +static struct ab8500_regulator_platform_data ab8500_regulator_plat_data = { + .reg_init = ab8500_reg_init, + .num_reg_init = ARRAY_SIZE(ab8500_reg_init), + .regulator = ab8500_regulators, + .num_regulator = ARRAY_SIZE(ab8500_regulators), + .ext_regulator = ab8500_ext_regulators, + .num_ext_regulator = ARRAY_SIZE(ab8500_ext_regulators), +}; + +/** + * struct ab8500_ext_regulator_info - ab8500 regulator information + * @dev: device pointer + * @desc: regulator description + * @rdev: regulator device + * @cfg: regulator configuration (extension of regulator FW configuration) + * @update_bank: bank to control on/off + * @update_reg: register to control on/off + * @update_mask: mask to enable/disable and set mode of regulator + * @update_val: bits holding the regulator current mode + * @update_val_hp: bits to set EN pin active (LPn pin deactive) + * normally this means high power mode + * @update_val_lp: bits to set EN pin active and LPn pin active + * normally this means low power mode + * @update_val_hw: bits to set regulator pins in HW control + * SysClkReq pins and logic will choose mode + */ +struct ab8500_ext_regulator_info { + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *rdev; + struct ab8500_ext_regulator_cfg *cfg; + u8 update_bank; + u8 update_reg; + u8 update_mask; + u8 update_val; + u8 update_val_hp; + u8 update_val_lp; + u8 update_val_hw; +}; + +static int ab8500_ext_regulator_enable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + /* + * To satisfy both HW high power request and SW request, the regulator + * must be on in high power. + */ + if (info->cfg && info->cfg->hwreq) + regval = info->update_val_hp; + else + regval = info->update_val; + + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, regval); + if (ret < 0) { + dev_err(rdev_get_dev(info->rdev), + "couldn't set enable bits for regulator\n"); + return ret; + } + + dev_dbg(rdev_get_dev(rdev), + "%s-enable (bank, reg, mask, value): 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, regval); + + return 0; +} + +static int ab8500_ext_regulator_disable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + /* + * Set the regulator in HW request mode if configured + */ + if (info->cfg && info->cfg->hwreq) + regval = info->update_val_hw; + else + regval = 0; + + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, regval); + if (ret < 0) { + dev_err(rdev_get_dev(info->rdev), + "couldn't set disable bits for regulator\n"); + return ret; + } + + dev_dbg(rdev_get_dev(rdev), "%s-disable (bank, reg, mask, value):" + " 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, regval); + + return 0; +} + +static int ab8500_ext_regulator_is_enabled(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = abx500_get_register_interruptible(info->dev, + info->update_bank, info->update_reg, ®val); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't read 0x%x register\n", info->update_reg); + return ret; + } + + dev_dbg(rdev_get_dev(rdev), "%s-is_enabled (bank, reg, mask, value):" + " 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, regval); + + if (((regval & info->update_mask) == info->update_val_lp) || + ((regval & info->update_mask) == info->update_val_hp)) + return 1; + else + return 0; +} + +static int ab8500_ext_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + int ret = 0; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + switch (mode) { + case REGULATOR_MODE_NORMAL: + regval = info->update_val_hp; + break; + case REGULATOR_MODE_IDLE: + regval = info->update_val_lp; + break; + + default: + return -EINVAL; + } + + /* If regulator is enabled and info->cfg->hwreq is set, the regulator + must be on in high power, so we don't need to write the register with + the same value. + */ + if (ab8500_ext_regulator_is_enabled(rdev) && + !(info->cfg && info->cfg->hwreq)) { + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, regval); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "Could not set regulator mode.\n"); + return ret; + } + + dev_dbg(rdev_get_dev(rdev), + "%s-set_mode (bank, reg, mask, value): " + "0x%x, 0x%x, 0x%x, 0x%x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, regval); + } + + info->update_val = regval; + + return 0; +} + +static unsigned int ab8500_ext_regulator_get_mode(struct regulator_dev *rdev) +{ + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + if (info->update_val == info->update_val_hp) + ret = REGULATOR_MODE_NORMAL; + else if (info->update_val == info->update_val_lp) + ret = REGULATOR_MODE_IDLE; + else + ret = -EINVAL; + + return ret; +} + +static int ab8500_ext_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct regulation_constraints *regu_constraints = rdev->constraints; + + if (!regu_constraints) { + dev_err(rdev_get_dev(rdev), "No regulator constraints\n"); + return -EINVAL; + } + + if (regu_constraints->min_uV == min_uV && + regu_constraints->max_uV == max_uV) + return 0; + + dev_err(rdev_get_dev(rdev), + "Requested min %duV max %duV != constrained min %duV max %duV\n", + min_uV, max_uV, + regu_constraints->min_uV, regu_constraints->max_uV); + + return -EINVAL; +} + +static int ab8500_ext_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct regulation_constraints *regu_constraints = rdev->constraints; + + if (regu_constraints == NULL) { + dev_err(rdev_get_dev(rdev), "regulator constraints null pointer\n"); + return -EINVAL; + } + /* return the uV for the fixed regulators */ + if (regu_constraints->min_uV && regu_constraints->max_uV) { + if (regu_constraints->min_uV == regu_constraints->max_uV) + return regu_constraints->min_uV; + } + return -EINVAL; +} + +static struct regulator_ops ab8500_ext_regulator_ops = { + .enable = ab8500_ext_regulator_enable, + .disable = ab8500_ext_regulator_disable, + .is_enabled = ab8500_ext_regulator_is_enabled, + .set_mode = ab8500_ext_regulator_set_mode, + .get_mode = ab8500_ext_regulator_get_mode, + .set_voltage = ab8500_ext_set_voltage, + .list_voltage = ab8500_ext_list_voltage, +}; + +static struct ab8500_ext_regulator_info + ab8500_ext_regulator_info[AB8500_NUM_EXT_REGULATORS] = { + [AB8500_EXT_SUPPLY1] = { + .desc = { + .name = "VEXTSUPPLY1", + .ops = &ab8500_ext_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_EXT_SUPPLY1, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .update_bank = 0x04, + .update_reg = 0x08, + .update_mask = 0x03, + .update_val = 0x01, + .update_val_hp = 0x01, + .update_val_lp = 0x03, + .update_val_hw = 0x02, + }, + [AB8500_EXT_SUPPLY2] = { + .desc = { + .name = "VEXTSUPPLY2", + .ops = &ab8500_ext_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_EXT_SUPPLY2, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .update_bank = 0x04, + .update_reg = 0x08, + .update_mask = 0x0c, + .update_val = 0x04, + .update_val_hp = 0x04, + .update_val_lp = 0x0c, + .update_val_hw = 0x08, + }, + [AB8500_EXT_SUPPLY3] = { + .desc = { + .name = "VEXTSUPPLY3", + .ops = &ab8500_ext_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_EXT_SUPPLY3, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .update_bank = 0x04, + .update_reg = 0x08, + .update_mask = 0x30, + .update_val = 0x10, + .update_val_hp = 0x10, + .update_val_lp = 0x30, + .update_val_hw = 0x20, + }, +}; + +static struct of_regulator_match ab8500_ext_regulator_match[] = { + { .name = "ab8500_ext1", .driver_data = (void *) AB8500_EXT_SUPPLY1, }, + { .name = "ab8500_ext2", .driver_data = (void *) AB8500_EXT_SUPPLY2, }, + { .name = "ab8500_ext3", .driver_data = (void *) AB8500_EXT_SUPPLY3, }, +}; + +static int ab8500_ext_regulator_probe(struct platform_device *pdev) +{ + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); + struct ab8500_regulator_platform_data *pdata = &ab8500_regulator_plat_data; + struct device_node *np = pdev->dev.of_node; + struct regulator_config config = { }; + int i, err; + + if (np) { + err = of_regulator_match(&pdev->dev, np, + ab8500_ext_regulator_match, + ARRAY_SIZE(ab8500_ext_regulator_match)); + if (err < 0) { + dev_err(&pdev->dev, + "Error parsing regulator init data: %d\n", err); + return err; + } + } + + if (!ab8500) { + dev_err(&pdev->dev, "null mfd parent\n"); + return -EINVAL; + } + + /* make sure the platform data has the correct size */ + if (pdata->num_ext_regulator != ARRAY_SIZE(ab8500_ext_regulator_info)) { + dev_err(&pdev->dev, "Configuration error: size mismatch.\n"); + return -EINVAL; + } + + /* check for AB8500 2.x */ + if (is_ab8500_2p0_or_earlier(ab8500)) { + struct ab8500_ext_regulator_info *info; + + /* VextSupply3LPn is inverted on AB8500 2.x */ + info = &ab8500_ext_regulator_info[AB8500_EXT_SUPPLY3]; + info->update_val = 0x30; + info->update_val_hp = 0x30; + info->update_val_lp = 0x10; + } + + /* register all regulators */ + for (i = 0; i < ARRAY_SIZE(ab8500_ext_regulator_info); i++) { + struct ab8500_ext_regulator_info *info = NULL; + + /* assign per-regulator data */ + info = &ab8500_ext_regulator_info[i]; + info->dev = &pdev->dev; + info->cfg = (struct ab8500_ext_regulator_cfg *) + pdata->ext_regulator[i].driver_data; + + config.dev = &pdev->dev; + config.driver_data = info; + config.of_node = ab8500_ext_regulator_match[i].of_node; + config.init_data = (np) ? + ab8500_ext_regulator_match[i].init_data : + &pdata->ext_regulator[i]; + + /* register regulator with framework */ + info->rdev = devm_regulator_register(&pdev->dev, &info->desc, + &config); + if (IS_ERR(info->rdev)) { + err = PTR_ERR(info->rdev); + dev_err(&pdev->dev, "failed to register regulator %s\n", + info->desc.name); + return err; + } + + dev_dbg(rdev_get_dev(info->rdev), + "%s-probed\n", info->desc.name); + } + + return 0; +} + +static struct platform_driver ab8500_ext_regulator_driver = { + .probe = ab8500_ext_regulator_probe, + .driver = { + .name = "ab8500-ext-regulator", + }, +}; + +static int __init ab8500_ext_regulator_init(void) +{ + int ret; + + ret = platform_driver_register(&ab8500_ext_regulator_driver); + if (ret) + pr_err("Failed to register ab8500 ext regulator: %d\n", ret); + + return ret; +} +subsys_initcall(ab8500_ext_regulator_init); + +static void __exit ab8500_ext_regulator_exit(void) +{ + platform_driver_unregister(&ab8500_ext_regulator_driver); +} +module_exit(ab8500_ext_regulator_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bengt Jonsson <bengt.g.jonsson@stericsson.com>"); +MODULE_DESCRIPTION("AB8500 external regulator driver"); +MODULE_ALIAS("platform:ab8500-ext-regulator"); diff --git a/drivers/regulator/ab8500.c b/drivers/regulator/ab8500.c new file mode 100644 index 000000000..605788204 --- /dev/null +++ b/drivers/regulator/ab8500.c @@ -0,0 +1,1682 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * + * Authors: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + * Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + * Daniel Willerud <daniel.willerud@stericsson.com> for ST-Ericsson + * + * AB8500 peripheral regulators + * + * AB8500 supports the following regulators: + * VAUX1/2/3, VINTCORE, VTVOUT, VUSB, VAUDIO, VAMIC1/2, VDMIC, VANA + * + * AB8505 supports the following regulators: + * VAUX1/2/3/4/5/6, VINTCORE, VADC, VUSB, VAUDIO, VAMIC1/2, VDMIC, VANA + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/of.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/ab8500.h> +#include <linux/slab.h> + +/** + * struct ab8500_shared_mode - is used when mode is shared between + * two regulators. + * @shared_regulator: pointer to the other sharing regulator + * @lp_mode_req: low power mode requested by this regulator + */ +struct ab8500_shared_mode { + struct ab8500_regulator_info *shared_regulator; + bool lp_mode_req; +}; + +/** + * struct ab8500_regulator_info - ab8500 regulator information + * @dev: device pointer + * @desc: regulator description + * @regulator_dev: regulator device + * @shared_mode: used when mode is shared between two regulators + * @load_lp_uA: maximum load in idle (low power) mode + * @update_bank: bank to control on/off + * @update_reg: register to control on/off + * @update_mask: mask to enable/disable and set mode of regulator + * @update_val: bits holding the regulator current mode + * @update_val_idle: bits to enable the regulator in idle (low power) mode + * @update_val_normal: bits to enable the regulator in normal (high power) mode + * @mode_bank: bank with location of mode register + * @mode_reg: mode register + * @mode_mask: mask for setting mode + * @mode_val_idle: mode setting for low power + * @mode_val_normal: mode setting for normal power + * @voltage_bank: bank to control regulator voltage + * @voltage_reg: register to control regulator voltage + * @voltage_mask: mask to control regulator voltage + */ +struct ab8500_regulator_info { + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *regulator; + struct ab8500_shared_mode *shared_mode; + int load_lp_uA; + u8 update_bank; + u8 update_reg; + u8 update_mask; + u8 update_val; + u8 update_val_idle; + u8 update_val_normal; + u8 mode_bank; + u8 mode_reg; + u8 mode_mask; + u8 mode_val_idle; + u8 mode_val_normal; + u8 voltage_bank; + u8 voltage_reg; + u8 voltage_mask; + struct { + u8 voltage_limit; + u8 voltage_bank; + u8 voltage_reg; + u8 voltage_mask; + } expand_register; +}; + +/* voltage tables for the vauxn/vintcore supplies */ +static const unsigned int ldo_vauxn_voltages[] = { + 1100000, + 1200000, + 1300000, + 1400000, + 1500000, + 1800000, + 1850000, + 1900000, + 2500000, + 2650000, + 2700000, + 2750000, + 2800000, + 2900000, + 3000000, + 3300000, +}; + +static const unsigned int ldo_vaux3_voltages[] = { + 1200000, + 1500000, + 1800000, + 2100000, + 2500000, + 2750000, + 2790000, + 2910000, +}; + +static const unsigned int ldo_vaux56_voltages[] = { + 1800000, + 1050000, + 1100000, + 1200000, + 1500000, + 2200000, + 2500000, + 2790000, +}; + +static const unsigned int ldo_vintcore_voltages[] = { + 1200000, + 1225000, + 1250000, + 1275000, + 1300000, + 1325000, + 1350000, +}; + +static const unsigned int ldo_sdio_voltages[] = { + 1160000, + 1050000, + 1100000, + 1500000, + 1800000, + 2200000, + 2910000, + 3050000, +}; + +static const unsigned int fixed_1200000_voltage[] = { + 1200000, +}; + +static const unsigned int fixed_1800000_voltage[] = { + 1800000, +}; + +static const unsigned int fixed_2000000_voltage[] = { + 2000000, +}; + +static const unsigned int fixed_2050000_voltage[] = { + 2050000, +}; + +static const unsigned int fixed_3300000_voltage[] = { + 3300000, +}; + +static const unsigned int ldo_vana_voltages[] = { + 1050000, + 1075000, + 1100000, + 1125000, + 1150000, + 1175000, + 1200000, + 1225000, +}; + +static const unsigned int ldo_vaudio_voltages[] = { + 2000000, + 2100000, + 2200000, + 2300000, + 2400000, + 2500000, + 2600000, + 2600000, /* Duplicated in Vaudio and IsoUicc Control register. */ +}; + +static const unsigned int ldo_vdmic_voltages[] = { + 1800000, + 1900000, + 2000000, + 2850000, +}; + +static DEFINE_MUTEX(shared_mode_mutex); +static struct ab8500_shared_mode ldo_anamic1_shared; +static struct ab8500_shared_mode ldo_anamic2_shared; + +static int ab8500_regulator_enable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, info->update_val); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't set enable bits for regulator\n"); + return ret; + } + + dev_vdbg(rdev_get_dev(rdev), + "%s-enable (bank, reg, mask, value): 0x%x, 0x%x, 0x%x, 0x%x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, info->update_val); + + return ret; +} + +static int ab8500_regulator_disable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, 0x0); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't set disable bits for regulator\n"); + return ret; + } + + dev_vdbg(rdev_get_dev(rdev), + "%s-disable (bank, reg, mask, value): 0x%x, 0x%x, 0x%x, 0x%x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, 0x0); + + return ret; +} + +static int ab8500_regulator_is_enabled(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = abx500_get_register_interruptible(info->dev, + info->update_bank, info->update_reg, ®val); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't read 0x%x register\n", info->update_reg); + return ret; + } + + dev_vdbg(rdev_get_dev(rdev), + "%s-is_enabled (bank, reg, mask, value): 0x%x, 0x%x, 0x%x," + " 0x%x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, regval); + + if (regval & info->update_mask) + return 1; + else + return 0; +} + +static unsigned int ab8500_regulator_get_optimum_mode( + struct regulator_dev *rdev, int input_uV, + int output_uV, int load_uA) +{ + unsigned int mode; + + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + if (load_uA <= info->load_lp_uA) + mode = REGULATOR_MODE_IDLE; + else + mode = REGULATOR_MODE_NORMAL; + + return mode; +} + +static int ab8500_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + int ret = 0; + u8 bank, reg, mask, val; + bool lp_mode_req = false; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + if (info->mode_mask) { + bank = info->mode_bank; + reg = info->mode_reg; + mask = info->mode_mask; + } else { + bank = info->update_bank; + reg = info->update_reg; + mask = info->update_mask; + } + + if (info->shared_mode) + mutex_lock(&shared_mode_mutex); + + switch (mode) { + case REGULATOR_MODE_NORMAL: + if (info->shared_mode) + lp_mode_req = false; + + if (info->mode_mask) + val = info->mode_val_normal; + else + val = info->update_val_normal; + break; + case REGULATOR_MODE_IDLE: + if (info->shared_mode) { + struct ab8500_regulator_info *shared_regulator; + + shared_regulator = info->shared_mode->shared_regulator; + if (!shared_regulator->shared_mode->lp_mode_req) { + /* Other regulator prevent LP mode */ + info->shared_mode->lp_mode_req = true; + goto out_unlock; + } + + lp_mode_req = true; + } + + if (info->mode_mask) + val = info->mode_val_idle; + else + val = info->update_val_idle; + break; + default: + ret = -EINVAL; + goto out_unlock; + } + + if (info->mode_mask || ab8500_regulator_is_enabled(rdev)) { + ret = abx500_mask_and_set_register_interruptible(info->dev, + bank, reg, mask, val); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't set regulator mode\n"); + goto out_unlock; + } + + dev_vdbg(rdev_get_dev(rdev), + "%s-set_mode (bank, reg, mask, value): " + "0x%x, 0x%x, 0x%x, 0x%x\n", + info->desc.name, bank, reg, + mask, val); + } + + if (!info->mode_mask) + info->update_val = val; + + if (info->shared_mode) + info->shared_mode->lp_mode_req = lp_mode_req; + +out_unlock: + if (info->shared_mode) + mutex_unlock(&shared_mode_mutex); + + return ret; +} + +static unsigned int ab8500_regulator_get_mode(struct regulator_dev *rdev) +{ + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + u8 val; + u8 val_normal; + u8 val_idle; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + /* Need special handling for shared mode */ + if (info->shared_mode) { + if (info->shared_mode->lp_mode_req) + return REGULATOR_MODE_IDLE; + else + return REGULATOR_MODE_NORMAL; + } + + if (info->mode_mask) { + /* Dedicated register for handling mode */ + ret = abx500_get_register_interruptible(info->dev, + info->mode_bank, info->mode_reg, &val); + val = val & info->mode_mask; + + val_normal = info->mode_val_normal; + val_idle = info->mode_val_idle; + } else { + /* Mode register same as enable register */ + val = info->update_val; + val_normal = info->update_val_normal; + val_idle = info->update_val_idle; + } + + if (val == val_normal) + ret = REGULATOR_MODE_NORMAL; + else if (val == val_idle) + ret = REGULATOR_MODE_IDLE; + else + ret = -EINVAL; + + return ret; +} + +static int ab8500_regulator_get_voltage_sel(struct regulator_dev *rdev) +{ + int ret, voltage_shift; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + voltage_shift = ffs(info->voltage_mask) - 1; + + ret = abx500_get_register_interruptible(info->dev, + info->voltage_bank, info->voltage_reg, ®val); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't read voltage reg for regulator\n"); + return ret; + } + + dev_vdbg(rdev_get_dev(rdev), + "%s-get_voltage (bank, reg, mask, shift, value): " + "0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", + info->desc.name, info->voltage_bank, + info->voltage_reg, info->voltage_mask, + voltage_shift, regval); + + return (regval & info->voltage_mask) >> voltage_shift; +} + +static int ab8500_regulator_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + int ret, voltage_shift; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + voltage_shift = ffs(info->voltage_mask) - 1; + + /* set the registers for the request */ + regval = (u8)selector << voltage_shift; + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->voltage_bank, info->voltage_reg, + info->voltage_mask, regval); + if (ret < 0) + dev_err(rdev_get_dev(rdev), + "couldn't set voltage reg for regulator\n"); + + dev_vdbg(rdev_get_dev(rdev), + "%s-set_voltage (bank, reg, mask, value): 0x%x, 0x%x, 0x%x," + " 0x%x\n", + info->desc.name, info->voltage_bank, info->voltage_reg, + info->voltage_mask, regval); + + return ret; +} + +static struct regulator_ops ab8500_regulator_volt_mode_ops = { + .enable = ab8500_regulator_enable, + .disable = ab8500_regulator_disable, + .is_enabled = ab8500_regulator_is_enabled, + .get_optimum_mode = ab8500_regulator_get_optimum_mode, + .set_mode = ab8500_regulator_set_mode, + .get_mode = ab8500_regulator_get_mode, + .get_voltage_sel = ab8500_regulator_get_voltage_sel, + .set_voltage_sel = ab8500_regulator_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, +}; + +static struct regulator_ops ab8500_regulator_volt_ops = { + .enable = ab8500_regulator_enable, + .disable = ab8500_regulator_disable, + .is_enabled = ab8500_regulator_is_enabled, + .get_voltage_sel = ab8500_regulator_get_voltage_sel, + .set_voltage_sel = ab8500_regulator_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, +}; + +static struct regulator_ops ab8500_regulator_mode_ops = { + .enable = ab8500_regulator_enable, + .disable = ab8500_regulator_disable, + .is_enabled = ab8500_regulator_is_enabled, + .get_optimum_mode = ab8500_regulator_get_optimum_mode, + .set_mode = ab8500_regulator_set_mode, + .get_mode = ab8500_regulator_get_mode, + .list_voltage = regulator_list_voltage_table, +}; + +static struct regulator_ops ab8500_regulator_ops = { + .enable = ab8500_regulator_enable, + .disable = ab8500_regulator_disable, + .is_enabled = ab8500_regulator_is_enabled, + .list_voltage = regulator_list_voltage_table, +}; + +static struct regulator_ops ab8500_regulator_anamic_mode_ops = { + .enable = ab8500_regulator_enable, + .disable = ab8500_regulator_disable, + .is_enabled = ab8500_regulator_is_enabled, + .set_mode = ab8500_regulator_set_mode, + .get_mode = ab8500_regulator_get_mode, + .list_voltage = regulator_list_voltage_table, +}; + +/* AB8500 regulator information */ +static struct ab8500_regulator_info + ab8500_regulator_info[AB8500_NUM_REGULATORS] = { + /* + * Variable Voltage Regulators + * name, min mV, max mV, + * update bank, reg, mask, enable val + * volt bank, reg, mask + */ + [AB8500_LDO_AUX1] = { + .desc = { + .name = "LDO-AUX1", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_AUX1, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vauxn_voltages), + .volt_table = ldo_vauxn_voltages, + .enable_time = 200, + .supply_name = "vin", + }, + .load_lp_uA = 5000, + .update_bank = 0x04, + .update_reg = 0x09, + .update_mask = 0x03, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, + .voltage_bank = 0x04, + .voltage_reg = 0x1f, + .voltage_mask = 0x0f, + }, + [AB8500_LDO_AUX2] = { + .desc = { + .name = "LDO-AUX2", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_AUX2, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vauxn_voltages), + .volt_table = ldo_vauxn_voltages, + .enable_time = 200, + .supply_name = "vin", + }, + .load_lp_uA = 5000, + .update_bank = 0x04, + .update_reg = 0x09, + .update_mask = 0x0c, + .update_val = 0x04, + .update_val_idle = 0x0c, + .update_val_normal = 0x04, + .voltage_bank = 0x04, + .voltage_reg = 0x20, + .voltage_mask = 0x0f, + }, + [AB8500_LDO_AUX3] = { + .desc = { + .name = "LDO-AUX3", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_AUX3, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vaux3_voltages), + .volt_table = ldo_vaux3_voltages, + .enable_time = 450, + .supply_name = "vin", + }, + .load_lp_uA = 5000, + .update_bank = 0x04, + .update_reg = 0x0a, + .update_mask = 0x03, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, + .voltage_bank = 0x04, + .voltage_reg = 0x21, + .voltage_mask = 0x07, + }, + [AB8500_LDO_INTCORE] = { + .desc = { + .name = "LDO-INTCORE", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_INTCORE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vintcore_voltages), + .volt_table = ldo_vintcore_voltages, + .enable_time = 750, + }, + .load_lp_uA = 5000, + .update_bank = 0x03, + .update_reg = 0x80, + .update_mask = 0x44, + .update_val = 0x44, + .update_val_idle = 0x44, + .update_val_normal = 0x04, + .voltage_bank = 0x03, + .voltage_reg = 0x80, + .voltage_mask = 0x38, + }, + + /* + * Fixed Voltage Regulators + * name, fixed mV, + * update bank, reg, mask, enable val + */ + [AB8500_LDO_TVOUT] = { + .desc = { + .name = "LDO-TVOUT", + .ops = &ab8500_regulator_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_TVOUT, + .owner = THIS_MODULE, + .n_voltages = 1, + .volt_table = fixed_2000000_voltage, + .enable_time = 500, + }, + .load_lp_uA = 1000, + .update_bank = 0x03, + .update_reg = 0x80, + .update_mask = 0x82, + .update_val = 0x02, + .update_val_idle = 0x82, + .update_val_normal = 0x02, + }, + [AB8500_LDO_AUDIO] = { + .desc = { + .name = "LDO-AUDIO", + .ops = &ab8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_AUDIO, + .owner = THIS_MODULE, + .n_voltages = 1, + .enable_time = 140, + .volt_table = fixed_2000000_voltage, + }, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x02, + .update_val = 0x02, + }, + [AB8500_LDO_ANAMIC1] = { + .desc = { + .name = "LDO-ANAMIC1", + .ops = &ab8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_ANAMIC1, + .owner = THIS_MODULE, + .n_voltages = 1, + .enable_time = 500, + .volt_table = fixed_2050000_voltage, + }, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x08, + .update_val = 0x08, + }, + [AB8500_LDO_ANAMIC2] = { + .desc = { + .name = "LDO-ANAMIC2", + .ops = &ab8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_ANAMIC2, + .owner = THIS_MODULE, + .n_voltages = 1, + .enable_time = 500, + .volt_table = fixed_2050000_voltage, + }, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x10, + .update_val = 0x10, + }, + [AB8500_LDO_DMIC] = { + .desc = { + .name = "LDO-DMIC", + .ops = &ab8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_DMIC, + .owner = THIS_MODULE, + .n_voltages = 1, + .enable_time = 420, + .volt_table = fixed_1800000_voltage, + }, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x04, + .update_val = 0x04, + }, + + /* + * Regulators with fixed voltage and normal/idle modes + */ + [AB8500_LDO_ANA] = { + .desc = { + .name = "LDO-ANA", + .ops = &ab8500_regulator_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_ANA, + .owner = THIS_MODULE, + .n_voltages = 1, + .enable_time = 140, + .volt_table = fixed_1200000_voltage, + }, + .load_lp_uA = 1000, + .update_bank = 0x04, + .update_reg = 0x06, + .update_mask = 0x0c, + .update_val = 0x04, + .update_val_idle = 0x0c, + .update_val_normal = 0x04, + }, +}; + +/* AB8505 regulator information */ +static struct ab8500_regulator_info + ab8505_regulator_info[AB8505_NUM_REGULATORS] = { + /* + * Variable Voltage Regulators + * name, min mV, max mV, + * update bank, reg, mask, enable val + * volt bank, reg, mask + */ + [AB8505_LDO_AUX1] = { + .desc = { + .name = "LDO-AUX1", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_AUX1, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vauxn_voltages), + .volt_table = ldo_vauxn_voltages, + }, + .load_lp_uA = 5000, + .update_bank = 0x04, + .update_reg = 0x09, + .update_mask = 0x03, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, + .voltage_bank = 0x04, + .voltage_reg = 0x1f, + .voltage_mask = 0x0f, + }, + [AB8505_LDO_AUX2] = { + .desc = { + .name = "LDO-AUX2", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_AUX2, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vauxn_voltages), + .volt_table = ldo_vauxn_voltages, + }, + .load_lp_uA = 5000, + .update_bank = 0x04, + .update_reg = 0x09, + .update_mask = 0x0c, + .update_val = 0x04, + .update_val_idle = 0x0c, + .update_val_normal = 0x04, + .voltage_bank = 0x04, + .voltage_reg = 0x20, + .voltage_mask = 0x0f, + }, + [AB8505_LDO_AUX3] = { + .desc = { + .name = "LDO-AUX3", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_AUX3, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vaux3_voltages), + .volt_table = ldo_vaux3_voltages, + }, + .load_lp_uA = 5000, + .update_bank = 0x04, + .update_reg = 0x0a, + .update_mask = 0x03, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, + .voltage_bank = 0x04, + .voltage_reg = 0x21, + .voltage_mask = 0x07, + }, + [AB8505_LDO_AUX4] = { + .desc = { + .name = "LDO-AUX4", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_AUX4, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vauxn_voltages), + .volt_table = ldo_vauxn_voltages, + }, + .load_lp_uA = 5000, + /* values for Vaux4Regu register */ + .update_bank = 0x04, + .update_reg = 0x2e, + .update_mask = 0x03, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, + /* values for Vaux4SEL register */ + .voltage_bank = 0x04, + .voltage_reg = 0x2f, + .voltage_mask = 0x0f, + }, + [AB8505_LDO_AUX5] = { + .desc = { + .name = "LDO-AUX5", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_AUX5, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vaux56_voltages), + .volt_table = ldo_vaux56_voltages, + }, + .load_lp_uA = 2000, + /* values for CtrlVaux5 register */ + .update_bank = 0x01, + .update_reg = 0x55, + .update_mask = 0x18, + .update_val = 0x10, + .update_val_idle = 0x18, + .update_val_normal = 0x10, + .voltage_bank = 0x01, + .voltage_reg = 0x55, + .voltage_mask = 0x07, + }, + [AB8505_LDO_AUX6] = { + .desc = { + .name = "LDO-AUX6", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_AUX6, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vaux56_voltages), + .volt_table = ldo_vaux56_voltages, + }, + .load_lp_uA = 2000, + /* values for CtrlVaux6 register */ + .update_bank = 0x01, + .update_reg = 0x56, + .update_mask = 0x18, + .update_val = 0x10, + .update_val_idle = 0x18, + .update_val_normal = 0x10, + .voltage_bank = 0x01, + .voltage_reg = 0x56, + .voltage_mask = 0x07, + }, + [AB8505_LDO_INTCORE] = { + .desc = { + .name = "LDO-INTCORE", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_INTCORE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vintcore_voltages), + .volt_table = ldo_vintcore_voltages, + }, + .load_lp_uA = 5000, + .update_bank = 0x03, + .update_reg = 0x80, + .update_mask = 0x44, + .update_val = 0x04, + .update_val_idle = 0x44, + .update_val_normal = 0x04, + .voltage_bank = 0x03, + .voltage_reg = 0x80, + .voltage_mask = 0x38, + }, + + /* + * Fixed Voltage Regulators + * name, fixed mV, + * update bank, reg, mask, enable val + */ + [AB8505_LDO_ADC] = { + .desc = { + .name = "LDO-ADC", + .ops = &ab8500_regulator_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_ADC, + .owner = THIS_MODULE, + .n_voltages = 1, + .volt_table = fixed_2000000_voltage, + .enable_time = 10000, + }, + .load_lp_uA = 1000, + .update_bank = 0x03, + .update_reg = 0x80, + .update_mask = 0x82, + .update_val = 0x02, + .update_val_idle = 0x82, + .update_val_normal = 0x02, + }, + [AB8505_LDO_AUDIO] = { + .desc = { + .name = "LDO-AUDIO", + .ops = &ab8500_regulator_volt_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_AUDIO, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vaudio_voltages), + .volt_table = ldo_vaudio_voltages, + }, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x02, + .update_val = 0x02, + .voltage_bank = 0x01, + .voltage_reg = 0x57, + .voltage_mask = 0x70, + }, + [AB8505_LDO_ANAMIC1] = { + .desc = { + .name = "LDO-ANAMIC1", + .ops = &ab8500_regulator_anamic_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_ANAMIC1, + .owner = THIS_MODULE, + .n_voltages = 1, + .volt_table = fixed_2050000_voltage, + }, + .shared_mode = &ldo_anamic1_shared, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x08, + .update_val = 0x08, + .mode_bank = 0x01, + .mode_reg = 0x54, + .mode_mask = 0x04, + .mode_val_idle = 0x04, + .mode_val_normal = 0x00, + }, + [AB8505_LDO_ANAMIC2] = { + .desc = { + .name = "LDO-ANAMIC2", + .ops = &ab8500_regulator_anamic_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_ANAMIC2, + .owner = THIS_MODULE, + .n_voltages = 1, + .volt_table = fixed_2050000_voltage, + }, + .shared_mode = &ldo_anamic2_shared, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x10, + .update_val = 0x10, + .mode_bank = 0x01, + .mode_reg = 0x54, + .mode_mask = 0x04, + .mode_val_idle = 0x04, + .mode_val_normal = 0x00, + }, + [AB8505_LDO_AUX8] = { + .desc = { + .name = "LDO-AUX8", + .ops = &ab8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_AUX8, + .owner = THIS_MODULE, + .n_voltages = 1, + .volt_table = fixed_1800000_voltage, + }, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x04, + .update_val = 0x04, + }, + /* + * Regulators with fixed voltage and normal/idle modes + */ + [AB8505_LDO_ANA] = { + .desc = { + .name = "LDO-ANA", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8505_LDO_ANA, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vana_voltages), + .volt_table = ldo_vana_voltages, + }, + .load_lp_uA = 1000, + .update_bank = 0x04, + .update_reg = 0x06, + .update_mask = 0x0c, + .update_val = 0x04, + .update_val_idle = 0x0c, + .update_val_normal = 0x04, + .voltage_bank = 0x04, + .voltage_reg = 0x29, + .voltage_mask = 0x7, + }, +}; + +static struct ab8500_shared_mode ldo_anamic1_shared = { + .shared_regulator = &ab8505_regulator_info[AB8505_LDO_ANAMIC2], +}; + +static struct ab8500_shared_mode ldo_anamic2_shared = { + .shared_regulator = &ab8505_regulator_info[AB8505_LDO_ANAMIC1], +}; + +struct ab8500_reg_init { + u8 bank; + u8 addr; + u8 mask; +}; + +#define REG_INIT(_id, _bank, _addr, _mask) \ + [_id] = { \ + .bank = _bank, \ + .addr = _addr, \ + .mask = _mask, \ + } + +/* AB8500 register init */ +static struct ab8500_reg_init ab8500_reg_init[] = { + /* + * 0x30, VanaRequestCtrl + * 0xc0, VextSupply1RequestCtrl + */ + REG_INIT(AB8500_REGUREQUESTCTRL2, 0x03, 0x04, 0xf0), + /* + * 0x03, VextSupply2RequestCtrl + * 0x0c, VextSupply3RequestCtrl + * 0x30, Vaux1RequestCtrl + * 0xc0, Vaux2RequestCtrl + */ + REG_INIT(AB8500_REGUREQUESTCTRL3, 0x03, 0x05, 0xff), + /* + * 0x03, Vaux3RequestCtrl + * 0x04, SwHPReq + */ + REG_INIT(AB8500_REGUREQUESTCTRL4, 0x03, 0x06, 0x07), + /* + * 0x08, VanaSysClkReq1HPValid + * 0x20, Vaux1SysClkReq1HPValid + * 0x40, Vaux2SysClkReq1HPValid + * 0x80, Vaux3SysClkReq1HPValid + */ + REG_INIT(AB8500_REGUSYSCLKREQ1HPVALID1, 0x03, 0x07, 0xe8), + /* + * 0x10, VextSupply1SysClkReq1HPValid + * 0x20, VextSupply2SysClkReq1HPValid + * 0x40, VextSupply3SysClkReq1HPValid + */ + REG_INIT(AB8500_REGUSYSCLKREQ1HPVALID2, 0x03, 0x08, 0x70), + /* + * 0x08, VanaHwHPReq1Valid + * 0x20, Vaux1HwHPReq1Valid + * 0x40, Vaux2HwHPReq1Valid + * 0x80, Vaux3HwHPReq1Valid + */ + REG_INIT(AB8500_REGUHWHPREQ1VALID1, 0x03, 0x09, 0xe8), + /* + * 0x01, VextSupply1HwHPReq1Valid + * 0x02, VextSupply2HwHPReq1Valid + * 0x04, VextSupply3HwHPReq1Valid + */ + REG_INIT(AB8500_REGUHWHPREQ1VALID2, 0x03, 0x0a, 0x07), + /* + * 0x08, VanaHwHPReq2Valid + * 0x20, Vaux1HwHPReq2Valid + * 0x40, Vaux2HwHPReq2Valid + * 0x80, Vaux3HwHPReq2Valid + */ + REG_INIT(AB8500_REGUHWHPREQ2VALID1, 0x03, 0x0b, 0xe8), + /* + * 0x01, VextSupply1HwHPReq2Valid + * 0x02, VextSupply2HwHPReq2Valid + * 0x04, VextSupply3HwHPReq2Valid + */ + REG_INIT(AB8500_REGUHWHPREQ2VALID2, 0x03, 0x0c, 0x07), + /* + * 0x20, VanaSwHPReqValid + * 0x80, Vaux1SwHPReqValid + */ + REG_INIT(AB8500_REGUSWHPREQVALID1, 0x03, 0x0d, 0xa0), + /* + * 0x01, Vaux2SwHPReqValid + * 0x02, Vaux3SwHPReqValid + * 0x04, VextSupply1SwHPReqValid + * 0x08, VextSupply2SwHPReqValid + * 0x10, VextSupply3SwHPReqValid + */ + REG_INIT(AB8500_REGUSWHPREQVALID2, 0x03, 0x0e, 0x1f), + /* + * 0x02, SysClkReq2Valid1 + * 0x04, SysClkReq3Valid1 + * 0x08, SysClkReq4Valid1 + * 0x10, SysClkReq5Valid1 + * 0x20, SysClkReq6Valid1 + * 0x40, SysClkReq7Valid1 + * 0x80, SysClkReq8Valid1 + */ + REG_INIT(AB8500_REGUSYSCLKREQVALID1, 0x03, 0x0f, 0xfe), + /* + * 0x02, SysClkReq2Valid2 + * 0x04, SysClkReq3Valid2 + * 0x08, SysClkReq4Valid2 + * 0x10, SysClkReq5Valid2 + * 0x20, SysClkReq6Valid2 + * 0x40, SysClkReq7Valid2 + * 0x80, SysClkReq8Valid2 + */ + REG_INIT(AB8500_REGUSYSCLKREQVALID2, 0x03, 0x10, 0xfe), + /* + * 0x02, VTVoutEna + * 0x04, Vintcore12Ena + * 0x38, Vintcore12Sel + * 0x40, Vintcore12LP + * 0x80, VTVoutLP + */ + REG_INIT(AB8500_REGUMISC1, 0x03, 0x80, 0xfe), + /* + * 0x02, VaudioEna + * 0x04, VdmicEna + * 0x08, Vamic1Ena + * 0x10, Vamic2Ena + */ + REG_INIT(AB8500_VAUDIOSUPPLY, 0x03, 0x83, 0x1e), + /* + * 0x01, Vamic1_dzout + * 0x02, Vamic2_dzout + */ + REG_INIT(AB8500_REGUCTRL1VAMIC, 0x03, 0x84, 0x03), + /* + * 0x03, VpllRegu (NOTE! PRCMU register bits) + * 0x0c, VanaRegu + */ + REG_INIT(AB8500_VPLLVANAREGU, 0x04, 0x06, 0x0f), + /* + * 0x01, VrefDDREna + * 0x02, VrefDDRSleepMode + */ + REG_INIT(AB8500_VREFDDR, 0x04, 0x07, 0x03), + /* + * 0x03, VextSupply1Regu + * 0x0c, VextSupply2Regu + * 0x30, VextSupply3Regu + * 0x40, ExtSupply2Bypass + * 0x80, ExtSupply3Bypass + */ + REG_INIT(AB8500_EXTSUPPLYREGU, 0x04, 0x08, 0xff), + /* + * 0x03, Vaux1Regu + * 0x0c, Vaux2Regu + */ + REG_INIT(AB8500_VAUX12REGU, 0x04, 0x09, 0x0f), + /* + * 0x03, Vaux3Regu + */ + REG_INIT(AB8500_VRF1VAUX3REGU, 0x04, 0x0a, 0x03), + /* + * 0x0f, Vaux1Sel + */ + REG_INIT(AB8500_VAUX1SEL, 0x04, 0x1f, 0x0f), + /* + * 0x0f, Vaux2Sel + */ + REG_INIT(AB8500_VAUX2SEL, 0x04, 0x20, 0x0f), + /* + * 0x07, Vaux3Sel + */ + REG_INIT(AB8500_VRF1VAUX3SEL, 0x04, 0x21, 0x07), + /* + * 0x01, VextSupply12LP + */ + REG_INIT(AB8500_REGUCTRL2SPARE, 0x04, 0x22, 0x01), + /* + * 0x04, Vaux1Disch + * 0x08, Vaux2Disch + * 0x10, Vaux3Disch + * 0x20, Vintcore12Disch + * 0x40, VTVoutDisch + * 0x80, VaudioDisch + */ + REG_INIT(AB8500_REGUCTRLDISCH, 0x04, 0x43, 0xfc), + /* + * 0x02, VanaDisch + * 0x04, VdmicPullDownEna + * 0x10, VdmicDisch + */ + REG_INIT(AB8500_REGUCTRLDISCH2, 0x04, 0x44, 0x16), +}; + +/* AB8505 register init */ +static struct ab8500_reg_init ab8505_reg_init[] = { + /* + * 0x03, VarmRequestCtrl + * 0x0c, VsmpsCRequestCtrl + * 0x30, VsmpsARequestCtrl + * 0xc0, VsmpsBRequestCtrl + */ + REG_INIT(AB8505_REGUREQUESTCTRL1, 0x03, 0x03, 0xff), + /* + * 0x03, VsafeRequestCtrl + * 0x0c, VpllRequestCtrl + * 0x30, VanaRequestCtrl + */ + REG_INIT(AB8505_REGUREQUESTCTRL2, 0x03, 0x04, 0x3f), + /* + * 0x30, Vaux1RequestCtrl + * 0xc0, Vaux2RequestCtrl + */ + REG_INIT(AB8505_REGUREQUESTCTRL3, 0x03, 0x05, 0xf0), + /* + * 0x03, Vaux3RequestCtrl + * 0x04, SwHPReq + */ + REG_INIT(AB8505_REGUREQUESTCTRL4, 0x03, 0x06, 0x07), + /* + * 0x01, VsmpsASysClkReq1HPValid + * 0x02, VsmpsBSysClkReq1HPValid + * 0x04, VsafeSysClkReq1HPValid + * 0x08, VanaSysClkReq1HPValid + * 0x10, VpllSysClkReq1HPValid + * 0x20, Vaux1SysClkReq1HPValid + * 0x40, Vaux2SysClkReq1HPValid + * 0x80, Vaux3SysClkReq1HPValid + */ + REG_INIT(AB8505_REGUSYSCLKREQ1HPVALID1, 0x03, 0x07, 0xff), + /* + * 0x01, VsmpsCSysClkReq1HPValid + * 0x02, VarmSysClkReq1HPValid + * 0x04, VbbSysClkReq1HPValid + * 0x08, VsmpsMSysClkReq1HPValid + */ + REG_INIT(AB8505_REGUSYSCLKREQ1HPVALID2, 0x03, 0x08, 0x0f), + /* + * 0x01, VsmpsAHwHPReq1Valid + * 0x02, VsmpsBHwHPReq1Valid + * 0x04, VsafeHwHPReq1Valid + * 0x08, VanaHwHPReq1Valid + * 0x10, VpllHwHPReq1Valid + * 0x20, Vaux1HwHPReq1Valid + * 0x40, Vaux2HwHPReq1Valid + * 0x80, Vaux3HwHPReq1Valid + */ + REG_INIT(AB8505_REGUHWHPREQ1VALID1, 0x03, 0x09, 0xff), + /* + * 0x08, VsmpsMHwHPReq1Valid + */ + REG_INIT(AB8505_REGUHWHPREQ1VALID2, 0x03, 0x0a, 0x08), + /* + * 0x01, VsmpsAHwHPReq2Valid + * 0x02, VsmpsBHwHPReq2Valid + * 0x04, VsafeHwHPReq2Valid + * 0x08, VanaHwHPReq2Valid + * 0x10, VpllHwHPReq2Valid + * 0x20, Vaux1HwHPReq2Valid + * 0x40, Vaux2HwHPReq2Valid + * 0x80, Vaux3HwHPReq2Valid + */ + REG_INIT(AB8505_REGUHWHPREQ2VALID1, 0x03, 0x0b, 0xff), + /* + * 0x08, VsmpsMHwHPReq2Valid + */ + REG_INIT(AB8505_REGUHWHPREQ2VALID2, 0x03, 0x0c, 0x08), + /* + * 0x01, VsmpsCSwHPReqValid + * 0x02, VarmSwHPReqValid + * 0x04, VsmpsASwHPReqValid + * 0x08, VsmpsBSwHPReqValid + * 0x10, VsafeSwHPReqValid + * 0x20, VanaSwHPReqValid + * 0x40, VpllSwHPReqValid + * 0x80, Vaux1SwHPReqValid + */ + REG_INIT(AB8505_REGUSWHPREQVALID1, 0x03, 0x0d, 0xff), + /* + * 0x01, Vaux2SwHPReqValid + * 0x02, Vaux3SwHPReqValid + * 0x20, VsmpsMSwHPReqValid + */ + REG_INIT(AB8505_REGUSWHPREQVALID2, 0x03, 0x0e, 0x23), + /* + * 0x02, SysClkReq2Valid1 + * 0x04, SysClkReq3Valid1 + * 0x08, SysClkReq4Valid1 + */ + REG_INIT(AB8505_REGUSYSCLKREQVALID1, 0x03, 0x0f, 0x0e), + /* + * 0x02, SysClkReq2Valid2 + * 0x04, SysClkReq3Valid2 + * 0x08, SysClkReq4Valid2 + */ + REG_INIT(AB8505_REGUSYSCLKREQVALID2, 0x03, 0x10, 0x0e), + /* + * 0x01, Vaux4SwHPReqValid + * 0x02, Vaux4HwHPReq2Valid + * 0x04, Vaux4HwHPReq1Valid + * 0x08, Vaux4SysClkReq1HPValid + */ + REG_INIT(AB8505_REGUVAUX4REQVALID, 0x03, 0x11, 0x0f), + /* + * 0x02, VadcEna + * 0x04, VintCore12Ena + * 0x38, VintCore12Sel + * 0x40, VintCore12LP + * 0x80, VadcLP + */ + REG_INIT(AB8505_REGUMISC1, 0x03, 0x80, 0xfe), + /* + * 0x02, VaudioEna + * 0x04, VdmicEna + * 0x08, Vamic1Ena + * 0x10, Vamic2Ena + */ + REG_INIT(AB8505_VAUDIOSUPPLY, 0x03, 0x83, 0x1e), + /* + * 0x01, Vamic1_dzout + * 0x02, Vamic2_dzout + */ + REG_INIT(AB8505_REGUCTRL1VAMIC, 0x03, 0x84, 0x03), + /* + * 0x03, VsmpsARegu + * 0x0c, VsmpsASelCtrl + * 0x10, VsmpsAAutoMode + * 0x20, VsmpsAPWMMode + */ + REG_INIT(AB8505_VSMPSAREGU, 0x04, 0x03, 0x3f), + /* + * 0x03, VsmpsBRegu + * 0x0c, VsmpsBSelCtrl + * 0x10, VsmpsBAutoMode + * 0x20, VsmpsBPWMMode + */ + REG_INIT(AB8505_VSMPSBREGU, 0x04, 0x04, 0x3f), + /* + * 0x03, VsafeRegu + * 0x0c, VsafeSelCtrl + * 0x10, VsafeAutoMode + * 0x20, VsafePWMMode + */ + REG_INIT(AB8505_VSAFEREGU, 0x04, 0x05, 0x3f), + /* + * 0x03, VpllRegu (NOTE! PRCMU register bits) + * 0x0c, VanaRegu + */ + REG_INIT(AB8505_VPLLVANAREGU, 0x04, 0x06, 0x0f), + /* + * 0x03, VextSupply1Regu + * 0x0c, VextSupply2Regu + * 0x30, VextSupply3Regu + * 0x40, ExtSupply2Bypass + * 0x80, ExtSupply3Bypass + */ + REG_INIT(AB8505_EXTSUPPLYREGU, 0x04, 0x08, 0xff), + /* + * 0x03, Vaux1Regu + * 0x0c, Vaux2Regu + */ + REG_INIT(AB8505_VAUX12REGU, 0x04, 0x09, 0x0f), + /* + * 0x0f, Vaux3Regu + */ + REG_INIT(AB8505_VRF1VAUX3REGU, 0x04, 0x0a, 0x0f), + /* + * 0x3f, VsmpsASel1 + */ + REG_INIT(AB8505_VSMPSASEL1, 0x04, 0x13, 0x3f), + /* + * 0x3f, VsmpsASel2 + */ + REG_INIT(AB8505_VSMPSASEL2, 0x04, 0x14, 0x3f), + /* + * 0x3f, VsmpsASel3 + */ + REG_INIT(AB8505_VSMPSASEL3, 0x04, 0x15, 0x3f), + /* + * 0x3f, VsmpsBSel1 + */ + REG_INIT(AB8505_VSMPSBSEL1, 0x04, 0x17, 0x3f), + /* + * 0x3f, VsmpsBSel2 + */ + REG_INIT(AB8505_VSMPSBSEL2, 0x04, 0x18, 0x3f), + /* + * 0x3f, VsmpsBSel3 + */ + REG_INIT(AB8505_VSMPSBSEL3, 0x04, 0x19, 0x3f), + /* + * 0x7f, VsafeSel1 + */ + REG_INIT(AB8505_VSAFESEL1, 0x04, 0x1b, 0x7f), + /* + * 0x3f, VsafeSel2 + */ + REG_INIT(AB8505_VSAFESEL2, 0x04, 0x1c, 0x7f), + /* + * 0x3f, VsafeSel3 + */ + REG_INIT(AB8505_VSAFESEL3, 0x04, 0x1d, 0x7f), + /* + * 0x0f, Vaux1Sel + */ + REG_INIT(AB8505_VAUX1SEL, 0x04, 0x1f, 0x0f), + /* + * 0x0f, Vaux2Sel + */ + REG_INIT(AB8505_VAUX2SEL, 0x04, 0x20, 0x0f), + /* + * 0x07, Vaux3Sel + * 0x30, VRF1Sel + */ + REG_INIT(AB8505_VRF1VAUX3SEL, 0x04, 0x21, 0x37), + /* + * 0x03, Vaux4RequestCtrl + */ + REG_INIT(AB8505_VAUX4REQCTRL, 0x04, 0x2d, 0x03), + /* + * 0x03, Vaux4Regu + */ + REG_INIT(AB8505_VAUX4REGU, 0x04, 0x2e, 0x03), + /* + * 0x0f, Vaux4Sel + */ + REG_INIT(AB8505_VAUX4SEL, 0x04, 0x2f, 0x0f), + /* + * 0x04, Vaux1Disch + * 0x08, Vaux2Disch + * 0x10, Vaux3Disch + * 0x20, Vintcore12Disch + * 0x40, VTVoutDisch + * 0x80, VaudioDisch + */ + REG_INIT(AB8505_REGUCTRLDISCH, 0x04, 0x43, 0xfc), + /* + * 0x02, VanaDisch + * 0x04, VdmicPullDownEna + * 0x10, VdmicDisch + */ + REG_INIT(AB8505_REGUCTRLDISCH2, 0x04, 0x44, 0x16), + /* + * 0x01, Vaux4Disch + */ + REG_INIT(AB8505_REGUCTRLDISCH3, 0x04, 0x48, 0x01), + /* + * 0x07, Vaux5Sel + * 0x08, Vaux5LP + * 0x10, Vaux5Ena + * 0x20, Vaux5Disch + * 0x40, Vaux5DisSfst + * 0x80, Vaux5DisPulld + */ + REG_INIT(AB8505_CTRLVAUX5, 0x01, 0x55, 0xff), + /* + * 0x07, Vaux6Sel + * 0x08, Vaux6LP + * 0x10, Vaux6Ena + * 0x80, Vaux6DisPulld + */ + REG_INIT(AB8505_CTRLVAUX6, 0x01, 0x56, 0x9f), +}; + +static struct of_regulator_match ab8500_regulator_match[] = { + { .name = "ab8500_ldo_aux1", .driver_data = (void *) AB8500_LDO_AUX1, }, + { .name = "ab8500_ldo_aux2", .driver_data = (void *) AB8500_LDO_AUX2, }, + { .name = "ab8500_ldo_aux3", .driver_data = (void *) AB8500_LDO_AUX3, }, + { .name = "ab8500_ldo_intcore", .driver_data = (void *) AB8500_LDO_INTCORE, }, + { .name = "ab8500_ldo_tvout", .driver_data = (void *) AB8500_LDO_TVOUT, }, + { .name = "ab8500_ldo_audio", .driver_data = (void *) AB8500_LDO_AUDIO, }, + { .name = "ab8500_ldo_anamic1", .driver_data = (void *) AB8500_LDO_ANAMIC1, }, + { .name = "ab8500_ldo_anamic2", .driver_data = (void *) AB8500_LDO_ANAMIC2, }, + { .name = "ab8500_ldo_dmic", .driver_data = (void *) AB8500_LDO_DMIC, }, + { .name = "ab8500_ldo_ana", .driver_data = (void *) AB8500_LDO_ANA, }, +}; + +static struct of_regulator_match ab8505_regulator_match[] = { + { .name = "ab8500_ldo_aux1", .driver_data = (void *) AB8505_LDO_AUX1, }, + { .name = "ab8500_ldo_aux2", .driver_data = (void *) AB8505_LDO_AUX2, }, + { .name = "ab8500_ldo_aux3", .driver_data = (void *) AB8505_LDO_AUX3, }, + { .name = "ab8500_ldo_aux4", .driver_data = (void *) AB8505_LDO_AUX4, }, + { .name = "ab8500_ldo_aux5", .driver_data = (void *) AB8505_LDO_AUX5, }, + { .name = "ab8500_ldo_aux6", .driver_data = (void *) AB8505_LDO_AUX6, }, + { .name = "ab8500_ldo_intcore", .driver_data = (void *) AB8505_LDO_INTCORE, }, + { .name = "ab8500_ldo_adc", .driver_data = (void *) AB8505_LDO_ADC, }, + { .name = "ab8500_ldo_audio", .driver_data = (void *) AB8505_LDO_AUDIO, }, + { .name = "ab8500_ldo_anamic1", .driver_data = (void *) AB8505_LDO_ANAMIC1, }, + { .name = "ab8500_ldo_anamic2", .driver_data = (void *) AB8505_LDO_ANAMIC2, }, + { .name = "ab8500_ldo_aux8", .driver_data = (void *) AB8505_LDO_AUX8, }, + { .name = "ab8500_ldo_ana", .driver_data = (void *) AB8505_LDO_ANA, }, +}; + +static struct { + struct ab8500_regulator_info *info; + int info_size; + struct ab8500_reg_init *init; + int init_size; + struct of_regulator_match *match; + int match_size; +} abx500_regulator; + +static void abx500_get_regulator_info(struct ab8500 *ab8500) +{ + if (is_ab8505(ab8500)) { + abx500_regulator.info = ab8505_regulator_info; + abx500_regulator.info_size = ARRAY_SIZE(ab8505_regulator_info); + abx500_regulator.init = ab8505_reg_init; + abx500_regulator.init_size = AB8505_NUM_REGULATOR_REGISTERS; + abx500_regulator.match = ab8505_regulator_match; + abx500_regulator.match_size = ARRAY_SIZE(ab8505_regulator_match); + } else { + abx500_regulator.info = ab8500_regulator_info; + abx500_regulator.info_size = ARRAY_SIZE(ab8500_regulator_info); + abx500_regulator.init = ab8500_reg_init; + abx500_regulator.init_size = AB8500_NUM_REGULATOR_REGISTERS; + abx500_regulator.match = ab8500_regulator_match; + abx500_regulator.match_size = ARRAY_SIZE(ab8500_regulator_match); + } +} + +static int ab8500_regulator_register(struct platform_device *pdev, + struct regulator_init_data *init_data, + int id, struct device_node *np) +{ + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); + struct ab8500_regulator_info *info = NULL; + struct regulator_config config = { }; + + /* assign per-regulator data */ + info = &abx500_regulator.info[id]; + info->dev = &pdev->dev; + + config.dev = &pdev->dev; + config.init_data = init_data; + config.driver_data = info; + config.of_node = np; + + /* fix for hardware before ab8500v2.0 */ + if (is_ab8500_1p1_or_earlier(ab8500)) { + if (info->desc.id == AB8500_LDO_AUX3) { + info->desc.n_voltages = + ARRAY_SIZE(ldo_vauxn_voltages); + info->desc.volt_table = ldo_vauxn_voltages; + info->voltage_mask = 0xf; + } + } + + /* register regulator with framework */ + info->regulator = devm_regulator_register(&pdev->dev, &info->desc, + &config); + if (IS_ERR(info->regulator)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + info->desc.name); + return PTR_ERR(info->regulator); + } + + return 0; +} + +static int ab8500_regulator_probe(struct platform_device *pdev) +{ + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); + struct device_node *np = pdev->dev.of_node; + struct of_regulator_match *match; + int err, i; + + if (!ab8500) { + dev_err(&pdev->dev, "null mfd parent\n"); + return -EINVAL; + } + + abx500_get_regulator_info(ab8500); + + err = of_regulator_match(&pdev->dev, np, + abx500_regulator.match, + abx500_regulator.match_size); + if (err < 0) { + dev_err(&pdev->dev, + "Error parsing regulator init data: %d\n", err); + return err; + } + + match = abx500_regulator.match; + for (i = 0; i < abx500_regulator.info_size; i++) { + err = ab8500_regulator_register(pdev, match[i].init_data, i, + match[i].of_node); + if (err) + return err; + } + + return 0; +} + +static struct platform_driver ab8500_regulator_driver = { + .probe = ab8500_regulator_probe, + .driver = { + .name = "ab8500-regulator", + }, +}; + +static int __init ab8500_regulator_init(void) +{ + int ret; + + ret = platform_driver_register(&ab8500_regulator_driver); + if (ret != 0) + pr_err("Failed to register ab8500 regulator: %d\n", ret); + + return ret; +} +subsys_initcall(ab8500_regulator_init); + +static void __exit ab8500_regulator_exit(void) +{ + platform_driver_unregister(&ab8500_regulator_driver); +} +module_exit(ab8500_regulator_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sundar Iyer <sundar.iyer@stericsson.com>"); +MODULE_AUTHOR("Bengt Jonsson <bengt.g.jonsson@stericsson.com>"); +MODULE_AUTHOR("Daniel Willerud <daniel.willerud@stericsson.com>"); +MODULE_DESCRIPTION("Regulator Driver for ST-Ericsson AB8500 Mixed-Sig PMIC"); +MODULE_ALIAS("platform:ab8500-regulator"); diff --git a/drivers/regulator/act8865-regulator.c b/drivers/regulator/act8865-regulator.c new file mode 100644 index 000000000..e0239cf3f --- /dev/null +++ b/drivers/regulator/act8865-regulator.c @@ -0,0 +1,625 @@ +/* + * act8865-regulator.c - Voltage regulation for active-semi ACT88xx PMUs + * + * http://www.active-semi.com/products/power-management-units/act88xx/ + * + * Copyright (C) 2013 Atmel Corporation + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/act8865.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regmap.h> + +/* + * ACT8600 Global Register Map. + */ +#define ACT8600_SYS_MODE 0x00 +#define ACT8600_SYS_CTRL 0x01 +#define ACT8600_DCDC1_VSET 0x10 +#define ACT8600_DCDC1_CTRL 0x12 +#define ACT8600_DCDC2_VSET 0x20 +#define ACT8600_DCDC2_CTRL 0x22 +#define ACT8600_DCDC3_VSET 0x30 +#define ACT8600_DCDC3_CTRL 0x32 +#define ACT8600_SUDCDC4_VSET 0x40 +#define ACT8600_SUDCDC4_CTRL 0x41 +#define ACT8600_LDO5_VSET 0x50 +#define ACT8600_LDO5_CTRL 0x51 +#define ACT8600_LDO6_VSET 0x60 +#define ACT8600_LDO6_CTRL 0x61 +#define ACT8600_LDO7_VSET 0x70 +#define ACT8600_LDO7_CTRL 0x71 +#define ACT8600_LDO8_VSET 0x80 +#define ACT8600_LDO8_CTRL 0x81 +#define ACT8600_LDO910_CTRL 0x91 +#define ACT8600_APCH0 0xA1 +#define ACT8600_APCH1 0xA8 +#define ACT8600_APCH2 0xA9 +#define ACT8600_APCH_STAT 0xAA +#define ACT8600_OTG0 0xB0 +#define ACT8600_OTG1 0xB2 + +/* + * ACT8846 Global Register Map. + */ +#define ACT8846_SYS0 0x00 +#define ACT8846_SYS1 0x01 +#define ACT8846_REG1_VSET 0x10 +#define ACT8846_REG1_CTRL 0x12 +#define ACT8846_REG2_VSET0 0x20 +#define ACT8846_REG2_VSET1 0x21 +#define ACT8846_REG2_CTRL 0x22 +#define ACT8846_REG3_VSET0 0x30 +#define ACT8846_REG3_VSET1 0x31 +#define ACT8846_REG3_CTRL 0x32 +#define ACT8846_REG4_VSET0 0x40 +#define ACT8846_REG4_VSET1 0x41 +#define ACT8846_REG4_CTRL 0x42 +#define ACT8846_REG5_VSET 0x50 +#define ACT8846_REG5_CTRL 0x51 +#define ACT8846_REG6_VSET 0x58 +#define ACT8846_REG6_CTRL 0x59 +#define ACT8846_REG7_VSET 0x60 +#define ACT8846_REG7_CTRL 0x61 +#define ACT8846_REG8_VSET 0x68 +#define ACT8846_REG8_CTRL 0x69 +#define ACT8846_REG9_VSET 0x70 +#define ACT8846_REG9_CTRL 0x71 +#define ACT8846_REG10_VSET 0x80 +#define ACT8846_REG10_CTRL 0x81 +#define ACT8846_REG11_VSET 0x90 +#define ACT8846_REG11_CTRL 0x91 +#define ACT8846_REG12_VSET 0xa0 +#define ACT8846_REG12_CTRL 0xa1 +#define ACT8846_REG13_CTRL 0xb1 +#define ACT8846_GLB_OFF_CTRL 0xc3 +#define ACT8846_OFF_SYSMASK 0x18 + +/* + * ACT8865 Global Register Map. + */ +#define ACT8865_SYS_MODE 0x00 +#define ACT8865_SYS_CTRL 0x01 +#define ACT8865_DCDC1_VSET1 0x20 +#define ACT8865_DCDC1_VSET2 0x21 +#define ACT8865_DCDC1_CTRL 0x22 +#define ACT8865_DCDC2_VSET1 0x30 +#define ACT8865_DCDC2_VSET2 0x31 +#define ACT8865_DCDC2_CTRL 0x32 +#define ACT8865_DCDC3_VSET1 0x40 +#define ACT8865_DCDC3_VSET2 0x41 +#define ACT8865_DCDC3_CTRL 0x42 +#define ACT8865_LDO1_VSET 0x50 +#define ACT8865_LDO1_CTRL 0x51 +#define ACT8865_LDO2_VSET 0x54 +#define ACT8865_LDO2_CTRL 0x55 +#define ACT8865_LDO3_VSET 0x60 +#define ACT8865_LDO3_CTRL 0x61 +#define ACT8865_LDO4_VSET 0x64 +#define ACT8865_LDO4_CTRL 0x65 +#define ACT8865_MSTROFF 0x20 + +/* + * Field Definitions. + */ +#define ACT8865_ENA 0x80 /* ON - [7] */ +#define ACT8865_VSEL_MASK 0x3F /* VSET - [5:0] */ + + +#define ACT8600_LDO10_ENA 0x40 /* ON - [6] */ +#define ACT8600_SUDCDC_VSEL_MASK 0xFF /* SUDCDC VSET - [7:0] */ + +/* + * ACT8865 voltage number + */ +#define ACT8865_VOLTAGE_NUM 64 +#define ACT8600_SUDCDC_VOLTAGE_NUM 256 + +struct act8865 { + struct regmap *regmap; + int off_reg; + int off_mask; +}; + +static const struct regmap_range act8600_reg_ranges[] = { + regmap_reg_range(0x00, 0x01), + regmap_reg_range(0x10, 0x10), + regmap_reg_range(0x12, 0x12), + regmap_reg_range(0x20, 0x20), + regmap_reg_range(0x22, 0x22), + regmap_reg_range(0x30, 0x30), + regmap_reg_range(0x32, 0x32), + regmap_reg_range(0x40, 0x41), + regmap_reg_range(0x50, 0x51), + regmap_reg_range(0x60, 0x61), + regmap_reg_range(0x70, 0x71), + regmap_reg_range(0x80, 0x81), + regmap_reg_range(0x91, 0x91), + regmap_reg_range(0xA1, 0xA1), + regmap_reg_range(0xA8, 0xAA), + regmap_reg_range(0xB0, 0xB0), + regmap_reg_range(0xB2, 0xB2), + regmap_reg_range(0xC1, 0xC1), +}; + +static const struct regmap_range act8600_reg_ro_ranges[] = { + regmap_reg_range(0xAA, 0xAA), + regmap_reg_range(0xC1, 0xC1), +}; + +static const struct regmap_range act8600_reg_volatile_ranges[] = { + regmap_reg_range(0x00, 0x01), + regmap_reg_range(0x12, 0x12), + regmap_reg_range(0x22, 0x22), + regmap_reg_range(0x32, 0x32), + regmap_reg_range(0x41, 0x41), + regmap_reg_range(0x51, 0x51), + regmap_reg_range(0x61, 0x61), + regmap_reg_range(0x71, 0x71), + regmap_reg_range(0x81, 0x81), + regmap_reg_range(0xA8, 0xA8), + regmap_reg_range(0xAA, 0xAA), + regmap_reg_range(0xB0, 0xB0), + regmap_reg_range(0xC1, 0xC1), +}; + +static const struct regmap_access_table act8600_write_ranges_table = { + .yes_ranges = act8600_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(act8600_reg_ranges), + .no_ranges = act8600_reg_ro_ranges, + .n_no_ranges = ARRAY_SIZE(act8600_reg_ro_ranges), +}; + +static const struct regmap_access_table act8600_read_ranges_table = { + .yes_ranges = act8600_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(act8600_reg_ranges), +}; + +static const struct regmap_access_table act8600_volatile_ranges_table = { + .yes_ranges = act8600_reg_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(act8600_reg_volatile_ranges), +}; + +static const struct regmap_config act8600_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xFF, + .wr_table = &act8600_write_ranges_table, + .rd_table = &act8600_read_ranges_table, + .volatile_table = &act8600_volatile_ranges_table, +}; + +static const struct regmap_config act8865_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct regulator_linear_range act8865_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 23, 25000), + REGULATOR_LINEAR_RANGE(1200000, 24, 47, 50000), + REGULATOR_LINEAR_RANGE(2400000, 48, 63, 100000), +}; + +static const struct regulator_linear_range act8600_sudcdc_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(3000000, 0, 63, 0), + REGULATOR_LINEAR_RANGE(3000000, 64, 159, 100000), + REGULATOR_LINEAR_RANGE(12600000, 160, 191, 200000), + REGULATOR_LINEAR_RANGE(19000000, 192, 247, 400000), + REGULATOR_LINEAR_RANGE(41400000, 248, 255, 0), +}; + +static struct regulator_ops act8865_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static struct regulator_ops act8865_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +#define ACT88xx_REG(_name, _family, _id, _vsel_reg, _supply) \ + [_family##_ID_##_id] = { \ + .name = _name, \ + .supply_name = _supply, \ + .id = _family##_ID_##_id, \ + .type = REGULATOR_VOLTAGE, \ + .ops = &act8865_ops, \ + .n_voltages = ACT8865_VOLTAGE_NUM, \ + .linear_ranges = act8865_voltage_ranges, \ + .n_linear_ranges = ARRAY_SIZE(act8865_voltage_ranges), \ + .vsel_reg = _family##_##_id##_##_vsel_reg, \ + .vsel_mask = ACT8865_VSEL_MASK, \ + .enable_reg = _family##_##_id##_CTRL, \ + .enable_mask = ACT8865_ENA, \ + .owner = THIS_MODULE, \ + } + +static const struct regulator_desc act8600_regulators[] = { + ACT88xx_REG("DCDC1", ACT8600, DCDC1, VSET, "vp1"), + ACT88xx_REG("DCDC2", ACT8600, DCDC2, VSET, "vp2"), + ACT88xx_REG("DCDC3", ACT8600, DCDC3, VSET, "vp3"), + { + .name = "SUDCDC_REG4", + .id = ACT8600_ID_SUDCDC4, + .ops = &act8865_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = ACT8600_SUDCDC_VOLTAGE_NUM, + .linear_ranges = act8600_sudcdc_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(act8600_sudcdc_voltage_ranges), + .vsel_reg = ACT8600_SUDCDC4_VSET, + .vsel_mask = ACT8600_SUDCDC_VSEL_MASK, + .enable_reg = ACT8600_SUDCDC4_CTRL, + .enable_mask = ACT8865_ENA, + .owner = THIS_MODULE, + }, + ACT88xx_REG("LDO5", ACT8600, LDO5, VSET, "inl"), + ACT88xx_REG("LDO6", ACT8600, LDO6, VSET, "inl"), + ACT88xx_REG("LDO7", ACT8600, LDO7, VSET, "inl"), + ACT88xx_REG("LDO8", ACT8600, LDO8, VSET, "inl"), + { + .name = "LDO_REG9", + .id = ACT8600_ID_LDO9, + .ops = &act8865_ldo_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = 1, + .fixed_uV = 3300000, + .enable_reg = ACT8600_LDO910_CTRL, + .enable_mask = ACT8865_ENA, + .owner = THIS_MODULE, + }, + { + .name = "LDO_REG10", + .id = ACT8600_ID_LDO10, + .ops = &act8865_ldo_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = 1, + .fixed_uV = 1200000, + .enable_reg = ACT8600_LDO910_CTRL, + .enable_mask = ACT8600_LDO10_ENA, + .owner = THIS_MODULE, + }, +}; + +static const struct regulator_desc act8846_regulators[] = { + ACT88xx_REG("REG1", ACT8846, REG1, VSET, "vp1"), + ACT88xx_REG("REG2", ACT8846, REG2, VSET0, "vp2"), + ACT88xx_REG("REG3", ACT8846, REG3, VSET0, "vp3"), + ACT88xx_REG("REG4", ACT8846, REG4, VSET0, "vp4"), + ACT88xx_REG("REG5", ACT8846, REG5, VSET, "inl1"), + ACT88xx_REG("REG6", ACT8846, REG6, VSET, "inl1"), + ACT88xx_REG("REG7", ACT8846, REG7, VSET, "inl1"), + ACT88xx_REG("REG8", ACT8846, REG8, VSET, "inl2"), + ACT88xx_REG("REG9", ACT8846, REG9, VSET, "inl2"), + ACT88xx_REG("REG10", ACT8846, REG10, VSET, "inl3"), + ACT88xx_REG("REG11", ACT8846, REG11, VSET, "inl3"), + ACT88xx_REG("REG12", ACT8846, REG12, VSET, "inl3"), +}; + +static const struct regulator_desc act8865_regulators[] = { + ACT88xx_REG("DCDC_REG1", ACT8865, DCDC1, VSET1, "vp1"), + ACT88xx_REG("DCDC_REG2", ACT8865, DCDC2, VSET1, "vp2"), + ACT88xx_REG("DCDC_REG3", ACT8865, DCDC3, VSET1, "vp3"), + ACT88xx_REG("LDO_REG1", ACT8865, LDO1, VSET, "inl45"), + ACT88xx_REG("LDO_REG2", ACT8865, LDO2, VSET, "inl45"), + ACT88xx_REG("LDO_REG3", ACT8865, LDO3, VSET, "inl67"), + ACT88xx_REG("LDO_REG4", ACT8865, LDO4, VSET, "inl67"), +}; + +static const struct regulator_desc act8865_alt_regulators[] = { + ACT88xx_REG("DCDC_REG1", ACT8865, DCDC1, VSET2, "vp1"), + ACT88xx_REG("DCDC_REG2", ACT8865, DCDC2, VSET2, "vp2"), + ACT88xx_REG("DCDC_REG3", ACT8865, DCDC3, VSET2, "vp3"), + ACT88xx_REG("LDO_REG1", ACT8865, LDO1, VSET, "inl45"), + ACT88xx_REG("LDO_REG2", ACT8865, LDO2, VSET, "inl45"), + ACT88xx_REG("LDO_REG3", ACT8865, LDO3, VSET, "inl67"), + ACT88xx_REG("LDO_REG4", ACT8865, LDO4, VSET, "inl67"), +}; + +#ifdef CONFIG_OF +static const struct of_device_id act8865_dt_ids[] = { + { .compatible = "active-semi,act8600", .data = (void *)ACT8600 }, + { .compatible = "active-semi,act8846", .data = (void *)ACT8846 }, + { .compatible = "active-semi,act8865", .data = (void *)ACT8865 }, + { } +}; +MODULE_DEVICE_TABLE(of, act8865_dt_ids); + +static struct of_regulator_match act8846_matches[] = { + [ACT8846_ID_REG1] = { .name = "REG1" }, + [ACT8846_ID_REG2] = { .name = "REG2" }, + [ACT8846_ID_REG3] = { .name = "REG3" }, + [ACT8846_ID_REG4] = { .name = "REG4" }, + [ACT8846_ID_REG5] = { .name = "REG5" }, + [ACT8846_ID_REG6] = { .name = "REG6" }, + [ACT8846_ID_REG7] = { .name = "REG7" }, + [ACT8846_ID_REG8] = { .name = "REG8" }, + [ACT8846_ID_REG9] = { .name = "REG9" }, + [ACT8846_ID_REG10] = { .name = "REG10" }, + [ACT8846_ID_REG11] = { .name = "REG11" }, + [ACT8846_ID_REG12] = { .name = "REG12" }, +}; + +static struct of_regulator_match act8865_matches[] = { + [ACT8865_ID_DCDC1] = { .name = "DCDC_REG1"}, + [ACT8865_ID_DCDC2] = { .name = "DCDC_REG2"}, + [ACT8865_ID_DCDC3] = { .name = "DCDC_REG3"}, + [ACT8865_ID_LDO1] = { .name = "LDO_REG1"}, + [ACT8865_ID_LDO2] = { .name = "LDO_REG2"}, + [ACT8865_ID_LDO3] = { .name = "LDO_REG3"}, + [ACT8865_ID_LDO4] = { .name = "LDO_REG4"}, +}; + +static struct of_regulator_match act8600_matches[] = { + [ACT8600_ID_DCDC1] = { .name = "DCDC_REG1"}, + [ACT8600_ID_DCDC2] = { .name = "DCDC_REG2"}, + [ACT8600_ID_DCDC3] = { .name = "DCDC_REG3"}, + [ACT8600_ID_SUDCDC4] = { .name = "SUDCDC_REG4"}, + [ACT8600_ID_LDO5] = { .name = "LDO_REG5"}, + [ACT8600_ID_LDO6] = { .name = "LDO_REG6"}, + [ACT8600_ID_LDO7] = { .name = "LDO_REG7"}, + [ACT8600_ID_LDO8] = { .name = "LDO_REG8"}, + [ACT8600_ID_LDO9] = { .name = "LDO_REG9"}, + [ACT8600_ID_LDO10] = { .name = "LDO_REG10"}, +}; + +static int act8865_pdata_from_dt(struct device *dev, + struct act8865_platform_data *pdata, + unsigned long type) +{ + int matched, i, num_matches; + struct device_node *np; + struct act8865_regulator_data *regulator; + struct of_regulator_match *matches; + + switch (type) { + case ACT8600: + matches = act8600_matches; + num_matches = ARRAY_SIZE(act8600_matches); + break; + case ACT8846: + matches = act8846_matches; + num_matches = ARRAY_SIZE(act8846_matches); + break; + case ACT8865: + matches = act8865_matches; + num_matches = ARRAY_SIZE(act8865_matches); + break; + default: + dev_err(dev, "invalid device id %lu\n", type); + return -EINVAL; + } + + np = of_get_child_by_name(dev->of_node, "regulators"); + if (!np) { + dev_err(dev, "missing 'regulators' subnode in DT\n"); + return -EINVAL; + } + + matched = of_regulator_match(dev, np, matches, num_matches); + of_node_put(np); + if (matched <= 0) + return matched; + + pdata->regulators = devm_kcalloc(dev, + num_matches, + sizeof(struct act8865_regulator_data), + GFP_KERNEL); + if (!pdata->regulators) + return -ENOMEM; + + pdata->num_regulators = num_matches; + regulator = pdata->regulators; + + for (i = 0; i < num_matches; i++) { + regulator->id = i; + regulator->name = matches[i].name; + regulator->init_data = matches[i].init_data; + regulator->of_node = matches[i].of_node; + regulator++; + } + + return 0; +} +#else +static inline int act8865_pdata_from_dt(struct device *dev, + struct act8865_platform_data *pdata, + unsigned long type) +{ + return 0; +} +#endif + +static struct act8865_regulator_data *act8865_get_regulator_data( + int id, struct act8865_platform_data *pdata) +{ + int i; + + if (!pdata) + return NULL; + + for (i = 0; i < pdata->num_regulators; i++) { + if (pdata->regulators[i].id == id) + return &pdata->regulators[i]; + } + + return NULL; +} + +static struct i2c_client *act8865_i2c_client; +static void act8865_power_off(void) +{ + struct act8865 *act8865; + + act8865 = i2c_get_clientdata(act8865_i2c_client); + regmap_write(act8865->regmap, act8865->off_reg, act8865->off_mask); + while (1); +} + +static int act8865_pmic_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + const struct regulator_desc *regulators; + struct act8865_platform_data pdata_of, *pdata; + struct device *dev = &client->dev; + int i, ret, num_regulators; + struct act8865 *act8865; + const struct regmap_config *regmap_config; + unsigned long type; + int off_reg, off_mask; + int voltage_select = 0; + + pdata = dev_get_platdata(dev); + + if (dev->of_node && !pdata) { + const struct of_device_id *id; + + id = of_match_device(of_match_ptr(act8865_dt_ids), dev); + if (!id) + return -ENODEV; + + type = (unsigned long) id->data; + + voltage_select = !!of_get_property(dev->of_node, + "active-semi,vsel-high", + NULL); + } else { + type = i2c_id->driver_data; + } + + switch (type) { + case ACT8600: + regulators = act8600_regulators; + num_regulators = ARRAY_SIZE(act8600_regulators); + regmap_config = &act8600_regmap_config; + off_reg = -1; + off_mask = -1; + break; + case ACT8846: + regulators = act8846_regulators; + num_regulators = ARRAY_SIZE(act8846_regulators); + regmap_config = &act8865_regmap_config; + off_reg = ACT8846_GLB_OFF_CTRL; + off_mask = ACT8846_OFF_SYSMASK; + break; + case ACT8865: + if (voltage_select) { + regulators = act8865_alt_regulators; + num_regulators = ARRAY_SIZE(act8865_alt_regulators); + } else { + regulators = act8865_regulators; + num_regulators = ARRAY_SIZE(act8865_regulators); + } + regmap_config = &act8865_regmap_config; + off_reg = ACT8865_SYS_CTRL; + off_mask = ACT8865_MSTROFF; + break; + default: + dev_err(dev, "invalid device id %lu\n", type); + return -EINVAL; + } + + if (dev->of_node && !pdata) { + ret = act8865_pdata_from_dt(dev, &pdata_of, type); + if (ret < 0) + return ret; + + pdata = &pdata_of; + } + + act8865 = devm_kzalloc(dev, sizeof(struct act8865), GFP_KERNEL); + if (!act8865) + return -ENOMEM; + + act8865->regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(act8865->regmap)) { + ret = PTR_ERR(act8865->regmap); + dev_err(dev, "Failed to allocate register map: %d\n", ret); + return ret; + } + + if (of_device_is_system_power_controller(dev->of_node)) { + if (!pm_power_off && (off_reg > 0)) { + act8865_i2c_client = client; + act8865->off_reg = off_reg; + act8865->off_mask = off_mask; + pm_power_off = act8865_power_off; + } else { + dev_err(dev, "Failed to set poweroff capability, already defined\n"); + } + } + + /* Finally register devices */ + for (i = 0; i < num_regulators; i++) { + const struct regulator_desc *desc = ®ulators[i]; + struct regulator_config config = { }; + struct act8865_regulator_data *rdata; + struct regulator_dev *rdev; + + config.dev = dev; + config.driver_data = act8865; + config.regmap = act8865->regmap; + + rdata = act8865_get_regulator_data(desc->id, pdata); + if (rdata) { + config.init_data = rdata->init_data; + config.of_node = rdata->of_node; + } + + rdev = devm_regulator_register(dev, desc, &config); + if (IS_ERR(rdev)) { + dev_err(dev, "failed to register %s\n", desc->name); + return PTR_ERR(rdev); + } + } + + i2c_set_clientdata(client, act8865); + + return 0; +} + +static const struct i2c_device_id act8865_ids[] = { + { .name = "act8600", .driver_data = ACT8600 }, + { .name = "act8846", .driver_data = ACT8846 }, + { .name = "act8865", .driver_data = ACT8865 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, act8865_ids); + +static struct i2c_driver act8865_pmic_driver = { + .driver = { + .name = "act8865", + }, + .probe = act8865_pmic_probe, + .id_table = act8865_ids, +}; + +module_i2c_driver(act8865_pmic_driver); + +MODULE_DESCRIPTION("active-semi act88xx voltage regulator driver"); +MODULE_AUTHOR("Wenyou Yang <wenyou.yang@atmel.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/act8945a-regulator.c b/drivers/regulator/act8945a-regulator.c new file mode 100644 index 000000000..43fda8b44 --- /dev/null +++ b/drivers/regulator/act8945a-regulator.c @@ -0,0 +1,165 @@ +/* + * Voltage regulation driver for active-semi ACT8945A PMIC + * + * Copyright (C) 2015 Atmel Corporation + * + * Author: Wenyou Yang <wenyou.yang@atmel.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; either version 2 of the + * License, or (at your option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> + +/** + * ACT8945A Global Register Map. + */ +#define ACT8945A_SYS_MODE 0x00 +#define ACT8945A_SYS_CTRL 0x01 +#define ACT8945A_DCDC1_VSET1 0x20 +#define ACT8945A_DCDC1_VSET2 0x21 +#define ACT8945A_DCDC1_CTRL 0x22 +#define ACT8945A_DCDC2_VSET1 0x30 +#define ACT8945A_DCDC2_VSET2 0x31 +#define ACT8945A_DCDC2_CTRL 0x32 +#define ACT8945A_DCDC3_VSET1 0x40 +#define ACT8945A_DCDC3_VSET2 0x41 +#define ACT8945A_DCDC3_CTRL 0x42 +#define ACT8945A_LDO1_VSET 0x50 +#define ACT8945A_LDO1_CTRL 0x51 +#define ACT8945A_LDO2_VSET 0x54 +#define ACT8945A_LDO2_CTRL 0x55 +#define ACT8945A_LDO3_VSET 0x60 +#define ACT8945A_LDO3_CTRL 0x61 +#define ACT8945A_LDO4_VSET 0x64 +#define ACT8945A_LDO4_CTRL 0x65 + +/** + * Field Definitions. + */ +#define ACT8945A_ENA 0x80 /* ON - [7] */ +#define ACT8945A_VSEL_MASK 0x3F /* VSET - [5:0] */ + +/** + * ACT8945A Voltage Number + */ +#define ACT8945A_VOLTAGE_NUM 64 + +enum { + ACT8945A_ID_DCDC1, + ACT8945A_ID_DCDC2, + ACT8945A_ID_DCDC3, + ACT8945A_ID_LDO1, + ACT8945A_ID_LDO2, + ACT8945A_ID_LDO3, + ACT8945A_ID_LDO4, + ACT8945A_REG_NUM, +}; + +static const struct regulator_linear_range act8945a_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 23, 25000), + REGULATOR_LINEAR_RANGE(1200000, 24, 47, 50000), + REGULATOR_LINEAR_RANGE(2400000, 48, 63, 100000), +}; + +static const struct regulator_ops act8945a_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +#define ACT89xx_REG(_name, _family, _id, _vsel_reg, _supply) \ + [_family##_ID_##_id] = { \ + .name = _name, \ + .supply_name = _supply, \ + .of_match = of_match_ptr("REG_"#_id), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = _family##_ID_##_id, \ + .type = REGULATOR_VOLTAGE, \ + .ops = &act8945a_ops, \ + .n_voltages = ACT8945A_VOLTAGE_NUM, \ + .linear_ranges = act8945a_voltage_ranges, \ + .n_linear_ranges = ARRAY_SIZE(act8945a_voltage_ranges), \ + .vsel_reg = _family##_##_id##_##_vsel_reg, \ + .vsel_mask = ACT8945A_VSEL_MASK, \ + .enable_reg = _family##_##_id##_CTRL, \ + .enable_mask = ACT8945A_ENA, \ + .owner = THIS_MODULE, \ + } + +static const struct regulator_desc act8945a_regulators[] = { + ACT89xx_REG("DCDC_REG1", ACT8945A, DCDC1, VSET1, "vp1"), + ACT89xx_REG("DCDC_REG2", ACT8945A, DCDC2, VSET1, "vp2"), + ACT89xx_REG("DCDC_REG3", ACT8945A, DCDC3, VSET1, "vp3"), + ACT89xx_REG("LDO_REG1", ACT8945A, LDO1, VSET, "inl45"), + ACT89xx_REG("LDO_REG2", ACT8945A, LDO2, VSET, "inl45"), + ACT89xx_REG("LDO_REG3", ACT8945A, LDO3, VSET, "inl67"), + ACT89xx_REG("LDO_REG4", ACT8945A, LDO4, VSET, "inl67"), +}; + +static const struct regulator_desc act8945a_alt_regulators[] = { + ACT89xx_REG("DCDC_REG1", ACT8945A, DCDC1, VSET2, "vp1"), + ACT89xx_REG("DCDC_REG2", ACT8945A, DCDC2, VSET2, "vp2"), + ACT89xx_REG("DCDC_REG3", ACT8945A, DCDC3, VSET2, "vp3"), + ACT89xx_REG("LDO_REG1", ACT8945A, LDO1, VSET, "inl45"), + ACT89xx_REG("LDO_REG2", ACT8945A, LDO2, VSET, "inl45"), + ACT89xx_REG("LDO_REG3", ACT8945A, LDO3, VSET, "inl67"), + ACT89xx_REG("LDO_REG4", ACT8945A, LDO4, VSET, "inl67"), +}; + +static int act8945a_pmic_probe(struct platform_device *pdev) +{ + struct regulator_config config = { }; + const struct regulator_desc *regulators; + struct regulator_dev *rdev; + int i, num_regulators; + bool voltage_select; + + voltage_select = of_property_read_bool(pdev->dev.parent->of_node, + "active-semi,vsel-high"); + + if (voltage_select) { + regulators = act8945a_alt_regulators; + num_regulators = ARRAY_SIZE(act8945a_alt_regulators); + } else { + regulators = act8945a_regulators; + num_regulators = ARRAY_SIZE(act8945a_regulators); + } + + config.dev = &pdev->dev; + config.dev->of_node = pdev->dev.parent->of_node; + for (i = 0; i < num_regulators; i++) { + rdev = devm_regulator_register(&pdev->dev, ®ulators[i], &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, + "failed to register %s regulator\n", + regulators[i].name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static struct platform_driver act8945a_pmic_driver = { + .driver = { + .name = "act8945a-regulator", + }, + .probe = act8945a_pmic_probe, +}; +module_platform_driver(act8945a_pmic_driver); + +MODULE_DESCRIPTION("Active-semi ACT8945A voltage regulator driver"); +MODULE_AUTHOR("Wenyou Yang <wenyou.yang@atmel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/ad5398.c b/drivers/regulator/ad5398.c new file mode 100644 index 000000000..11c1f880b --- /dev/null +++ b/drivers/regulator/ad5398.c @@ -0,0 +1,279 @@ +/* + * Voltage and current regulation for AD5398 and AD5821 + * + * Copyright 2010 Analog Devices Inc. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> + +#define AD5398_CURRENT_EN_MASK 0x8000 + +struct ad5398_chip_info { + struct i2c_client *client; + int min_uA; + int max_uA; + unsigned int current_level; + unsigned int current_mask; + unsigned int current_offset; + struct regulator_dev *rdev; +}; + +static int ad5398_calc_current(struct ad5398_chip_info *chip, + unsigned selector) +{ + unsigned range_uA = chip->max_uA - chip->min_uA; + + return chip->min_uA + (selector * range_uA / chip->current_level); +} + +static int ad5398_read_reg(struct i2c_client *client, unsigned short *data) +{ + unsigned short val; + int ret; + + ret = i2c_master_recv(client, (char *)&val, 2); + if (ret < 0) { + dev_err(&client->dev, "I2C read error\n"); + return ret; + } + *data = be16_to_cpu(val); + + return ret; +} + +static int ad5398_write_reg(struct i2c_client *client, const unsigned short data) +{ + unsigned short val; + int ret; + + val = cpu_to_be16(data); + ret = i2c_master_send(client, (char *)&val, 2); + if (ret != 2) { + dev_err(&client->dev, "I2C write error\n"); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +static int ad5398_get_current_limit(struct regulator_dev *rdev) +{ + struct ad5398_chip_info *chip = rdev_get_drvdata(rdev); + struct i2c_client *client = chip->client; + unsigned short data; + int ret; + + ret = ad5398_read_reg(client, &data); + if (ret < 0) + return ret; + + ret = (data & chip->current_mask) >> chip->current_offset; + + return ad5398_calc_current(chip, ret); +} + +static int ad5398_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA) +{ + struct ad5398_chip_info *chip = rdev_get_drvdata(rdev); + struct i2c_client *client = chip->client; + unsigned range_uA = chip->max_uA - chip->min_uA; + unsigned selector; + unsigned short data; + int ret; + + if (min_uA < chip->min_uA) + min_uA = chip->min_uA; + if (max_uA > chip->max_uA) + max_uA = chip->max_uA; + + if (min_uA > chip->max_uA || max_uA < chip->min_uA) + return -EINVAL; + + selector = DIV_ROUND_UP((min_uA - chip->min_uA) * chip->current_level, + range_uA); + if (ad5398_calc_current(chip, selector) > max_uA) + return -EINVAL; + + dev_dbg(&client->dev, "changing current %duA\n", + ad5398_calc_current(chip, selector)); + + /* read chip enable bit */ + ret = ad5398_read_reg(client, &data); + if (ret < 0) + return ret; + + /* prepare register data */ + selector = (selector << chip->current_offset) & chip->current_mask; + data = (unsigned short)selector | (data & AD5398_CURRENT_EN_MASK); + + /* write the new current value back as well as enable bit */ + ret = ad5398_write_reg(client, data); + + return ret; +} + +static int ad5398_is_enabled(struct regulator_dev *rdev) +{ + struct ad5398_chip_info *chip = rdev_get_drvdata(rdev); + struct i2c_client *client = chip->client; + unsigned short data; + int ret; + + ret = ad5398_read_reg(client, &data); + if (ret < 0) + return ret; + + if (data & AD5398_CURRENT_EN_MASK) + return 1; + else + return 0; +} + +static int ad5398_enable(struct regulator_dev *rdev) +{ + struct ad5398_chip_info *chip = rdev_get_drvdata(rdev); + struct i2c_client *client = chip->client; + unsigned short data; + int ret; + + ret = ad5398_read_reg(client, &data); + if (ret < 0) + return ret; + + if (data & AD5398_CURRENT_EN_MASK) + return 0; + + data |= AD5398_CURRENT_EN_MASK; + + ret = ad5398_write_reg(client, data); + + return ret; +} + +static int ad5398_disable(struct regulator_dev *rdev) +{ + struct ad5398_chip_info *chip = rdev_get_drvdata(rdev); + struct i2c_client *client = chip->client; + unsigned short data; + int ret; + + ret = ad5398_read_reg(client, &data); + if (ret < 0) + return ret; + + if (!(data & AD5398_CURRENT_EN_MASK)) + return 0; + + data &= ~AD5398_CURRENT_EN_MASK; + + ret = ad5398_write_reg(client, data); + + return ret; +} + +static const struct regulator_ops ad5398_ops = { + .get_current_limit = ad5398_get_current_limit, + .set_current_limit = ad5398_set_current_limit, + .enable = ad5398_enable, + .disable = ad5398_disable, + .is_enabled = ad5398_is_enabled, +}; + +static const struct regulator_desc ad5398_reg = { + .name = "isink", + .id = 0, + .ops = &ad5398_ops, + .type = REGULATOR_CURRENT, + .owner = THIS_MODULE, +}; + +struct ad5398_current_data_format { + int current_bits; + int current_offset; + int min_uA; + int max_uA; +}; + +static const struct ad5398_current_data_format df_10_4_120 = {10, 4, 0, 120000}; + +static const struct i2c_device_id ad5398_id[] = { + { "ad5398", (kernel_ulong_t)&df_10_4_120 }, + { "ad5821", (kernel_ulong_t)&df_10_4_120 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5398_id); + +static int ad5398_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regulator_init_data *init_data = dev_get_platdata(&client->dev); + struct regulator_config config = { }; + struct ad5398_chip_info *chip; + const struct ad5398_current_data_format *df = + (struct ad5398_current_data_format *)id->driver_data; + + if (!init_data) + return -EINVAL; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + config.dev = &client->dev; + config.init_data = init_data; + config.driver_data = chip; + + chip->client = client; + + chip->min_uA = df->min_uA; + chip->max_uA = df->max_uA; + chip->current_level = 1 << df->current_bits; + chip->current_offset = df->current_offset; + chip->current_mask = (chip->current_level - 1) << chip->current_offset; + + chip->rdev = devm_regulator_register(&client->dev, &ad5398_reg, + &config); + if (IS_ERR(chip->rdev)) { + dev_err(&client->dev, "failed to register %s %s\n", + id->name, ad5398_reg.name); + return PTR_ERR(chip->rdev); + } + + i2c_set_clientdata(client, chip); + dev_dbg(&client->dev, "%s regulator driver is registered.\n", id->name); + return 0; +} + +static struct i2c_driver ad5398_driver = { + .probe = ad5398_probe, + .driver = { + .name = "ad5398", + }, + .id_table = ad5398_id, +}; + +static int __init ad5398_init(void) +{ + return i2c_add_driver(&ad5398_driver); +} +subsys_initcall(ad5398_init); + +static void __exit ad5398_exit(void) +{ + i2c_del_driver(&ad5398_driver); +} +module_exit(ad5398_exit); + +MODULE_DESCRIPTION("AD5398 and AD5821 current regulator driver"); +MODULE_AUTHOR("Sonic Zhang"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/anatop-regulator.c b/drivers/regulator/anatop-regulator.c new file mode 100644 index 000000000..d9d8155ed --- /dev/null +++ b/drivers/regulator/anatop-regulator.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/mfd/syscon.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/machine.h> + +#define LDO_RAMP_UP_UNIT_IN_CYCLES 64 /* 64 cycles per step */ +#define LDO_RAMP_UP_FREQ_IN_MHZ 24 /* cycle based on 24M OSC */ + +#define LDO_POWER_GATE 0x00 +#define LDO_FET_FULL_ON 0x1f + +struct anatop_regulator { + u32 control_reg; + struct regmap *anatop; + int vol_bit_shift; + int vol_bit_width; + u32 delay_reg; + int delay_bit_shift; + int delay_bit_width; + int min_bit_val; + int min_voltage; + int max_voltage; + struct regulator_desc rdesc; + struct regulator_init_data *initdata; + bool bypass; + int sel; +}; + +static int anatop_regmap_set_voltage_time_sel(struct regulator_dev *reg, + unsigned int old_sel, + unsigned int new_sel) +{ + struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg); + u32 val; + int ret = 0; + + /* check whether need to care about LDO ramp up speed */ + if (anatop_reg->delay_bit_width && new_sel > old_sel) { + /* + * the delay for LDO ramp up time is + * based on the register setting, we need + * to calculate how many steps LDO need to + * ramp up, and how much delay needed. (us) + */ + regmap_read(anatop_reg->anatop, anatop_reg->delay_reg, &val); + val = (val >> anatop_reg->delay_bit_shift) & + ((1 << anatop_reg->delay_bit_width) - 1); + ret = (new_sel - old_sel) * (LDO_RAMP_UP_UNIT_IN_CYCLES << + val) / LDO_RAMP_UP_FREQ_IN_MHZ + 1; + } + + return ret; +} + +static int anatop_regmap_enable(struct regulator_dev *reg) +{ + struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg); + int sel; + + sel = anatop_reg->bypass ? LDO_FET_FULL_ON : anatop_reg->sel; + return regulator_set_voltage_sel_regmap(reg, sel); +} + +static int anatop_regmap_disable(struct regulator_dev *reg) +{ + return regulator_set_voltage_sel_regmap(reg, LDO_POWER_GATE); +} + +static int anatop_regmap_is_enabled(struct regulator_dev *reg) +{ + return regulator_get_voltage_sel_regmap(reg) != LDO_POWER_GATE; +} + +static int anatop_regmap_core_set_voltage_sel(struct regulator_dev *reg, + unsigned selector) +{ + struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg); + int ret; + + if (anatop_reg->bypass || !anatop_regmap_is_enabled(reg)) { + anatop_reg->sel = selector; + return 0; + } + + ret = regulator_set_voltage_sel_regmap(reg, selector); + if (!ret) + anatop_reg->sel = selector; + return ret; +} + +static int anatop_regmap_core_get_voltage_sel(struct regulator_dev *reg) +{ + struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg); + + if (anatop_reg->bypass || !anatop_regmap_is_enabled(reg)) + return anatop_reg->sel; + + return regulator_get_voltage_sel_regmap(reg); +} + +static int anatop_regmap_get_bypass(struct regulator_dev *reg, bool *enable) +{ + struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg); + int sel; + + sel = regulator_get_voltage_sel_regmap(reg); + if (sel == LDO_FET_FULL_ON) + WARN_ON(!anatop_reg->bypass); + else if (sel != LDO_POWER_GATE) + WARN_ON(anatop_reg->bypass); + + *enable = anatop_reg->bypass; + return 0; +} + +static int anatop_regmap_set_bypass(struct regulator_dev *reg, bool enable) +{ + struct anatop_regulator *anatop_reg = rdev_get_drvdata(reg); + int sel; + + if (enable == anatop_reg->bypass) + return 0; + + sel = enable ? LDO_FET_FULL_ON : anatop_reg->sel; + anatop_reg->bypass = enable; + + return regulator_set_voltage_sel_regmap(reg, sel); +} + +static struct regulator_ops anatop_rops = { + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, +}; + +static struct regulator_ops anatop_core_rops = { + .enable = anatop_regmap_enable, + .disable = anatop_regmap_disable, + .is_enabled = anatop_regmap_is_enabled, + .set_voltage_sel = anatop_regmap_core_set_voltage_sel, + .set_voltage_time_sel = anatop_regmap_set_voltage_time_sel, + .get_voltage_sel = anatop_regmap_core_get_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_bypass = anatop_regmap_get_bypass, + .set_bypass = anatop_regmap_set_bypass, +}; + +static int anatop_regulator_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *anatop_np; + struct regulator_desc *rdesc; + struct regulator_dev *rdev; + struct anatop_regulator *sreg; + struct regulator_init_data *initdata; + struct regulator_config config = { }; + int ret = 0; + u32 val; + + sreg = devm_kzalloc(dev, sizeof(*sreg), GFP_KERNEL); + if (!sreg) + return -ENOMEM; + + rdesc = &sreg->rdesc; + rdesc->type = REGULATOR_VOLTAGE; + rdesc->owner = THIS_MODULE; + + of_property_read_string(np, "regulator-name", &rdesc->name); + if (!rdesc->name) { + dev_err(dev, "failed to get a regulator-name\n"); + return -EINVAL; + } + + initdata = of_get_regulator_init_data(dev, np, rdesc); + if (!initdata) + return -ENOMEM; + + initdata->supply_regulator = "vin"; + sreg->initdata = initdata; + + anatop_np = of_get_parent(np); + if (!anatop_np) + return -ENODEV; + sreg->anatop = syscon_node_to_regmap(anatop_np); + of_node_put(anatop_np); + if (IS_ERR(sreg->anatop)) + return PTR_ERR(sreg->anatop); + + ret = of_property_read_u32(np, "anatop-reg-offset", + &sreg->control_reg); + if (ret) { + dev_err(dev, "no anatop-reg-offset property set\n"); + return ret; + } + ret = of_property_read_u32(np, "anatop-vol-bit-width", + &sreg->vol_bit_width); + if (ret) { + dev_err(dev, "no anatop-vol-bit-width property set\n"); + return ret; + } + ret = of_property_read_u32(np, "anatop-vol-bit-shift", + &sreg->vol_bit_shift); + if (ret) { + dev_err(dev, "no anatop-vol-bit-shift property set\n"); + return ret; + } + ret = of_property_read_u32(np, "anatop-min-bit-val", + &sreg->min_bit_val); + if (ret) { + dev_err(dev, "no anatop-min-bit-val property set\n"); + return ret; + } + ret = of_property_read_u32(np, "anatop-min-voltage", + &sreg->min_voltage); + if (ret) { + dev_err(dev, "no anatop-min-voltage property set\n"); + return ret; + } + ret = of_property_read_u32(np, "anatop-max-voltage", + &sreg->max_voltage); + if (ret) { + dev_err(dev, "no anatop-max-voltage property set\n"); + return ret; + } + + /* read LDO ramp up setting, only for core reg */ + of_property_read_u32(np, "anatop-delay-reg-offset", + &sreg->delay_reg); + of_property_read_u32(np, "anatop-delay-bit-width", + &sreg->delay_bit_width); + of_property_read_u32(np, "anatop-delay-bit-shift", + &sreg->delay_bit_shift); + + rdesc->n_voltages = (sreg->max_voltage - sreg->min_voltage) / 25000 + 1 + + sreg->min_bit_val; + rdesc->min_uV = sreg->min_voltage; + rdesc->uV_step = 25000; + rdesc->linear_min_sel = sreg->min_bit_val; + rdesc->vsel_reg = sreg->control_reg; + rdesc->vsel_mask = ((1 << sreg->vol_bit_width) - 1) << + sreg->vol_bit_shift; + rdesc->min_dropout_uV = 125000; + + config.dev = &pdev->dev; + config.init_data = initdata; + config.driver_data = sreg; + config.of_node = pdev->dev.of_node; + config.regmap = sreg->anatop; + + /* Only core regulators have the ramp up delay configuration. */ + if (sreg->control_reg && sreg->delay_bit_width) { + rdesc->ops = &anatop_core_rops; + + ret = regmap_read(config.regmap, rdesc->vsel_reg, &val); + if (ret) { + dev_err(dev, "failed to read initial state\n"); + return ret; + } + + sreg->sel = (val & rdesc->vsel_mask) >> sreg->vol_bit_shift; + if (sreg->sel == LDO_FET_FULL_ON) { + sreg->sel = 0; + sreg->bypass = true; + } + + /* + * In case vddpu was disabled by the bootloader, we need to set + * a sane default until imx6-cpufreq was probed and changes the + * voltage to the correct value. In this case we set 1.25V. + */ + if (!sreg->sel && !strcmp(rdesc->name, "vddpu")) + sreg->sel = 22; + + /* set the default voltage of the pcie phy to be 1.100v */ + if (!sreg->sel && !strcmp(rdesc->name, "vddpcie")) + sreg->sel = 0x10; + + if (!sreg->bypass && !sreg->sel) { + dev_err(&pdev->dev, "Failed to read a valid default voltage selector.\n"); + return -EINVAL; + } + } else { + u32 enable_bit; + + rdesc->ops = &anatop_rops; + + if (!of_property_read_u32(np, "anatop-enable-bit", + &enable_bit)) { + anatop_rops.enable = regulator_enable_regmap; + anatop_rops.disable = regulator_disable_regmap; + anatop_rops.is_enabled = regulator_is_enabled_regmap; + + rdesc->enable_reg = sreg->control_reg; + rdesc->enable_mask = BIT(enable_bit); + } + } + + /* register regulator */ + rdev = devm_regulator_register(dev, rdesc, &config); + if (IS_ERR(rdev)) { + dev_err(dev, "failed to register %s\n", + rdesc->name); + return PTR_ERR(rdev); + } + + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static const struct of_device_id of_anatop_regulator_match_tbl[] = { + { .compatible = "fsl,anatop-regulator", }, + { /* end */ } +}; +MODULE_DEVICE_TABLE(of, of_anatop_regulator_match_tbl); + +static struct platform_driver anatop_regulator_driver = { + .driver = { + .name = "anatop_regulator", + .of_match_table = of_anatop_regulator_match_tbl, + }, + .probe = anatop_regulator_probe, +}; + +static int __init anatop_regulator_init(void) +{ + return platform_driver_register(&anatop_regulator_driver); +} +postcore_initcall(anatop_regulator_init); + +static void __exit anatop_regulator_exit(void) +{ + platform_driver_unregister(&anatop_regulator_driver); +} +module_exit(anatop_regulator_exit); + +MODULE_AUTHOR("Nancy Chen <Nancy.Chen@freescale.com>"); +MODULE_AUTHOR("Ying-Chun Liu (PaulLiu) <paul.liu@linaro.org>"); +MODULE_DESCRIPTION("ANATOP Regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:anatop_regulator"); diff --git a/drivers/regulator/arizona-ldo1.c b/drivers/regulator/arizona-ldo1.c new file mode 100644 index 000000000..e976d073f --- /dev/null +++ b/drivers/regulator/arizona-ldo1.c @@ -0,0 +1,371 @@ +/* + * arizona-ldo1.c -- LDO1 supply for Arizona devices + * + * Copyright 2012 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/slab.h> + +#include <linux/regulator/arizona-ldo1.h> + +#include <linux/mfd/arizona/core.h> +#include <linux/mfd/arizona/pdata.h> +#include <linux/mfd/arizona/registers.h> + +struct arizona_ldo1 { + struct regulator_dev *regulator; + struct regmap *regmap; + + struct regulator_consumer_supply supply; + struct regulator_init_data init_data; + + struct gpio_desc *ena_gpiod; +}; + +static int arizona_ldo1_hc_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + if (selector >= rdev->desc->n_voltages) + return -EINVAL; + + if (selector == rdev->desc->n_voltages - 1) + return 1800000; + else + return rdev->desc->min_uV + (rdev->desc->uV_step * selector); +} + +static int arizona_ldo1_hc_map_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + int sel; + + sel = DIV_ROUND_UP(min_uV - rdev->desc->min_uV, rdev->desc->uV_step); + if (sel >= rdev->desc->n_voltages) + sel = rdev->desc->n_voltages - 1; + + return sel; +} + +static int arizona_ldo1_hc_set_voltage_sel(struct regulator_dev *rdev, + unsigned sel) +{ + struct arizona_ldo1 *ldo = rdev_get_drvdata(rdev); + struct regmap *regmap = ldo->regmap; + unsigned int val; + int ret; + + if (sel == rdev->desc->n_voltages - 1) + val = ARIZONA_LDO1_HI_PWR; + else + val = 0; + + ret = regmap_update_bits(regmap, ARIZONA_LDO1_CONTROL_2, + ARIZONA_LDO1_HI_PWR, val); + if (ret != 0) + return ret; + + if (val) + return 0; + + val = sel << ARIZONA_LDO1_VSEL_SHIFT; + + return regmap_update_bits(regmap, ARIZONA_LDO1_CONTROL_1, + ARIZONA_LDO1_VSEL_MASK, val); +} + +static int arizona_ldo1_hc_get_voltage_sel(struct regulator_dev *rdev) +{ + struct arizona_ldo1 *ldo = rdev_get_drvdata(rdev); + struct regmap *regmap = ldo->regmap; + unsigned int val; + int ret; + + ret = regmap_read(regmap, ARIZONA_LDO1_CONTROL_2, &val); + if (ret != 0) + return ret; + + if (val & ARIZONA_LDO1_HI_PWR) + return rdev->desc->n_voltages - 1; + + ret = regmap_read(regmap, ARIZONA_LDO1_CONTROL_1, &val); + if (ret != 0) + return ret; + + return (val & ARIZONA_LDO1_VSEL_MASK) >> ARIZONA_LDO1_VSEL_SHIFT; +} + +static const struct regulator_ops arizona_ldo1_hc_ops = { + .list_voltage = arizona_ldo1_hc_list_voltage, + .map_voltage = arizona_ldo1_hc_map_voltage, + .get_voltage_sel = arizona_ldo1_hc_get_voltage_sel, + .set_voltage_sel = arizona_ldo1_hc_set_voltage_sel, + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = regulator_set_bypass_regmap, +}; + +static const struct regulator_desc arizona_ldo1_hc = { + .name = "LDO1", + .supply_name = "LDOVDD", + .type = REGULATOR_VOLTAGE, + .ops = &arizona_ldo1_hc_ops, + + .bypass_reg = ARIZONA_LDO1_CONTROL_1, + .bypass_mask = ARIZONA_LDO1_BYPASS, + .min_uV = 900000, + .uV_step = 50000, + .n_voltages = 8, + .enable_time = 1500, + .ramp_delay = 24000, + + .owner = THIS_MODULE, +}; + +static const struct regulator_ops arizona_ldo1_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_desc arizona_ldo1 = { + .name = "LDO1", + .supply_name = "LDOVDD", + .type = REGULATOR_VOLTAGE, + .ops = &arizona_ldo1_ops, + + .vsel_reg = ARIZONA_LDO1_CONTROL_1, + .vsel_mask = ARIZONA_LDO1_VSEL_MASK, + .min_uV = 900000, + .uV_step = 25000, + .n_voltages = 13, + .enable_time = 500, + .ramp_delay = 24000, + + .owner = THIS_MODULE, +}; + +static const struct regulator_init_data arizona_ldo1_dvfs = { + .constraints = { + .min_uV = 1200000, + .max_uV = 1800000, + .valid_ops_mask = REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_VOLTAGE, + }, + .num_consumer_supplies = 1, +}; + +static const struct regulator_init_data arizona_ldo1_default = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, +}; + +static const struct regulator_init_data arizona_ldo1_wm5110 = { + .constraints = { + .min_uV = 1175000, + .max_uV = 1200000, + .valid_ops_mask = REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_VOLTAGE, + }, + .num_consumer_supplies = 1, +}; + +static int arizona_ldo1_of_get_pdata(struct arizona_ldo1_pdata *pdata, + struct regulator_config *config, + const struct regulator_desc *desc, + bool *external_dcvdd) +{ + struct arizona_ldo1 *ldo1 = config->driver_data; + struct device_node *np = config->dev->of_node; + struct device_node *init_node, *dcvdd_node; + struct regulator_init_data *init_data; + + init_node = of_get_child_by_name(np, "ldo1"); + dcvdd_node = of_parse_phandle(np, "DCVDD-supply", 0); + + if (init_node) { + config->of_node = init_node; + + init_data = of_get_regulator_init_data(config->dev, init_node, + desc); + if (init_data) { + init_data->consumer_supplies = &ldo1->supply; + init_data->num_consumer_supplies = 1; + + if (dcvdd_node && dcvdd_node != init_node) + *external_dcvdd = true; + + pdata->init_data = init_data; + } + } else if (dcvdd_node) { + *external_dcvdd = true; + } + + of_node_put(dcvdd_node); + + return 0; +} + +static int arizona_ldo1_common_init(struct platform_device *pdev, + struct arizona_ldo1 *ldo1, + const struct regulator_desc *desc, + struct arizona_ldo1_pdata *pdata, + bool *external_dcvdd) +{ + struct device *parent_dev = pdev->dev.parent; + struct regulator_config config = { }; + int ret; + + *external_dcvdd = false; + + ldo1->supply.supply = "DCVDD"; + ldo1->init_data.consumer_supplies = &ldo1->supply; + ldo1->supply.dev_name = dev_name(parent_dev); + + config.dev = parent_dev; + config.driver_data = ldo1; + config.regmap = ldo1->regmap; + + if (IS_ENABLED(CONFIG_OF)) { + if (!dev_get_platdata(parent_dev)) { + ret = arizona_ldo1_of_get_pdata(pdata, + &config, desc, + external_dcvdd); + if (ret < 0) + return ret; + } + } + + /* We assume that high output = regulator off + * Don't use devm, since we need to get against the parent device + * so clean up would happen at the wrong time + */ + config.ena_gpiod = gpiod_get_optional(parent_dev, "wlf,ldoena", + GPIOD_OUT_LOW); + if (IS_ERR(config.ena_gpiod)) + return PTR_ERR(config.ena_gpiod); + + ldo1->ena_gpiod = config.ena_gpiod; + + if (pdata->init_data) + config.init_data = pdata->init_data; + else + config.init_data = &ldo1->init_data; + + /* + * LDO1 can only be used to supply DCVDD so if it has no + * consumers then DCVDD is supplied externally. + */ + if (config.init_data->num_consumer_supplies == 0) + *external_dcvdd = true; + + ldo1->regulator = devm_regulator_register(&pdev->dev, desc, &config); + + of_node_put(config.of_node); + + if (IS_ERR(ldo1->regulator)) { + if (config.ena_gpiod) + gpiod_put(config.ena_gpiod); + + ret = PTR_ERR(ldo1->regulator); + dev_err(&pdev->dev, "Failed to register LDO1 supply: %d\n", + ret); + return ret; + } + + platform_set_drvdata(pdev, ldo1); + + return 0; +} + +static int arizona_ldo1_probe(struct platform_device *pdev) +{ + struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); + struct arizona_ldo1 *ldo1; + const struct regulator_desc *desc; + bool external_dcvdd; + int ret; + + ldo1 = devm_kzalloc(&pdev->dev, sizeof(*ldo1), GFP_KERNEL); + if (!ldo1) + return -ENOMEM; + + ldo1->regmap = arizona->regmap; + + /* + * Since the chip usually supplies itself we provide some + * default init_data for it. This will be overridden with + * platform data if provided. + */ + switch (arizona->type) { + case WM5102: + case WM8997: + case WM8998: + case WM1814: + desc = &arizona_ldo1_hc; + ldo1->init_data = arizona_ldo1_dvfs; + break; + case WM5110: + case WM8280: + desc = &arizona_ldo1; + ldo1->init_data = arizona_ldo1_wm5110; + break; + default: + desc = &arizona_ldo1; + ldo1->init_data = arizona_ldo1_default; + break; + } + + ret = arizona_ldo1_common_init(pdev, ldo1, desc, + &arizona->pdata.ldo1, + &external_dcvdd); + if (ret == 0) + arizona->external_dcvdd = external_dcvdd; + + return ret; +} + +static int arizona_ldo1_remove(struct platform_device *pdev) +{ + struct arizona_ldo1 *ldo1 = platform_get_drvdata(pdev); + + if (ldo1->ena_gpiod) + gpiod_put(ldo1->ena_gpiod); + + return 0; +} + +static struct platform_driver arizona_ldo1_driver = { + .probe = arizona_ldo1_probe, + .remove = arizona_ldo1_remove, + .driver = { + .name = "arizona-ldo1", + }, +}; + +module_platform_driver(arizona_ldo1_driver); + +/* Module information */ +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("Arizona LDO1 driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:arizona-ldo1"); diff --git a/drivers/regulator/arizona-micsupp.c b/drivers/regulator/arizona-micsupp.c new file mode 100644 index 000000000..120de94ca --- /dev/null +++ b/drivers/regulator/arizona-micsupp.c @@ -0,0 +1,337 @@ +/* + * arizona-micsupp.c -- Microphone supply for Arizona devices + * + * Copyright 2012 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <sound/soc.h> + +#include <linux/mfd/arizona/core.h> +#include <linux/mfd/arizona/pdata.h> +#include <linux/mfd/arizona/registers.h> + +#include <linux/regulator/arizona-micsupp.h> + +struct arizona_micsupp { + struct regulator_dev *regulator; + struct regmap *regmap; + struct snd_soc_dapm_context **dapm; + unsigned int enable_reg; + struct device *dev; + + struct regulator_consumer_supply supply; + struct regulator_init_data init_data; + + struct work_struct check_cp_work; +}; + +static void arizona_micsupp_check_cp(struct work_struct *work) +{ + struct arizona_micsupp *micsupp = + container_of(work, struct arizona_micsupp, check_cp_work); + struct snd_soc_dapm_context *dapm = *micsupp->dapm; + struct snd_soc_component *component; + unsigned int val; + int ret; + + ret = regmap_read(micsupp->regmap, micsupp->enable_reg, &val); + if (ret != 0) { + dev_err(micsupp->dev, + "Failed to read CP state: %d\n", ret); + return; + } + + if (dapm) { + component = snd_soc_dapm_to_component(dapm); + + if ((val & (ARIZONA_CPMIC_ENA | ARIZONA_CPMIC_BYPASS)) == + ARIZONA_CPMIC_ENA) + snd_soc_component_force_enable_pin(component, + "MICSUPP"); + else + snd_soc_component_disable_pin(component, "MICSUPP"); + + snd_soc_dapm_sync(dapm); + } +} + +static int arizona_micsupp_enable(struct regulator_dev *rdev) +{ + struct arizona_micsupp *micsupp = rdev_get_drvdata(rdev); + int ret; + + ret = regulator_enable_regmap(rdev); + + if (ret == 0) + schedule_work(&micsupp->check_cp_work); + + return ret; +} + +static int arizona_micsupp_disable(struct regulator_dev *rdev) +{ + struct arizona_micsupp *micsupp = rdev_get_drvdata(rdev); + int ret; + + ret = regulator_disable_regmap(rdev); + if (ret == 0) + schedule_work(&micsupp->check_cp_work); + + return ret; +} + +static int arizona_micsupp_set_bypass(struct regulator_dev *rdev, bool ena) +{ + struct arizona_micsupp *micsupp = rdev_get_drvdata(rdev); + int ret; + + ret = regulator_set_bypass_regmap(rdev, ena); + if (ret == 0) + schedule_work(&micsupp->check_cp_work); + + return ret; +} + +static const struct regulator_ops arizona_micsupp_ops = { + .enable = arizona_micsupp_enable, + .disable = arizona_micsupp_disable, + .is_enabled = regulator_is_enabled_regmap, + + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = arizona_micsupp_set_bypass, +}; + +static const struct regulator_linear_range arizona_micsupp_ranges[] = { + REGULATOR_LINEAR_RANGE(1700000, 0, 0x1e, 50000), + REGULATOR_LINEAR_RANGE(3300000, 0x1f, 0x1f, 0), +}; + +static const struct regulator_desc arizona_micsupp = { + .name = "MICVDD", + .supply_name = "CPVDD", + .type = REGULATOR_VOLTAGE, + .n_voltages = 32, + .ops = &arizona_micsupp_ops, + + .vsel_reg = ARIZONA_LDO2_CONTROL_1, + .vsel_mask = ARIZONA_LDO2_VSEL_MASK, + .enable_reg = ARIZONA_MIC_CHARGE_PUMP_1, + .enable_mask = ARIZONA_CPMIC_ENA, + .bypass_reg = ARIZONA_MIC_CHARGE_PUMP_1, + .bypass_mask = ARIZONA_CPMIC_BYPASS, + + .linear_ranges = arizona_micsupp_ranges, + .n_linear_ranges = ARRAY_SIZE(arizona_micsupp_ranges), + + .enable_time = 3000, + + .owner = THIS_MODULE, +}; + +static const struct regulator_linear_range arizona_micsupp_ext_ranges[] = { + REGULATOR_LINEAR_RANGE(900000, 0, 0x14, 25000), + REGULATOR_LINEAR_RANGE(1500000, 0x15, 0x27, 100000), +}; + +static const struct regulator_desc arizona_micsupp_ext = { + .name = "MICVDD", + .supply_name = "CPVDD", + .type = REGULATOR_VOLTAGE, + .n_voltages = 40, + .ops = &arizona_micsupp_ops, + + .vsel_reg = ARIZONA_LDO2_CONTROL_1, + .vsel_mask = ARIZONA_LDO2_VSEL_MASK, + .enable_reg = ARIZONA_MIC_CHARGE_PUMP_1, + .enable_mask = ARIZONA_CPMIC_ENA, + .bypass_reg = ARIZONA_MIC_CHARGE_PUMP_1, + .bypass_mask = ARIZONA_CPMIC_BYPASS, + + .linear_ranges = arizona_micsupp_ext_ranges, + .n_linear_ranges = ARRAY_SIZE(arizona_micsupp_ext_ranges), + + .enable_time = 3000, + + .owner = THIS_MODULE, +}; + +static const struct regulator_init_data arizona_micsupp_default = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_BYPASS, + .min_uV = 1700000, + .max_uV = 3300000, + }, + + .num_consumer_supplies = 1, +}; + +static const struct regulator_init_data arizona_micsupp_ext_default = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_BYPASS, + .min_uV = 900000, + .max_uV = 3300000, + }, + + .num_consumer_supplies = 1, +}; + +static int arizona_micsupp_of_get_pdata(struct arizona_micsupp_pdata *pdata, + struct regulator_config *config, + const struct regulator_desc *desc) +{ + struct arizona_micsupp *micsupp = config->driver_data; + struct device_node *np; + struct regulator_init_data *init_data; + + np = of_get_child_by_name(config->dev->of_node, "micvdd"); + + if (np) { + config->of_node = np; + + init_data = of_get_regulator_init_data(config->dev, np, desc); + + if (init_data) { + init_data->consumer_supplies = &micsupp->supply; + init_data->num_consumer_supplies = 1; + + pdata->init_data = init_data; + } + } + + return 0; +} + +static int arizona_micsupp_common_init(struct platform_device *pdev, + struct arizona_micsupp *micsupp, + const struct regulator_desc *desc, + struct arizona_micsupp_pdata *pdata) +{ + struct regulator_config config = { }; + int ret; + + INIT_WORK(&micsupp->check_cp_work, arizona_micsupp_check_cp); + + micsupp->init_data.consumer_supplies = &micsupp->supply; + micsupp->supply.supply = "MICVDD"; + micsupp->supply.dev_name = dev_name(micsupp->dev); + micsupp->enable_reg = desc->enable_reg; + + config.dev = micsupp->dev; + config.driver_data = micsupp; + config.regmap = micsupp->regmap; + + if (IS_ENABLED(CONFIG_OF)) { + if (!dev_get_platdata(micsupp->dev)) { + ret = arizona_micsupp_of_get_pdata(pdata, &config, + desc); + if (ret < 0) + return ret; + } + } + + if (pdata->init_data) + config.init_data = pdata->init_data; + else + config.init_data = &micsupp->init_data; + + /* Default to regulated mode */ + regmap_update_bits(micsupp->regmap, micsupp->enable_reg, + ARIZONA_CPMIC_BYPASS, 0); + + micsupp->regulator = devm_regulator_register(&pdev->dev, + desc, + &config); + + of_node_put(config.of_node); + + if (IS_ERR(micsupp->regulator)) { + ret = PTR_ERR(micsupp->regulator); + dev_err(micsupp->dev, "Failed to register mic supply: %d\n", + ret); + return ret; + } + + platform_set_drvdata(pdev, micsupp); + + return 0; +} + +static int arizona_micsupp_probe(struct platform_device *pdev) +{ + struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); + const struct regulator_desc *desc; + struct arizona_micsupp *micsupp; + + micsupp = devm_kzalloc(&pdev->dev, sizeof(*micsupp), GFP_KERNEL); + if (!micsupp) + return -ENOMEM; + + micsupp->regmap = arizona->regmap; + micsupp->dapm = &arizona->dapm; + micsupp->dev = arizona->dev; + + /* + * Since the chip usually supplies itself we provide some + * default init_data for it. This will be overridden with + * platform data if provided. + */ + switch (arizona->type) { + case WM5110: + case WM8280: + desc = &arizona_micsupp_ext; + micsupp->init_data = arizona_micsupp_ext_default; + break; + default: + desc = &arizona_micsupp; + micsupp->init_data = arizona_micsupp_default; + break; + } + + return arizona_micsupp_common_init(pdev, micsupp, desc, + &arizona->pdata.micvdd); +} + +static struct platform_driver arizona_micsupp_driver = { + .probe = arizona_micsupp_probe, + .driver = { + .name = "arizona-micsupp", + }, +}; + +module_platform_driver(arizona_micsupp_driver); + +/* Module information */ +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("Arizona microphone supply driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:arizona-micsupp"); diff --git a/drivers/regulator/as3711-regulator.c b/drivers/regulator/as3711-regulator.c new file mode 100644 index 000000000..565a71343 --- /dev/null +++ b/drivers/regulator/as3711-regulator.c @@ -0,0 +1,291 @@ +/* + * AS3711 PMIC regulator driver, using DCDC Step Down and LDO supplies + * + * Copyright (C) 2012 Renesas Electronics Corporation + * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License as + * published by the Free Software Foundation + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/mfd/as3711.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/slab.h> + +struct as3711_regulator_info { + struct regulator_desc desc; +}; + +struct as3711_regulator { + struct as3711_regulator_info *reg_info; +}; + +/* + * The regulator API supports 4 modes of operataion: FAST, NORMAL, IDLE and + * STANDBY. We map them in the following way to AS3711 SD1-4 DCDC modes: + * FAST: sdX_fast=1 + * NORMAL: low_noise=1 + * IDLE: low_noise=0 + */ + +static int as3711_set_mode_sd(struct regulator_dev *rdev, unsigned int mode) +{ + unsigned int fast_bit = rdev->desc->enable_mask, + low_noise_bit = fast_bit << 4; + u8 val; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = fast_bit | low_noise_bit; + break; + case REGULATOR_MODE_NORMAL: + val = low_noise_bit; + break; + case REGULATOR_MODE_IDLE: + val = 0; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(rdev->regmap, AS3711_SD_CONTROL_1, + low_noise_bit | fast_bit, val); +} + +static unsigned int as3711_get_mode_sd(struct regulator_dev *rdev) +{ + unsigned int fast_bit = rdev->desc->enable_mask, + low_noise_bit = fast_bit << 4, mask = fast_bit | low_noise_bit; + unsigned int val; + int ret = regmap_read(rdev->regmap, AS3711_SD_CONTROL_1, &val); + + if (ret < 0) + return ret; + + if ((val & mask) == mask) + return REGULATOR_MODE_FAST; + + if ((val & mask) == low_noise_bit) + return REGULATOR_MODE_NORMAL; + + if (!(val & mask)) + return REGULATOR_MODE_IDLE; + + return -EINVAL; +} + +static const struct regulator_ops as3711_sd_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .get_mode = as3711_get_mode_sd, + .set_mode = as3711_set_mode_sd, +}; + +static const struct regulator_ops as3711_aldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, +}; + +static const struct regulator_ops as3711_dldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, +}; + +static const struct regulator_linear_range as3711_sd_ranges[] = { + REGULATOR_LINEAR_RANGE(612500, 0x1, 0x40, 12500), + REGULATOR_LINEAR_RANGE(1425000, 0x41, 0x70, 25000), + REGULATOR_LINEAR_RANGE(2650000, 0x71, 0x7f, 50000), +}; + +static const struct regulator_linear_range as3711_aldo_ranges[] = { + REGULATOR_LINEAR_RANGE(1200000, 0, 0xf, 50000), + REGULATOR_LINEAR_RANGE(1800000, 0x10, 0x1f, 100000), +}; + +static const struct regulator_linear_range as3711_dldo_ranges[] = { + REGULATOR_LINEAR_RANGE(900000, 0, 0x10, 50000), + REGULATOR_LINEAR_RANGE(1750000, 0x20, 0x3f, 50000), +}; + +#define AS3711_REG(_id, _en_reg, _en_bit, _vmask, _sfx) \ + [AS3711_REGULATOR_ ## _id] = { \ + .desc = { \ + .name = "as3711-regulator-" # _id, \ + .id = AS3711_REGULATOR_ ## _id, \ + .n_voltages = (_vmask + 1), \ + .ops = &as3711_ ## _sfx ## _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .vsel_reg = AS3711_ ## _id ## _VOLTAGE, \ + .vsel_mask = _vmask, \ + .enable_reg = AS3711_ ## _en_reg, \ + .enable_mask = BIT(_en_bit), \ + .linear_ranges = as3711_ ## _sfx ## _ranges, \ + .n_linear_ranges = ARRAY_SIZE(as3711_ ## _sfx ## _ranges), \ + }, \ +} + +static struct as3711_regulator_info as3711_reg_info[] = { + AS3711_REG(SD_1, SD_CONTROL, 0, 0x7f, sd), + AS3711_REG(SD_2, SD_CONTROL, 1, 0x7f, sd), + AS3711_REG(SD_3, SD_CONTROL, 2, 0x7f, sd), + AS3711_REG(SD_4, SD_CONTROL, 3, 0x7f, sd), + AS3711_REG(LDO_1, LDO_1_VOLTAGE, 7, 0x1f, aldo), + AS3711_REG(LDO_2, LDO_2_VOLTAGE, 7, 0x1f, aldo), + AS3711_REG(LDO_3, LDO_3_VOLTAGE, 7, 0x3f, dldo), + AS3711_REG(LDO_4, LDO_4_VOLTAGE, 7, 0x3f, dldo), + AS3711_REG(LDO_5, LDO_5_VOLTAGE, 7, 0x3f, dldo), + AS3711_REG(LDO_6, LDO_6_VOLTAGE, 7, 0x3f, dldo), + AS3711_REG(LDO_7, LDO_7_VOLTAGE, 7, 0x3f, dldo), + AS3711_REG(LDO_8, LDO_8_VOLTAGE, 7, 0x3f, dldo), + /* StepUp output voltage depends on supplying regulator */ +}; + +#define AS3711_REGULATOR_NUM ARRAY_SIZE(as3711_reg_info) + +static struct of_regulator_match +as3711_regulator_matches[AS3711_REGULATOR_NUM] = { + [AS3711_REGULATOR_SD_1] = { .name = "sd1" }, + [AS3711_REGULATOR_SD_2] = { .name = "sd2" }, + [AS3711_REGULATOR_SD_3] = { .name = "sd3" }, + [AS3711_REGULATOR_SD_4] = { .name = "sd4" }, + [AS3711_REGULATOR_LDO_1] = { .name = "ldo1" }, + [AS3711_REGULATOR_LDO_2] = { .name = "ldo2" }, + [AS3711_REGULATOR_LDO_3] = { .name = "ldo3" }, + [AS3711_REGULATOR_LDO_4] = { .name = "ldo4" }, + [AS3711_REGULATOR_LDO_5] = { .name = "ldo5" }, + [AS3711_REGULATOR_LDO_6] = { .name = "ldo6" }, + [AS3711_REGULATOR_LDO_7] = { .name = "ldo7" }, + [AS3711_REGULATOR_LDO_8] = { .name = "ldo8" }, +}; + +static int as3711_regulator_parse_dt(struct device *dev, + struct device_node **of_node, const int count) +{ + struct as3711_regulator_pdata *pdata = dev_get_platdata(dev); + struct device_node *regulators = + of_get_child_by_name(dev->parent->of_node, "regulators"); + struct of_regulator_match *match; + int ret, i; + + if (!regulators) { + dev_err(dev, "regulator node not found\n"); + return -ENODEV; + } + + ret = of_regulator_match(dev->parent, regulators, + as3711_regulator_matches, count); + of_node_put(regulators); + if (ret < 0) { + dev_err(dev, "Error parsing regulator init data: %d\n", ret); + return ret; + } + + for (i = 0, match = as3711_regulator_matches; i < count; i++, match++) + if (match->of_node) { + pdata->init_data[i] = match->init_data; + of_node[i] = match->of_node; + } + + return 0; +} + +static int as3711_regulator_probe(struct platform_device *pdev) +{ + struct as3711_regulator_pdata *pdata = dev_get_platdata(&pdev->dev); + struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = {.dev = &pdev->dev,}; + struct as3711_regulator *reg = NULL; + struct as3711_regulator *regs; + struct device_node *of_node[AS3711_REGULATOR_NUM] = {}; + struct regulator_dev *rdev; + struct as3711_regulator_info *ri; + int ret; + int id; + + if (!pdata) { + dev_err(&pdev->dev, "No platform data...\n"); + return -ENODEV; + } + + if (pdev->dev.parent->of_node) { + ret = as3711_regulator_parse_dt(&pdev->dev, of_node, AS3711_REGULATOR_NUM); + if (ret < 0) { + dev_err(&pdev->dev, "DT parsing failed: %d\n", ret); + return ret; + } + } + + regs = devm_kcalloc(&pdev->dev, + AS3711_REGULATOR_NUM, + sizeof(struct as3711_regulator), + GFP_KERNEL); + if (!regs) + return -ENOMEM; + + for (id = 0, ri = as3711_reg_info; id < AS3711_REGULATOR_NUM; ++id, ri++) { + reg = ®s[id]; + reg->reg_info = ri; + + config.init_data = pdata->init_data[id]; + config.driver_data = reg; + config.regmap = as3711->regmap; + config.of_node = of_node[id]; + + rdev = devm_regulator_register(&pdev->dev, &ri->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "Failed to register regulator %s\n", + ri->desc.name); + return PTR_ERR(rdev); + } + } + platform_set_drvdata(pdev, regs); + return 0; +} + +static struct platform_driver as3711_regulator_driver = { + .driver = { + .name = "as3711-regulator", + }, + .probe = as3711_regulator_probe, +}; + +static int __init as3711_regulator_init(void) +{ + return platform_driver_register(&as3711_regulator_driver); +} +subsys_initcall(as3711_regulator_init); + +static void __exit as3711_regulator_exit(void) +{ + platform_driver_unregister(&as3711_regulator_driver); +} +module_exit(as3711_regulator_exit); + +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_DESCRIPTION("AS3711 regulator driver"); +MODULE_ALIAS("platform:as3711-regulator"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/as3722-regulator.c b/drivers/regulator/as3722-regulator.c new file mode 100644 index 000000000..66337e127 --- /dev/null +++ b/drivers/regulator/as3722-regulator.c @@ -0,0 +1,973 @@ +/* + * Voltage regulator support for AMS AS3722 PMIC + * + * Copyright (C) 2013 ams + * + * Author: Florian Lobmaier <florian.lobmaier@ams.com> + * Author: Laxman Dewangan <ldewangan@nvidia.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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mfd/as3722.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/slab.h> + +/* Regulator IDs */ +enum as3722_regulators_id { + AS3722_REGULATOR_ID_SD0, + AS3722_REGULATOR_ID_SD1, + AS3722_REGULATOR_ID_SD2, + AS3722_REGULATOR_ID_SD3, + AS3722_REGULATOR_ID_SD4, + AS3722_REGULATOR_ID_SD5, + AS3722_REGULATOR_ID_SD6, + AS3722_REGULATOR_ID_LDO0, + AS3722_REGULATOR_ID_LDO1, + AS3722_REGULATOR_ID_LDO2, + AS3722_REGULATOR_ID_LDO3, + AS3722_REGULATOR_ID_LDO4, + AS3722_REGULATOR_ID_LDO5, + AS3722_REGULATOR_ID_LDO6, + AS3722_REGULATOR_ID_LDO7, + AS3722_REGULATOR_ID_LDO9, + AS3722_REGULATOR_ID_LDO10, + AS3722_REGULATOR_ID_LDO11, + AS3722_REGULATOR_ID_MAX, +}; + +struct as3722_register_mapping { + u8 regulator_id; + const char *name; + const char *sname; + u8 vsel_reg; + u8 vsel_mask; + int n_voltages; + u32 enable_reg; + u8 enable_mask; + u32 control_reg; + u8 mode_mask; + u32 sleep_ctrl_reg; + u8 sleep_ctrl_mask; +}; + +struct as3722_regulator_config_data { + struct regulator_init_data *reg_init; + bool enable_tracking; + int ext_control; +}; + +struct as3722_regulators { + struct device *dev; + struct as3722 *as3722; + struct regulator_dev *rdevs[AS3722_REGULATOR_ID_MAX]; + struct regulator_desc desc[AS3722_REGULATOR_ID_MAX]; + struct as3722_regulator_config_data + reg_config_data[AS3722_REGULATOR_ID_MAX]; +}; + +static const struct as3722_register_mapping as3722_reg_lookup[] = { + { + .regulator_id = AS3722_REGULATOR_ID_SD0, + .name = "as3722-sd0", + .vsel_reg = AS3722_SD0_VOLTAGE_REG, + .vsel_mask = AS3722_SD_VSEL_MASK, + .enable_reg = AS3722_SD_CONTROL_REG, + .enable_mask = AS3722_SDn_CTRL(0), + .sleep_ctrl_reg = AS3722_ENABLE_CTRL1_REG, + .sleep_ctrl_mask = AS3722_SD0_EXT_ENABLE_MASK, + .control_reg = AS3722_SD0_CONTROL_REG, + .mode_mask = AS3722_SD0_MODE_FAST, + }, + { + .regulator_id = AS3722_REGULATOR_ID_SD1, + .name = "as3722-sd1", + .vsel_reg = AS3722_SD1_VOLTAGE_REG, + .vsel_mask = AS3722_SD_VSEL_MASK, + .enable_reg = AS3722_SD_CONTROL_REG, + .enable_mask = AS3722_SDn_CTRL(1), + .sleep_ctrl_reg = AS3722_ENABLE_CTRL1_REG, + .sleep_ctrl_mask = AS3722_SD1_EXT_ENABLE_MASK, + .control_reg = AS3722_SD1_CONTROL_REG, + .mode_mask = AS3722_SD1_MODE_FAST, + }, + { + .regulator_id = AS3722_REGULATOR_ID_SD2, + .name = "as3722-sd2", + .sname = "vsup-sd2", + .vsel_reg = AS3722_SD2_VOLTAGE_REG, + .vsel_mask = AS3722_SD_VSEL_MASK, + .enable_reg = AS3722_SD_CONTROL_REG, + .enable_mask = AS3722_SDn_CTRL(2), + .sleep_ctrl_reg = AS3722_ENABLE_CTRL1_REG, + .sleep_ctrl_mask = AS3722_SD2_EXT_ENABLE_MASK, + .control_reg = AS3722_SD23_CONTROL_REG, + .mode_mask = AS3722_SD2_MODE_FAST, + .n_voltages = AS3722_SD2_VSEL_MAX + 1, + }, + { + .regulator_id = AS3722_REGULATOR_ID_SD3, + .name = "as3722-sd3", + .sname = "vsup-sd3", + .vsel_reg = AS3722_SD3_VOLTAGE_REG, + .vsel_mask = AS3722_SD_VSEL_MASK, + .enable_reg = AS3722_SD_CONTROL_REG, + .enable_mask = AS3722_SDn_CTRL(3), + .sleep_ctrl_reg = AS3722_ENABLE_CTRL1_REG, + .sleep_ctrl_mask = AS3722_SD3_EXT_ENABLE_MASK, + .control_reg = AS3722_SD23_CONTROL_REG, + .mode_mask = AS3722_SD3_MODE_FAST, + .n_voltages = AS3722_SD2_VSEL_MAX + 1, + }, + { + .regulator_id = AS3722_REGULATOR_ID_SD4, + .name = "as3722-sd4", + .sname = "vsup-sd4", + .vsel_reg = AS3722_SD4_VOLTAGE_REG, + .vsel_mask = AS3722_SD_VSEL_MASK, + .enable_reg = AS3722_SD_CONTROL_REG, + .enable_mask = AS3722_SDn_CTRL(4), + .sleep_ctrl_reg = AS3722_ENABLE_CTRL2_REG, + .sleep_ctrl_mask = AS3722_SD4_EXT_ENABLE_MASK, + .control_reg = AS3722_SD4_CONTROL_REG, + .mode_mask = AS3722_SD4_MODE_FAST, + .n_voltages = AS3722_SD2_VSEL_MAX + 1, + }, + { + .regulator_id = AS3722_REGULATOR_ID_SD5, + .name = "as3722-sd5", + .sname = "vsup-sd5", + .vsel_reg = AS3722_SD5_VOLTAGE_REG, + .vsel_mask = AS3722_SD_VSEL_MASK, + .enable_reg = AS3722_SD_CONTROL_REG, + .enable_mask = AS3722_SDn_CTRL(5), + .sleep_ctrl_reg = AS3722_ENABLE_CTRL2_REG, + .sleep_ctrl_mask = AS3722_SD5_EXT_ENABLE_MASK, + .control_reg = AS3722_SD5_CONTROL_REG, + .mode_mask = AS3722_SD5_MODE_FAST, + .n_voltages = AS3722_SD2_VSEL_MAX + 1, + }, + { + .regulator_id = AS3722_REGULATOR_ID_SD6, + .name = "as3722-sd6", + .vsel_reg = AS3722_SD6_VOLTAGE_REG, + .vsel_mask = AS3722_SD_VSEL_MASK, + .enable_reg = AS3722_SD_CONTROL_REG, + .enable_mask = AS3722_SDn_CTRL(6), + .sleep_ctrl_reg = AS3722_ENABLE_CTRL2_REG, + .sleep_ctrl_mask = AS3722_SD6_EXT_ENABLE_MASK, + .control_reg = AS3722_SD6_CONTROL_REG, + .mode_mask = AS3722_SD6_MODE_FAST, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO0, + .name = "as3722-ldo0", + .sname = "vin-ldo0", + .vsel_reg = AS3722_LDO0_VOLTAGE_REG, + .vsel_mask = AS3722_LDO0_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL0_REG, + .enable_mask = AS3722_LDO0_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL3_REG, + .sleep_ctrl_mask = AS3722_LDO0_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO0_NUM_VOLT, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO1, + .name = "as3722-ldo1", + .sname = "vin-ldo1-6", + .vsel_reg = AS3722_LDO1_VOLTAGE_REG, + .vsel_mask = AS3722_LDO_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL0_REG, + .enable_mask = AS3722_LDO1_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL3_REG, + .sleep_ctrl_mask = AS3722_LDO1_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO_NUM_VOLT, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO2, + .name = "as3722-ldo2", + .sname = "vin-ldo2-5-7", + .vsel_reg = AS3722_LDO2_VOLTAGE_REG, + .vsel_mask = AS3722_LDO_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL0_REG, + .enable_mask = AS3722_LDO2_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL3_REG, + .sleep_ctrl_mask = AS3722_LDO2_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO_NUM_VOLT, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO3, + .name = "as3722-ldo3", + .sname = "vin-ldo3-4", + .vsel_reg = AS3722_LDO3_VOLTAGE_REG, + .vsel_mask = AS3722_LDO3_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL0_REG, + .enable_mask = AS3722_LDO3_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL3_REG, + .sleep_ctrl_mask = AS3722_LDO3_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO3_NUM_VOLT, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO4, + .name = "as3722-ldo4", + .sname = "vin-ldo3-4", + .vsel_reg = AS3722_LDO4_VOLTAGE_REG, + .vsel_mask = AS3722_LDO_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL0_REG, + .enable_mask = AS3722_LDO4_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL4_REG, + .sleep_ctrl_mask = AS3722_LDO4_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO_NUM_VOLT, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO5, + .name = "as3722-ldo5", + .sname = "vin-ldo2-5-7", + .vsel_reg = AS3722_LDO5_VOLTAGE_REG, + .vsel_mask = AS3722_LDO_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL0_REG, + .enable_mask = AS3722_LDO5_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL4_REG, + .sleep_ctrl_mask = AS3722_LDO5_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO_NUM_VOLT, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO6, + .name = "as3722-ldo6", + .sname = "vin-ldo1-6", + .vsel_reg = AS3722_LDO6_VOLTAGE_REG, + .vsel_mask = AS3722_LDO_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL0_REG, + .enable_mask = AS3722_LDO6_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL4_REG, + .sleep_ctrl_mask = AS3722_LDO6_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO_NUM_VOLT, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO7, + .name = "as3722-ldo7", + .sname = "vin-ldo2-5-7", + .vsel_reg = AS3722_LDO7_VOLTAGE_REG, + .vsel_mask = AS3722_LDO_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL0_REG, + .enable_mask = AS3722_LDO7_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL4_REG, + .sleep_ctrl_mask = AS3722_LDO7_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO_NUM_VOLT, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO9, + .name = "as3722-ldo9", + .sname = "vin-ldo9-10", + .vsel_reg = AS3722_LDO9_VOLTAGE_REG, + .vsel_mask = AS3722_LDO_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL1_REG, + .enable_mask = AS3722_LDO9_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL5_REG, + .sleep_ctrl_mask = AS3722_LDO9_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO_NUM_VOLT, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO10, + .name = "as3722-ldo10", + .sname = "vin-ldo9-10", + .vsel_reg = AS3722_LDO10_VOLTAGE_REG, + .vsel_mask = AS3722_LDO_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL1_REG, + .enable_mask = AS3722_LDO10_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL5_REG, + .sleep_ctrl_mask = AS3722_LDO10_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO_NUM_VOLT, + }, + { + .regulator_id = AS3722_REGULATOR_ID_LDO11, + .name = "as3722-ldo11", + .sname = "vin-ldo11", + .vsel_reg = AS3722_LDO11_VOLTAGE_REG, + .vsel_mask = AS3722_LDO_VSEL_MASK, + .enable_reg = AS3722_LDOCONTROL1_REG, + .enable_mask = AS3722_LDO11_CTRL, + .sleep_ctrl_reg = AS3722_ENABLE_CTRL5_REG, + .sleep_ctrl_mask = AS3722_LDO11_EXT_ENABLE_MASK, + .n_voltages = AS3722_LDO_NUM_VOLT, + }, +}; + + +static const int as3722_ldo_current[] = { 150000, 300000 }; +static const int as3722_sd016_current[] = { 2500000, 3000000, 3500000 }; + +static int as3722_current_to_index(int min_uA, int max_uA, + const int *curr_table, int n_currents) +{ + int i; + + for (i = n_currents - 1; i >= 0; i--) { + if ((min_uA <= curr_table[i]) && (curr_table[i] <= max_uA)) + return i; + } + return -EINVAL; +} + +static int as3722_ldo_get_current_limit(struct regulator_dev *rdev) +{ + struct as3722_regulators *as3722_regs = rdev_get_drvdata(rdev); + struct as3722 *as3722 = as3722_regs->as3722; + int id = rdev_get_id(rdev); + u32 val; + int ret; + + ret = as3722_read(as3722, as3722_reg_lookup[id].vsel_reg, &val); + if (ret < 0) { + dev_err(as3722_regs->dev, "Reg 0x%02x read failed: %d\n", + as3722_reg_lookup[id].vsel_reg, ret); + return ret; + } + if (val & AS3722_LDO_ILIMIT_MASK) + return 300000; + return 150000; +} + +static int as3722_ldo_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct as3722_regulators *as3722_regs = rdev_get_drvdata(rdev); + struct as3722 *as3722 = as3722_regs->as3722; + int id = rdev_get_id(rdev); + int ret; + u32 reg = 0; + + ret = as3722_current_to_index(min_uA, max_uA, as3722_ldo_current, + ARRAY_SIZE(as3722_ldo_current)); + if (ret < 0) { + dev_err(as3722_regs->dev, + "Current range min:max = %d:%d does not support\n", + min_uA, max_uA); + return ret; + } + if (ret) + reg = AS3722_LDO_ILIMIT_BIT; + return as3722_update_bits(as3722, as3722_reg_lookup[id].vsel_reg, + AS3722_LDO_ILIMIT_MASK, reg); +} + +static const struct regulator_ops as3722_ldo0_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_current_limit = as3722_ldo_get_current_limit, + .set_current_limit = as3722_ldo_set_current_limit, +}; + +static const struct regulator_ops as3722_ldo0_extcntrl_ops = { + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_current_limit = as3722_ldo_get_current_limit, + .set_current_limit = as3722_ldo_set_current_limit, +}; + +static int as3722_ldo3_set_tracking_mode(struct as3722_regulators *as3722_reg, + int id, u8 mode) +{ + struct as3722 *as3722 = as3722_reg->as3722; + + switch (mode) { + case AS3722_LDO3_MODE_PMOS: + case AS3722_LDO3_MODE_PMOS_TRACKING: + case AS3722_LDO3_MODE_NMOS: + case AS3722_LDO3_MODE_SWITCH: + return as3722_update_bits(as3722, + as3722_reg_lookup[id].vsel_reg, + AS3722_LDO3_MODE_MASK, mode); + + default: + return -EINVAL; + } +} + +static int as3722_ldo3_get_current_limit(struct regulator_dev *rdev) +{ + return 150000; +} + +static const struct regulator_ops as3722_ldo3_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_current_limit = as3722_ldo3_get_current_limit, +}; + +static const struct regulator_ops as3722_ldo3_extcntrl_ops = { + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_current_limit = as3722_ldo3_get_current_limit, +}; + +static const struct regulator_ops as3722_ldo6_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .get_current_limit = as3722_ldo_get_current_limit, + .set_current_limit = as3722_ldo_set_current_limit, + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = regulator_set_bypass_regmap, +}; + +static const struct regulator_ops as3722_ldo6_extcntrl_ops = { + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .get_current_limit = as3722_ldo_get_current_limit, + .set_current_limit = as3722_ldo_set_current_limit, + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = regulator_set_bypass_regmap, +}; + +static const struct regulator_linear_range as3722_ldo_ranges[] = { + REGULATOR_LINEAR_RANGE(0, 0x00, 0x00, 0), + REGULATOR_LINEAR_RANGE(825000, 0x01, 0x24, 25000), + REGULATOR_LINEAR_RANGE(1725000, 0x40, 0x7F, 25000), +}; + +static const struct regulator_ops as3722_ldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .get_current_limit = as3722_ldo_get_current_limit, + .set_current_limit = as3722_ldo_set_current_limit, +}; + +static const struct regulator_ops as3722_ldo_extcntrl_ops = { + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .get_current_limit = as3722_ldo_get_current_limit, + .set_current_limit = as3722_ldo_set_current_limit, +}; + +static unsigned int as3722_sd_get_mode(struct regulator_dev *rdev) +{ + struct as3722_regulators *as3722_regs = rdev_get_drvdata(rdev); + struct as3722 *as3722 = as3722_regs->as3722; + int id = rdev_get_id(rdev); + u32 val; + int ret; + + if (!as3722_reg_lookup[id].control_reg) + return -ENOTSUPP; + + ret = as3722_read(as3722, as3722_reg_lookup[id].control_reg, &val); + if (ret < 0) { + dev_err(as3722_regs->dev, "Reg 0x%02x read failed: %d\n", + as3722_reg_lookup[id].control_reg, ret); + return ret; + } + + if (val & as3722_reg_lookup[id].mode_mask) + return REGULATOR_MODE_FAST; + else + return REGULATOR_MODE_NORMAL; +} + +static int as3722_sd_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct as3722_regulators *as3722_regs = rdev_get_drvdata(rdev); + struct as3722 *as3722 = as3722_regs->as3722; + u8 id = rdev_get_id(rdev); + u8 val = 0; + int ret; + + if (!as3722_reg_lookup[id].control_reg) + return -ERANGE; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = as3722_reg_lookup[id].mode_mask; + case REGULATOR_MODE_NORMAL: /* fall down */ + break; + default: + return -EINVAL; + } + + ret = as3722_update_bits(as3722, as3722_reg_lookup[id].control_reg, + as3722_reg_lookup[id].mode_mask, val); + if (ret < 0) { + dev_err(as3722_regs->dev, "Reg 0x%02x update failed: %d\n", + as3722_reg_lookup[id].control_reg, ret); + return ret; + } + return ret; +} + +static int as3722_sd016_get_current_limit(struct regulator_dev *rdev) +{ + struct as3722_regulators *as3722_regs = rdev_get_drvdata(rdev); + struct as3722 *as3722 = as3722_regs->as3722; + int id = rdev_get_id(rdev); + u32 val, reg; + int mask; + int ret; + + switch (id) { + case AS3722_REGULATOR_ID_SD0: + reg = AS3722_OVCURRENT_REG; + mask = AS3722_OVCURRENT_SD0_TRIP_MASK; + break; + case AS3722_REGULATOR_ID_SD1: + reg = AS3722_OVCURRENT_REG; + mask = AS3722_OVCURRENT_SD1_TRIP_MASK; + break; + case AS3722_REGULATOR_ID_SD6: + reg = AS3722_OVCURRENT_DEB_REG; + mask = AS3722_OVCURRENT_SD6_TRIP_MASK; + break; + default: + return -EINVAL; + } + ret = as3722_read(as3722, reg, &val); + if (ret < 0) { + dev_err(as3722_regs->dev, "Reg 0x%02x read failed: %d\n", + reg, ret); + return ret; + } + val &= mask; + val >>= ffs(mask) - 1; + if (val == 3) + return -EINVAL; + return as3722_sd016_current[val]; +} + +static int as3722_sd016_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct as3722_regulators *as3722_regs = rdev_get_drvdata(rdev); + struct as3722 *as3722 = as3722_regs->as3722; + int id = rdev_get_id(rdev); + int ret; + int val; + int mask; + u32 reg; + + ret = as3722_current_to_index(min_uA, max_uA, as3722_sd016_current, + ARRAY_SIZE(as3722_sd016_current)); + if (ret < 0) { + dev_err(as3722_regs->dev, + "Current range min:max = %d:%d does not support\n", + min_uA, max_uA); + return ret; + } + + switch (id) { + case AS3722_REGULATOR_ID_SD0: + reg = AS3722_OVCURRENT_REG; + mask = AS3722_OVCURRENT_SD0_TRIP_MASK; + break; + case AS3722_REGULATOR_ID_SD1: + reg = AS3722_OVCURRENT_REG; + mask = AS3722_OVCURRENT_SD1_TRIP_MASK; + break; + case AS3722_REGULATOR_ID_SD6: + reg = AS3722_OVCURRENT_DEB_REG; + mask = AS3722_OVCURRENT_SD6_TRIP_MASK; + break; + default: + return -EINVAL; + } + ret <<= ffs(mask) - 1; + val = ret & mask; + return as3722_update_bits(as3722, reg, mask, val); +} + +static bool as3722_sd0_is_low_voltage(struct as3722_regulators *as3722_regs) +{ + int err; + unsigned val; + + err = as3722_read(as3722_regs->as3722, AS3722_FUSE7_REG, &val); + if (err < 0) { + dev_err(as3722_regs->dev, "Reg 0x%02x read failed: %d\n", + AS3722_FUSE7_REG, err); + return false; + } + if (val & AS3722_FUSE7_SD0_LOW_VOLTAGE) + return true; + return false; +} + +static const struct regulator_linear_range as3722_sd2345_ranges[] = { + REGULATOR_LINEAR_RANGE(0, 0x00, 0x00, 0), + REGULATOR_LINEAR_RANGE(612500, 0x01, 0x40, 12500), + REGULATOR_LINEAR_RANGE(1425000, 0x41, 0x70, 25000), + REGULATOR_LINEAR_RANGE(2650000, 0x71, 0x7F, 50000), +}; + +static const struct regulator_ops as3722_sd016_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_current_limit = as3722_sd016_get_current_limit, + .set_current_limit = as3722_sd016_set_current_limit, + .get_mode = as3722_sd_get_mode, + .set_mode = as3722_sd_set_mode, +}; + +static const struct regulator_ops as3722_sd016_extcntrl_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_current_limit = as3722_sd016_get_current_limit, + .set_current_limit = as3722_sd016_set_current_limit, + .get_mode = as3722_sd_get_mode, + .set_mode = as3722_sd_set_mode, +}; + +static const struct regulator_ops as3722_sd2345_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .get_mode = as3722_sd_get_mode, + .set_mode = as3722_sd_set_mode, +}; + +static const struct regulator_ops as3722_sd2345_extcntrl_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .get_mode = as3722_sd_get_mode, + .set_mode = as3722_sd_set_mode, +}; + +static int as3722_extreg_init(struct as3722_regulators *as3722_regs, int id, + int ext_pwr_ctrl) +{ + int ret; + unsigned int val; + + if ((ext_pwr_ctrl < AS3722_EXT_CONTROL_ENABLE1) || + (ext_pwr_ctrl > AS3722_EXT_CONTROL_ENABLE3)) + return -EINVAL; + + val = ext_pwr_ctrl << (ffs(as3722_reg_lookup[id].sleep_ctrl_mask) - 1); + ret = as3722_update_bits(as3722_regs->as3722, + as3722_reg_lookup[id].sleep_ctrl_reg, + as3722_reg_lookup[id].sleep_ctrl_mask, val); + if (ret < 0) + dev_err(as3722_regs->dev, "Reg 0x%02x update failed: %d\n", + as3722_reg_lookup[id].sleep_ctrl_reg, ret); + return ret; +} + +static struct of_regulator_match as3722_regulator_matches[] = { + { .name = "sd0", }, + { .name = "sd1", }, + { .name = "sd2", }, + { .name = "sd3", }, + { .name = "sd4", }, + { .name = "sd5", }, + { .name = "sd6", }, + { .name = "ldo0", }, + { .name = "ldo1", }, + { .name = "ldo2", }, + { .name = "ldo3", }, + { .name = "ldo4", }, + { .name = "ldo5", }, + { .name = "ldo6", }, + { .name = "ldo7", }, + { .name = "ldo9", }, + { .name = "ldo10", }, + { .name = "ldo11", }, +}; + +static int as3722_get_regulator_dt_data(struct platform_device *pdev, + struct as3722_regulators *as3722_regs) +{ + struct device_node *np; + struct as3722_regulator_config_data *reg_config; + u32 prop; + int id; + int ret; + + np = of_get_child_by_name(pdev->dev.parent->of_node, "regulators"); + if (!np) { + dev_err(&pdev->dev, "Device is not having regulators node\n"); + return -ENODEV; + } + pdev->dev.of_node = np; + + ret = of_regulator_match(&pdev->dev, np, as3722_regulator_matches, + ARRAY_SIZE(as3722_regulator_matches)); + of_node_put(np); + if (ret < 0) { + dev_err(&pdev->dev, "Parsing of regulator node failed: %d\n", + ret); + return ret; + } + + for (id = 0; id < ARRAY_SIZE(as3722_regulator_matches); ++id) { + struct device_node *reg_node; + + reg_config = &as3722_regs->reg_config_data[id]; + reg_config->reg_init = as3722_regulator_matches[id].init_data; + reg_node = as3722_regulator_matches[id].of_node; + + if (!reg_config->reg_init || !reg_node) + continue; + + ret = of_property_read_u32(reg_node, "ams,ext-control", &prop); + if (!ret) { + if (prop < 3) + reg_config->ext_control = prop; + else + dev_warn(&pdev->dev, + "ext-control have invalid option: %u\n", + prop); + } + reg_config->enable_tracking = + of_property_read_bool(reg_node, "ams,enable-tracking"); + } + return 0; +} + +static int as3722_regulator_probe(struct platform_device *pdev) +{ + struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent); + struct as3722_regulators *as3722_regs; + struct as3722_regulator_config_data *reg_config; + struct regulator_dev *rdev; + struct regulator_config config = { }; + const struct regulator_ops *ops; + int id; + int ret; + + as3722_regs = devm_kzalloc(&pdev->dev, sizeof(*as3722_regs), + GFP_KERNEL); + if (!as3722_regs) + return -ENOMEM; + + as3722_regs->dev = &pdev->dev; + as3722_regs->as3722 = as3722; + platform_set_drvdata(pdev, as3722_regs); + + ret = as3722_get_regulator_dt_data(pdev, as3722_regs); + if (ret < 0) + return ret; + + config.dev = &pdev->dev; + config.driver_data = as3722_regs; + config.regmap = as3722->regmap; + + for (id = 0; id < AS3722_REGULATOR_ID_MAX; id++) { + reg_config = &as3722_regs->reg_config_data[id]; + + as3722_regs->desc[id].name = as3722_reg_lookup[id].name; + as3722_regs->desc[id].supply_name = as3722_reg_lookup[id].sname; + as3722_regs->desc[id].id = as3722_reg_lookup[id].regulator_id; + as3722_regs->desc[id].n_voltages = + as3722_reg_lookup[id].n_voltages; + as3722_regs->desc[id].type = REGULATOR_VOLTAGE; + as3722_regs->desc[id].owner = THIS_MODULE; + as3722_regs->desc[id].enable_reg = + as3722_reg_lookup[id].enable_reg; + as3722_regs->desc[id].enable_mask = + as3722_reg_lookup[id].enable_mask; + as3722_regs->desc[id].vsel_reg = as3722_reg_lookup[id].vsel_reg; + as3722_regs->desc[id].vsel_mask = + as3722_reg_lookup[id].vsel_mask; + switch (id) { + case AS3722_REGULATOR_ID_LDO0: + if (reg_config->ext_control) + ops = &as3722_ldo0_extcntrl_ops; + else + ops = &as3722_ldo0_ops; + as3722_regs->desc[id].min_uV = 825000; + as3722_regs->desc[id].uV_step = 25000; + as3722_regs->desc[id].linear_min_sel = 1; + as3722_regs->desc[id].enable_time = 500; + break; + case AS3722_REGULATOR_ID_LDO3: + if (reg_config->ext_control) + ops = &as3722_ldo3_extcntrl_ops; + else + ops = &as3722_ldo3_ops; + as3722_regs->desc[id].min_uV = 620000; + as3722_regs->desc[id].uV_step = 20000; + as3722_regs->desc[id].linear_min_sel = 1; + as3722_regs->desc[id].enable_time = 500; + if (reg_config->enable_tracking) { + ret = as3722_ldo3_set_tracking_mode(as3722_regs, + id, AS3722_LDO3_MODE_PMOS_TRACKING); + if (ret < 0) { + dev_err(&pdev->dev, + "LDO3 tracking failed: %d\n", + ret); + return ret; + } + } + break; + case AS3722_REGULATOR_ID_LDO6: + if (reg_config->ext_control) + ops = &as3722_ldo6_extcntrl_ops; + else + ops = &as3722_ldo6_ops; + as3722_regs->desc[id].enable_time = 500; + as3722_regs->desc[id].bypass_reg = + AS3722_LDO6_VOLTAGE_REG; + as3722_regs->desc[id].bypass_mask = + AS3722_LDO_VSEL_MASK; + as3722_regs->desc[id].bypass_val_on = + AS3722_LDO6_VSEL_BYPASS; + as3722_regs->desc[id].bypass_val_off = + AS3722_LDO6_VSEL_BYPASS; + as3722_regs->desc[id].linear_ranges = as3722_ldo_ranges; + as3722_regs->desc[id].n_linear_ranges = + ARRAY_SIZE(as3722_ldo_ranges); + break; + case AS3722_REGULATOR_ID_SD0: + case AS3722_REGULATOR_ID_SD1: + case AS3722_REGULATOR_ID_SD6: + if (reg_config->ext_control) + ops = &as3722_sd016_extcntrl_ops; + else + ops = &as3722_sd016_ops; + if (id == AS3722_REGULATOR_ID_SD0 && + as3722_sd0_is_low_voltage(as3722_regs)) { + as3722_regs->desc[id].n_voltages = + AS3722_SD0_VSEL_LOW_VOL_MAX + 1; + as3722_regs->desc[id].min_uV = 410000; + } else { + as3722_regs->desc[id].n_voltages = + AS3722_SD0_VSEL_MAX + 1, + as3722_regs->desc[id].min_uV = 610000; + } + as3722_regs->desc[id].uV_step = 10000; + as3722_regs->desc[id].linear_min_sel = 1; + as3722_regs->desc[id].enable_time = 600; + break; + case AS3722_REGULATOR_ID_SD2: + case AS3722_REGULATOR_ID_SD3: + case AS3722_REGULATOR_ID_SD4: + case AS3722_REGULATOR_ID_SD5: + if (reg_config->ext_control) + ops = &as3722_sd2345_extcntrl_ops; + else + ops = &as3722_sd2345_ops; + as3722_regs->desc[id].linear_ranges = + as3722_sd2345_ranges; + as3722_regs->desc[id].n_linear_ranges = + ARRAY_SIZE(as3722_sd2345_ranges); + break; + default: + if (reg_config->ext_control) + ops = &as3722_ldo_extcntrl_ops; + else + ops = &as3722_ldo_ops; + as3722_regs->desc[id].enable_time = 500; + as3722_regs->desc[id].linear_ranges = as3722_ldo_ranges; + as3722_regs->desc[id].n_linear_ranges = + ARRAY_SIZE(as3722_ldo_ranges); + break; + } + as3722_regs->desc[id].ops = ops; + config.init_data = reg_config->reg_init; + config.of_node = as3722_regulator_matches[id].of_node; + rdev = devm_regulator_register(&pdev->dev, + &as3722_regs->desc[id], &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(&pdev->dev, "regulator %d register failed %d\n", + id, ret); + return ret; + } + + as3722_regs->rdevs[id] = rdev; + if (reg_config->ext_control) { + ret = regulator_enable_regmap(rdev); + if (ret < 0) { + dev_err(&pdev->dev, + "Regulator %d enable failed: %d\n", + id, ret); + return ret; + } + ret = as3722_extreg_init(as3722_regs, id, + reg_config->ext_control); + if (ret < 0) { + dev_err(&pdev->dev, + "AS3722 ext control failed: %d", ret); + return ret; + } + } + } + return 0; +} + +static const struct of_device_id of_as3722_regulator_match[] = { + { .compatible = "ams,as3722-regulator", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_as3722_regulator_match); + +static struct platform_driver as3722_regulator_driver = { + .driver = { + .name = "as3722-regulator", + .of_match_table = of_as3722_regulator_match, + }, + .probe = as3722_regulator_probe, +}; + +module_platform_driver(as3722_regulator_driver); + +MODULE_ALIAS("platform:as3722-regulator"); +MODULE_DESCRIPTION("AS3722 regulator driver"); +MODULE_AUTHOR("Florian Lobmaier <florian.lobmaier@ams.com>"); +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/axp20x-regulator.c b/drivers/regulator/axp20x-regulator.c new file mode 100644 index 000000000..c9e3677ac --- /dev/null +++ b/drivers/regulator/axp20x-regulator.c @@ -0,0 +1,860 @@ +/* + * AXP20x regulators driver. + * + * Copyright (C) 2013 Carlo Caione <carlo@caione.org> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/axp20x.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +#define AXP20X_IO_ENABLED 0x03 +#define AXP20X_IO_DISABLED 0x07 + +#define AXP22X_IO_ENABLED 0x03 +#define AXP22X_IO_DISABLED 0x04 + +#define AXP20X_WORKMODE_DCDC2_MASK BIT(2) +#define AXP20X_WORKMODE_DCDC3_MASK BIT(1) +#define AXP22X_WORKMODE_DCDCX_MASK(x) BIT(x) + +#define AXP20X_FREQ_DCDC_MASK 0x0f + +#define AXP22X_MISC_N_VBUSEN_FUNC BIT(4) + +#define AXP_DESC_IO(_family, _id, _match, _supply, _min, _max, _step, _vreg, \ + _vmask, _ereg, _emask, _enable_val, _disable_val) \ + [_family##_##_id] = { \ + .name = (_match), \ + .supply_name = (_supply), \ + .of_match = of_match_ptr(_match), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = _family##_##_id, \ + .n_voltages = (((_max) - (_min)) / (_step) + 1), \ + .owner = THIS_MODULE, \ + .min_uV = (_min) * 1000, \ + .uV_step = (_step) * 1000, \ + .vsel_reg = (_vreg), \ + .vsel_mask = (_vmask), \ + .enable_reg = (_ereg), \ + .enable_mask = (_emask), \ + .enable_val = (_enable_val), \ + .disable_val = (_disable_val), \ + .ops = &axp20x_ops, \ + } + +#define AXP_DESC(_family, _id, _match, _supply, _min, _max, _step, _vreg, \ + _vmask, _ereg, _emask) \ + [_family##_##_id] = { \ + .name = (_match), \ + .supply_name = (_supply), \ + .of_match = of_match_ptr(_match), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = _family##_##_id, \ + .n_voltages = (((_max) - (_min)) / (_step) + 1), \ + .owner = THIS_MODULE, \ + .min_uV = (_min) * 1000, \ + .uV_step = (_step) * 1000, \ + .vsel_reg = (_vreg), \ + .vsel_mask = (_vmask), \ + .enable_reg = (_ereg), \ + .enable_mask = (_emask), \ + .ops = &axp20x_ops, \ + } + +#define AXP_DESC_SW(_family, _id, _match, _supply, _ereg, _emask) \ + [_family##_##_id] = { \ + .name = (_match), \ + .supply_name = (_supply), \ + .of_match = of_match_ptr(_match), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = _family##_##_id, \ + .owner = THIS_MODULE, \ + .enable_reg = (_ereg), \ + .enable_mask = (_emask), \ + .ops = &axp20x_ops_sw, \ + } + +#define AXP_DESC_FIXED(_family, _id, _match, _supply, _volt) \ + [_family##_##_id] = { \ + .name = (_match), \ + .supply_name = (_supply), \ + .of_match = of_match_ptr(_match), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = _family##_##_id, \ + .n_voltages = 1, \ + .owner = THIS_MODULE, \ + .min_uV = (_volt) * 1000, \ + .ops = &axp20x_ops_fixed \ + } + +#define AXP_DESC_RANGES(_family, _id, _match, _supply, _ranges, _n_voltages, \ + _vreg, _vmask, _ereg, _emask) \ + [_family##_##_id] = { \ + .name = (_match), \ + .supply_name = (_supply), \ + .of_match = of_match_ptr(_match), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = _family##_##_id, \ + .n_voltages = (_n_voltages), \ + .owner = THIS_MODULE, \ + .vsel_reg = (_vreg), \ + .vsel_mask = (_vmask), \ + .enable_reg = (_ereg), \ + .enable_mask = (_emask), \ + .linear_ranges = (_ranges), \ + .n_linear_ranges = ARRAY_SIZE(_ranges), \ + .ops = &axp20x_ops_range, \ + } + +static const struct regulator_ops axp20x_ops_fixed = { + .list_voltage = regulator_list_voltage_linear, +}; + +static const struct regulator_ops axp20x_ops_range = { + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct regulator_ops axp20x_ops = { + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct regulator_ops axp20x_ops_sw = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct regulator_linear_range axp20x_ldo4_ranges[] = { + REGULATOR_LINEAR_RANGE(1250000, 0x0, 0x0, 0), + REGULATOR_LINEAR_RANGE(1300000, 0x1, 0x8, 100000), + REGULATOR_LINEAR_RANGE(2500000, 0x9, 0x9, 0), + REGULATOR_LINEAR_RANGE(2700000, 0xa, 0xb, 100000), + REGULATOR_LINEAR_RANGE(3000000, 0xc, 0xf, 100000), +}; + +static const struct regulator_desc axp20x_regulators[] = { + AXP_DESC(AXP20X, DCDC2, "dcdc2", "vin2", 700, 2275, 25, + AXP20X_DCDC2_V_OUT, 0x3f, AXP20X_PWR_OUT_CTRL, 0x10), + AXP_DESC(AXP20X, DCDC3, "dcdc3", "vin3", 700, 3500, 25, + AXP20X_DCDC3_V_OUT, 0x7f, AXP20X_PWR_OUT_CTRL, 0x02), + AXP_DESC_FIXED(AXP20X, LDO1, "ldo1", "acin", 1300), + AXP_DESC(AXP20X, LDO2, "ldo2", "ldo24in", 1800, 3300, 100, + AXP20X_LDO24_V_OUT, 0xf0, AXP20X_PWR_OUT_CTRL, 0x04), + AXP_DESC(AXP20X, LDO3, "ldo3", "ldo3in", 700, 3500, 25, + AXP20X_LDO3_V_OUT, 0x7f, AXP20X_PWR_OUT_CTRL, 0x40), + AXP_DESC_RANGES(AXP20X, LDO4, "ldo4", "ldo24in", axp20x_ldo4_ranges, + 16, AXP20X_LDO24_V_OUT, 0x0f, AXP20X_PWR_OUT_CTRL, + 0x08), + AXP_DESC_IO(AXP20X, LDO5, "ldo5", "ldo5in", 1800, 3300, 100, + AXP20X_LDO5_V_OUT, 0xf0, AXP20X_GPIO0_CTRL, 0x07, + AXP20X_IO_ENABLED, AXP20X_IO_DISABLED), +}; + +static const struct regulator_desc axp22x_regulators[] = { + AXP_DESC(AXP22X, DCDC1, "dcdc1", "vin1", 1600, 3400, 100, + AXP22X_DCDC1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(1)), + AXP_DESC(AXP22X, DCDC2, "dcdc2", "vin2", 600, 1540, 20, + AXP22X_DCDC2_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(2)), + AXP_DESC(AXP22X, DCDC3, "dcdc3", "vin3", 600, 1860, 20, + AXP22X_DCDC3_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(3)), + AXP_DESC(AXP22X, DCDC4, "dcdc4", "vin4", 600, 1540, 20, + AXP22X_DCDC4_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(4)), + AXP_DESC(AXP22X, DCDC5, "dcdc5", "vin5", 1000, 2550, 50, + AXP22X_DCDC5_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(5)), + /* secondary switchable output of DCDC1 */ + AXP_DESC_SW(AXP22X, DC1SW, "dc1sw", NULL, AXP22X_PWR_OUT_CTRL2, + BIT(7)), + /* LDO regulator internally chained to DCDC5 */ + AXP_DESC(AXP22X, DC5LDO, "dc5ldo", NULL, 700, 1400, 100, + AXP22X_DC5LDO_V_OUT, 0x7, AXP22X_PWR_OUT_CTRL1, BIT(0)), + AXP_DESC(AXP22X, ALDO1, "aldo1", "aldoin", 700, 3300, 100, + AXP22X_ALDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(6)), + AXP_DESC(AXP22X, ALDO2, "aldo2", "aldoin", 700, 3300, 100, + AXP22X_ALDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(7)), + AXP_DESC(AXP22X, ALDO3, "aldo3", "aldoin", 700, 3300, 100, + AXP22X_ALDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(7)), + AXP_DESC(AXP22X, DLDO1, "dldo1", "dldoin", 700, 3300, 100, + AXP22X_DLDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(3)), + AXP_DESC(AXP22X, DLDO2, "dldo2", "dldoin", 700, 3300, 100, + AXP22X_DLDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(4)), + AXP_DESC(AXP22X, DLDO3, "dldo3", "dldoin", 700, 3300, 100, + AXP22X_DLDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(5)), + AXP_DESC(AXP22X, DLDO4, "dldo4", "dldoin", 700, 3300, 100, + AXP22X_DLDO4_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(6)), + AXP_DESC(AXP22X, ELDO1, "eldo1", "eldoin", 700, 3300, 100, + AXP22X_ELDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(0)), + AXP_DESC(AXP22X, ELDO2, "eldo2", "eldoin", 700, 3300, 100, + AXP22X_ELDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(1)), + AXP_DESC(AXP22X, ELDO3, "eldo3", "eldoin", 700, 3300, 100, + AXP22X_ELDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(2)), + /* Note the datasheet only guarantees reliable operation up to + * 3.3V, this needs to be enforced via dts provided constraints */ + AXP_DESC_IO(AXP22X, LDO_IO0, "ldo_io0", "ips", 700, 3800, 100, + AXP22X_LDO_IO0_V_OUT, 0x1f, AXP20X_GPIO0_CTRL, 0x07, + AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), + /* Note the datasheet only guarantees reliable operation up to + * 3.3V, this needs to be enforced via dts provided constraints */ + AXP_DESC_IO(AXP22X, LDO_IO1, "ldo_io1", "ips", 700, 3800, 100, + AXP22X_LDO_IO1_V_OUT, 0x1f, AXP20X_GPIO1_CTRL, 0x07, + AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), + AXP_DESC_FIXED(AXP22X, RTC_LDO, "rtc_ldo", "ips", 3000), +}; + +static const struct regulator_desc axp22x_drivevbus_regulator = { + .name = "drivevbus", + .supply_name = "drivevbus", + .of_match = of_match_ptr("drivevbus"), + .regulators_node = of_match_ptr("regulators"), + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = AXP20X_VBUS_IPSOUT_MGMT, + .enable_mask = BIT(2), + .ops = &axp20x_ops_sw, +}; + +/* DCDC ranges shared with AXP813 */ +static const struct regulator_linear_range axp803_dcdc234_ranges[] = { + REGULATOR_LINEAR_RANGE(500000, 0x0, 0x46, 10000), + REGULATOR_LINEAR_RANGE(1220000, 0x47, 0x4b, 20000), +}; + +static const struct regulator_linear_range axp803_dcdc5_ranges[] = { + REGULATOR_LINEAR_RANGE(800000, 0x0, 0x20, 10000), + REGULATOR_LINEAR_RANGE(1140000, 0x21, 0x44, 20000), +}; + +static const struct regulator_linear_range axp803_dcdc6_ranges[] = { + REGULATOR_LINEAR_RANGE(600000, 0x0, 0x32, 10000), + REGULATOR_LINEAR_RANGE(1120000, 0x33, 0x47, 20000), +}; + +/* AXP806's CLDO2 and AXP809's DLDO1 shares the same range */ +static const struct regulator_linear_range axp803_dldo2_ranges[] = { + REGULATOR_LINEAR_RANGE(700000, 0x0, 0x1a, 100000), + REGULATOR_LINEAR_RANGE(3400000, 0x1b, 0x1f, 200000), +}; + +static const struct regulator_desc axp803_regulators[] = { + AXP_DESC(AXP803, DCDC1, "dcdc1", "vin1", 1600, 3400, 100, + AXP803_DCDC1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(0)), + AXP_DESC_RANGES(AXP803, DCDC2, "dcdc2", "vin2", axp803_dcdc234_ranges, + 76, AXP803_DCDC2_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(1)), + AXP_DESC_RANGES(AXP803, DCDC3, "dcdc3", "vin3", axp803_dcdc234_ranges, + 76, AXP803_DCDC3_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(2)), + AXP_DESC_RANGES(AXP803, DCDC4, "dcdc4", "vin4", axp803_dcdc234_ranges, + 76, AXP803_DCDC4_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(3)), + AXP_DESC_RANGES(AXP803, DCDC5, "dcdc5", "vin5", axp803_dcdc5_ranges, + 68, AXP803_DCDC5_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(4)), + AXP_DESC_RANGES(AXP803, DCDC6, "dcdc6", "vin6", axp803_dcdc6_ranges, + 72, AXP803_DCDC6_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(5)), + /* secondary switchable output of DCDC1 */ + AXP_DESC_SW(AXP803, DC1SW, "dc1sw", NULL, AXP22X_PWR_OUT_CTRL2, + BIT(7)), + AXP_DESC(AXP803, ALDO1, "aldo1", "aldoin", 700, 3300, 100, + AXP22X_ALDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(5)), + AXP_DESC(AXP803, ALDO2, "aldo2", "aldoin", 700, 3300, 100, + AXP22X_ALDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(6)), + AXP_DESC(AXP803, ALDO3, "aldo3", "aldoin", 700, 3300, 100, + AXP22X_ALDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(7)), + AXP_DESC(AXP803, DLDO1, "dldo1", "dldoin", 700, 3300, 100, + AXP22X_DLDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(3)), + AXP_DESC_RANGES(AXP803, DLDO2, "dldo2", "dldoin", axp803_dldo2_ranges, + 32, AXP22X_DLDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, + BIT(4)), + AXP_DESC(AXP803, DLDO3, "dldo3", "dldoin", 700, 3300, 100, + AXP22X_DLDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(5)), + AXP_DESC(AXP803, DLDO4, "dldo4", "dldoin", 700, 3300, 100, + AXP22X_DLDO4_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(6)), + AXP_DESC(AXP803, ELDO1, "eldo1", "eldoin", 700, 1900, 50, + AXP22X_ELDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(0)), + AXP_DESC(AXP803, ELDO2, "eldo2", "eldoin", 700, 1900, 50, + AXP22X_ELDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(1)), + AXP_DESC(AXP803, ELDO3, "eldo3", "eldoin", 700, 1900, 50, + AXP22X_ELDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(2)), + AXP_DESC(AXP803, FLDO1, "fldo1", "fldoin", 700, 1450, 50, + AXP803_FLDO1_V_OUT, 0x0f, AXP22X_PWR_OUT_CTRL3, BIT(2)), + AXP_DESC(AXP803, FLDO2, "fldo2", "fldoin", 700, 1450, 50, + AXP803_FLDO2_V_OUT, 0x0f, AXP22X_PWR_OUT_CTRL3, BIT(3)), + AXP_DESC_IO(AXP803, LDO_IO0, "ldo-io0", "ips", 700, 3300, 100, + AXP22X_LDO_IO0_V_OUT, 0x1f, AXP20X_GPIO0_CTRL, 0x07, + AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), + AXP_DESC_IO(AXP803, LDO_IO1, "ldo-io1", "ips", 700, 3300, 100, + AXP22X_LDO_IO1_V_OUT, 0x1f, AXP20X_GPIO1_CTRL, 0x07, + AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), + AXP_DESC_FIXED(AXP803, RTC_LDO, "rtc-ldo", "ips", 3000), +}; + +static const struct regulator_linear_range axp806_dcdca_ranges[] = { + REGULATOR_LINEAR_RANGE(600000, 0x0, 0x32, 10000), + REGULATOR_LINEAR_RANGE(1120000, 0x33, 0x47, 20000), +}; + +static const struct regulator_linear_range axp806_dcdcd_ranges[] = { + REGULATOR_LINEAR_RANGE(600000, 0x0, 0x2d, 20000), + REGULATOR_LINEAR_RANGE(1600000, 0x2e, 0x3f, 100000), +}; + +static const struct regulator_desc axp806_regulators[] = { + AXP_DESC_RANGES(AXP806, DCDCA, "dcdca", "vina", axp806_dcdca_ranges, + 72, AXP806_DCDCA_V_CTRL, 0x7f, AXP806_PWR_OUT_CTRL1, + BIT(0)), + AXP_DESC(AXP806, DCDCB, "dcdcb", "vinb", 1000, 2550, 50, + AXP806_DCDCB_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL1, BIT(1)), + AXP_DESC_RANGES(AXP806, DCDCC, "dcdcc", "vinc", axp806_dcdca_ranges, + 72, AXP806_DCDCC_V_CTRL, 0x7f, AXP806_PWR_OUT_CTRL1, + BIT(2)), + AXP_DESC_RANGES(AXP806, DCDCD, "dcdcd", "vind", axp806_dcdcd_ranges, + 64, AXP806_DCDCD_V_CTRL, 0x3f, AXP806_PWR_OUT_CTRL1, + BIT(3)), + AXP_DESC(AXP806, DCDCE, "dcdce", "vine", 1100, 3400, 100, + AXP806_DCDCE_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL1, BIT(4)), + AXP_DESC(AXP806, ALDO1, "aldo1", "aldoin", 700, 3300, 100, + AXP806_ALDO1_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL1, BIT(5)), + AXP_DESC(AXP806, ALDO2, "aldo2", "aldoin", 700, 3400, 100, + AXP806_ALDO2_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL1, BIT(6)), + AXP_DESC(AXP806, ALDO3, "aldo3", "aldoin", 700, 3300, 100, + AXP806_ALDO3_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL1, BIT(7)), + AXP_DESC(AXP806, BLDO1, "bldo1", "bldoin", 700, 1900, 100, + AXP806_BLDO1_V_CTRL, 0x0f, AXP806_PWR_OUT_CTRL2, BIT(0)), + AXP_DESC(AXP806, BLDO2, "bldo2", "bldoin", 700, 1900, 100, + AXP806_BLDO2_V_CTRL, 0x0f, AXP806_PWR_OUT_CTRL2, BIT(1)), + AXP_DESC(AXP806, BLDO3, "bldo3", "bldoin", 700, 1900, 100, + AXP806_BLDO3_V_CTRL, 0x0f, AXP806_PWR_OUT_CTRL2, BIT(2)), + AXP_DESC(AXP806, BLDO4, "bldo4", "bldoin", 700, 1900, 100, + AXP806_BLDO4_V_CTRL, 0x0f, AXP806_PWR_OUT_CTRL2, BIT(3)), + AXP_DESC(AXP806, CLDO1, "cldo1", "cldoin", 700, 3300, 100, + AXP806_CLDO1_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL2, BIT(4)), + AXP_DESC_RANGES(AXP806, CLDO2, "cldo2", "cldoin", axp803_dldo2_ranges, + 32, AXP806_CLDO2_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL2, + BIT(5)), + AXP_DESC(AXP806, CLDO3, "cldo3", "cldoin", 700, 3300, 100, + AXP806_CLDO3_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL2, BIT(6)), + AXP_DESC_SW(AXP806, SW, "sw", "swin", AXP806_PWR_OUT_CTRL2, BIT(7)), +}; + +static const struct regulator_linear_range axp809_dcdc4_ranges[] = { + REGULATOR_LINEAR_RANGE(600000, 0x0, 0x2f, 20000), + REGULATOR_LINEAR_RANGE(1800000, 0x30, 0x38, 100000), +}; + +static const struct regulator_desc axp809_regulators[] = { + AXP_DESC(AXP809, DCDC1, "dcdc1", "vin1", 1600, 3400, 100, + AXP22X_DCDC1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(1)), + AXP_DESC(AXP809, DCDC2, "dcdc2", "vin2", 600, 1540, 20, + AXP22X_DCDC2_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(2)), + AXP_DESC(AXP809, DCDC3, "dcdc3", "vin3", 600, 1860, 20, + AXP22X_DCDC3_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(3)), + AXP_DESC_RANGES(AXP809, DCDC4, "dcdc4", "vin4", axp809_dcdc4_ranges, + 57, AXP22X_DCDC4_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, + BIT(4)), + AXP_DESC(AXP809, DCDC5, "dcdc5", "vin5", 1000, 2550, 50, + AXP22X_DCDC5_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(5)), + /* secondary switchable output of DCDC1 */ + AXP_DESC_SW(AXP809, DC1SW, "dc1sw", NULL, AXP22X_PWR_OUT_CTRL2, + BIT(7)), + /* LDO regulator internally chained to DCDC5 */ + AXP_DESC(AXP809, DC5LDO, "dc5ldo", NULL, 700, 1400, 100, + AXP22X_DC5LDO_V_OUT, 0x7, AXP22X_PWR_OUT_CTRL1, BIT(0)), + AXP_DESC(AXP809, ALDO1, "aldo1", "aldoin", 700, 3300, 100, + AXP22X_ALDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(6)), + AXP_DESC(AXP809, ALDO2, "aldo2", "aldoin", 700, 3300, 100, + AXP22X_ALDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(7)), + AXP_DESC(AXP809, ALDO3, "aldo3", "aldoin", 700, 3300, 100, + AXP22X_ALDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(5)), + AXP_DESC_RANGES(AXP809, DLDO1, "dldo1", "dldoin", axp803_dldo2_ranges, + 32, AXP22X_DLDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, + BIT(3)), + AXP_DESC(AXP809, DLDO2, "dldo2", "dldoin", 700, 3300, 100, + AXP22X_DLDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(4)), + AXP_DESC(AXP809, ELDO1, "eldo1", "eldoin", 700, 3300, 100, + AXP22X_ELDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(0)), + AXP_DESC(AXP809, ELDO2, "eldo2", "eldoin", 700, 3300, 100, + AXP22X_ELDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(1)), + AXP_DESC(AXP809, ELDO3, "eldo3", "eldoin", 700, 3300, 100, + AXP22X_ELDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(2)), + /* + * Note the datasheet only guarantees reliable operation up to + * 3.3V, this needs to be enforced via dts provided constraints + */ + AXP_DESC_IO(AXP809, LDO_IO0, "ldo_io0", "ips", 700, 3800, 100, + AXP22X_LDO_IO0_V_OUT, 0x1f, AXP20X_GPIO0_CTRL, 0x07, + AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), + /* + * Note the datasheet only guarantees reliable operation up to + * 3.3V, this needs to be enforced via dts provided constraints + */ + AXP_DESC_IO(AXP809, LDO_IO1, "ldo_io1", "ips", 700, 3800, 100, + AXP22X_LDO_IO1_V_OUT, 0x1f, AXP20X_GPIO1_CTRL, 0x07, + AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), + AXP_DESC_FIXED(AXP809, RTC_LDO, "rtc_ldo", "ips", 1800), + AXP_DESC_SW(AXP809, SW, "sw", "swin", AXP22X_PWR_OUT_CTRL2, BIT(6)), +}; + +static const struct regulator_desc axp813_regulators[] = { + AXP_DESC(AXP813, DCDC1, "dcdc1", "vin1", 1600, 3400, 100, + AXP803_DCDC1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(0)), + AXP_DESC_RANGES(AXP813, DCDC2, "dcdc2", "vin2", axp803_dcdc234_ranges, + 76, AXP803_DCDC2_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(1)), + AXP_DESC_RANGES(AXP813, DCDC3, "dcdc3", "vin3", axp803_dcdc234_ranges, + 76, AXP803_DCDC3_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(2)), + AXP_DESC_RANGES(AXP813, DCDC4, "dcdc4", "vin4", axp803_dcdc234_ranges, + 76, AXP803_DCDC4_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(3)), + AXP_DESC_RANGES(AXP813, DCDC5, "dcdc5", "vin5", axp803_dcdc5_ranges, + 68, AXP803_DCDC5_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(4)), + AXP_DESC_RANGES(AXP813, DCDC6, "dcdc6", "vin6", axp803_dcdc6_ranges, + 72, AXP803_DCDC6_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(5)), + AXP_DESC_RANGES(AXP813, DCDC7, "dcdc7", "vin7", axp803_dcdc6_ranges, + 72, AXP813_DCDC7_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, + BIT(6)), + AXP_DESC(AXP813, ALDO1, "aldo1", "aldoin", 700, 3300, 100, + AXP22X_ALDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(5)), + AXP_DESC(AXP813, ALDO2, "aldo2", "aldoin", 700, 3300, 100, + AXP22X_ALDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(6)), + AXP_DESC(AXP813, ALDO3, "aldo3", "aldoin", 700, 3300, 100, + AXP22X_ALDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(7)), + AXP_DESC(AXP813, DLDO1, "dldo1", "dldoin", 700, 3300, 100, + AXP22X_DLDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(3)), + AXP_DESC_RANGES(AXP813, DLDO2, "dldo2", "dldoin", axp803_dldo2_ranges, + 32, AXP22X_DLDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, + BIT(4)), + AXP_DESC(AXP813, DLDO3, "dldo3", "dldoin", 700, 3300, 100, + AXP22X_DLDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(5)), + AXP_DESC(AXP813, DLDO4, "dldo4", "dldoin", 700, 3300, 100, + AXP22X_DLDO4_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(6)), + AXP_DESC(AXP813, ELDO1, "eldo1", "eldoin", 700, 1900, 50, + AXP22X_ELDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(0)), + AXP_DESC(AXP813, ELDO2, "eldo2", "eldoin", 700, 1900, 50, + AXP22X_ELDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(1)), + AXP_DESC(AXP813, ELDO3, "eldo3", "eldoin", 700, 1900, 50, + AXP22X_ELDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(2)), + /* to do / check ... */ + AXP_DESC(AXP813, FLDO1, "fldo1", "fldoin", 700, 1450, 50, + AXP803_FLDO1_V_OUT, 0x0f, AXP22X_PWR_OUT_CTRL3, BIT(2)), + AXP_DESC(AXP813, FLDO2, "fldo2", "fldoin", 700, 1450, 50, + AXP803_FLDO2_V_OUT, 0x0f, AXP22X_PWR_OUT_CTRL3, BIT(3)), + /* + * TODO: FLDO3 = {DCDC5, FLDOIN} / 2 + * + * This means FLDO3 effectively switches supplies at runtime, + * something the regulator subsystem does not support. + */ + AXP_DESC_FIXED(AXP813, RTC_LDO, "rtc-ldo", "ips", 1800), + AXP_DESC_IO(AXP813, LDO_IO0, "ldo-io0", "ips", 700, 3300, 100, + AXP22X_LDO_IO0_V_OUT, 0x1f, AXP20X_GPIO0_CTRL, 0x07, + AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), + AXP_DESC_IO(AXP813, LDO_IO1, "ldo-io1", "ips", 700, 3300, 100, + AXP22X_LDO_IO1_V_OUT, 0x1f, AXP20X_GPIO1_CTRL, 0x07, + AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), + AXP_DESC_SW(AXP813, SW, "sw", "swin", AXP22X_PWR_OUT_CTRL2, BIT(7)), +}; + +static int axp20x_set_dcdc_freq(struct platform_device *pdev, u32 dcdcfreq) +{ + struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + unsigned int reg = AXP20X_DCDC_FREQ; + u32 min, max, def, step; + + switch (axp20x->variant) { + case AXP202_ID: + case AXP209_ID: + min = 750; + max = 1875; + def = 1500; + step = 75; + break; + case AXP803_ID: + case AXP813_ID: + /* + * AXP803/AXP813 DCDC work frequency setting has the same + * range and step as AXP22X, but at a different register. + * Fall through to the check below. + * (See include/linux/mfd/axp20x.h) + */ + reg = AXP803_DCDC_FREQ_CTRL; + case AXP806_ID: + /* + * AXP806 also have DCDC work frequency setting register at a + * different position. + */ + if (axp20x->variant == AXP806_ID) + reg = AXP806_DCDC_FREQ_CTRL; + case AXP221_ID: + case AXP223_ID: + case AXP809_ID: + min = 1800; + max = 4050; + def = 3000; + step = 150; + break; + default: + dev_err(&pdev->dev, + "Setting DCDC frequency for unsupported AXP variant\n"); + return -EINVAL; + } + + if (dcdcfreq == 0) + dcdcfreq = def; + + if (dcdcfreq < min) { + dcdcfreq = min; + dev_warn(&pdev->dev, "DCDC frequency too low. Set to %ukHz\n", + min); + } + + if (dcdcfreq > max) { + dcdcfreq = max; + dev_warn(&pdev->dev, "DCDC frequency too high. Set to %ukHz\n", + max); + } + + dcdcfreq = (dcdcfreq - min) / step; + + return regmap_update_bits(axp20x->regmap, reg, + AXP20X_FREQ_DCDC_MASK, dcdcfreq); +} + +static int axp20x_regulator_parse_dt(struct platform_device *pdev) +{ + struct device_node *np, *regulators; + int ret = 0; + u32 dcdcfreq = 0; + + np = of_node_get(pdev->dev.parent->of_node); + if (!np) + return 0; + + regulators = of_get_child_by_name(np, "regulators"); + if (!regulators) { + dev_warn(&pdev->dev, "regulators node not found\n"); + } else { + of_property_read_u32(regulators, "x-powers,dcdc-freq", &dcdcfreq); + ret = axp20x_set_dcdc_freq(pdev, dcdcfreq); + if (ret < 0) { + dev_err(&pdev->dev, "Error setting dcdc frequency: %d\n", ret); + } + of_node_put(regulators); + } + + of_node_put(np); + return ret; +} + +static int axp20x_set_dcdc_workmode(struct regulator_dev *rdev, int id, u32 workmode) +{ + struct axp20x_dev *axp20x = rdev_get_drvdata(rdev); + unsigned int reg = AXP20X_DCDC_MODE; + unsigned int mask; + + switch (axp20x->variant) { + case AXP202_ID: + case AXP209_ID: + if ((id != AXP20X_DCDC2) && (id != AXP20X_DCDC3)) + return -EINVAL; + + mask = AXP20X_WORKMODE_DCDC2_MASK; + if (id == AXP20X_DCDC3) + mask = AXP20X_WORKMODE_DCDC3_MASK; + + workmode <<= ffs(mask) - 1; + break; + + case AXP806_ID: + reg = AXP806_DCDC_MODE_CTRL2; + /* + * AXP806 DCDC regulator IDs have the same range as AXP22X. + * Fall through to the check below. + * (See include/linux/mfd/axp20x.h) + */ + case AXP221_ID: + case AXP223_ID: + case AXP809_ID: + if (id < AXP22X_DCDC1 || id > AXP22X_DCDC5) + return -EINVAL; + + mask = AXP22X_WORKMODE_DCDCX_MASK(id - AXP22X_DCDC1); + workmode <<= id - AXP22X_DCDC1; + break; + + case AXP803_ID: + if (id < AXP803_DCDC1 || id > AXP803_DCDC6) + return -EINVAL; + + mask = AXP22X_WORKMODE_DCDCX_MASK(id - AXP803_DCDC1); + workmode <<= id - AXP803_DCDC1; + break; + + case AXP813_ID: + if (id < AXP813_DCDC1 || id > AXP813_DCDC7) + return -EINVAL; + + mask = AXP22X_WORKMODE_DCDCX_MASK(id - AXP813_DCDC1); + workmode <<= id - AXP813_DCDC1; + break; + + default: + /* should not happen */ + WARN_ON(1); + return -EINVAL; + } + + return regmap_update_bits(rdev->regmap, reg, mask, workmode); +} + +/* + * This function checks whether a regulator is part of a poly-phase + * output setup based on the registers settings. Returns true if it is. + */ +static bool axp20x_is_polyphase_slave(struct axp20x_dev *axp20x, int id) +{ + u32 reg = 0; + + /* + * Currently in our supported AXP variants, only AXP803, AXP806, + * and AXP813 have polyphase regulators. + */ + switch (axp20x->variant) { + case AXP803_ID: + case AXP813_ID: + regmap_read(axp20x->regmap, AXP803_POLYPHASE_CTRL, ®); + + switch (id) { + case AXP803_DCDC3: + return !!(reg & BIT(6)); + case AXP803_DCDC6: + return !!(reg & BIT(5)); + } + break; + + case AXP806_ID: + regmap_read(axp20x->regmap, AXP806_DCDC_MODE_CTRL2, ®); + + switch (id) { + case AXP806_DCDCB: + return (((reg & GENMASK(7, 6)) == BIT(6)) || + ((reg & GENMASK(7, 6)) == BIT(7))); + case AXP806_DCDCC: + return ((reg & GENMASK(7, 6)) == BIT(7)); + case AXP806_DCDCE: + return !!(reg & BIT(5)); + } + break; + + default: + return false; + } + + return false; +} + +static int axp20x_regulator_probe(struct platform_device *pdev) +{ + struct regulator_dev *rdev; + struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + const struct regulator_desc *regulators; + struct regulator_config config = { + .dev = pdev->dev.parent, + .regmap = axp20x->regmap, + .driver_data = axp20x, + }; + int ret, i, nregulators; + u32 workmode; + const char *dcdc1_name = axp22x_regulators[AXP22X_DCDC1].name; + const char *dcdc5_name = axp22x_regulators[AXP22X_DCDC5].name; + bool drivevbus = false; + + switch (axp20x->variant) { + case AXP202_ID: + case AXP209_ID: + regulators = axp20x_regulators; + nregulators = AXP20X_REG_ID_MAX; + break; + case AXP221_ID: + case AXP223_ID: + regulators = axp22x_regulators; + nregulators = AXP22X_REG_ID_MAX; + drivevbus = of_property_read_bool(pdev->dev.parent->of_node, + "x-powers,drive-vbus-en"); + break; + case AXP803_ID: + regulators = axp803_regulators; + nregulators = AXP803_REG_ID_MAX; + drivevbus = of_property_read_bool(pdev->dev.parent->of_node, + "x-powers,drive-vbus-en"); + break; + case AXP806_ID: + regulators = axp806_regulators; + nregulators = AXP806_REG_ID_MAX; + break; + case AXP809_ID: + regulators = axp809_regulators; + nregulators = AXP809_REG_ID_MAX; + break; + case AXP813_ID: + regulators = axp813_regulators; + nregulators = AXP813_REG_ID_MAX; + drivevbus = of_property_read_bool(pdev->dev.parent->of_node, + "x-powers,drive-vbus-en"); + break; + default: + dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n", + axp20x->variant); + return -EINVAL; + } + + /* This only sets the dcdc freq. Ignore any errors */ + axp20x_regulator_parse_dt(pdev); + + for (i = 0; i < nregulators; i++) { + const struct regulator_desc *desc = ®ulators[i]; + struct regulator_desc *new_desc; + + /* + * If this regulator is a slave in a poly-phase setup, + * skip it, as its controls are bound to the master + * regulator and won't work. + */ + if (axp20x_is_polyphase_slave(axp20x, i)) + continue; + + /* Support for AXP813's FLDO3 is not implemented */ + if (axp20x->variant == AXP813_ID && i == AXP813_FLDO3) + continue; + + /* + * Regulators DC1SW and DC5LDO are connected internally, + * so we have to handle their supply names separately. + * + * We always register the regulators in proper sequence, + * so the supply names are correctly read. See the last + * part of this loop to see where we save the DT defined + * name. + */ + if ((regulators == axp22x_regulators && i == AXP22X_DC1SW) || + (regulators == axp803_regulators && i == AXP803_DC1SW) || + (regulators == axp809_regulators && i == AXP809_DC1SW)) { + new_desc = devm_kzalloc(&pdev->dev, sizeof(*desc), + GFP_KERNEL); + if (!new_desc) + return -ENOMEM; + + *new_desc = regulators[i]; + new_desc->supply_name = dcdc1_name; + desc = new_desc; + } + + if ((regulators == axp22x_regulators && i == AXP22X_DC5LDO) || + (regulators == axp809_regulators && i == AXP809_DC5LDO)) { + new_desc = devm_kzalloc(&pdev->dev, sizeof(*desc), + GFP_KERNEL); + if (!new_desc) + return -ENOMEM; + + *new_desc = regulators[i]; + new_desc->supply_name = dcdc5_name; + desc = new_desc; + } + + rdev = devm_regulator_register(&pdev->dev, desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "Failed to register %s\n", + regulators[i].name); + + return PTR_ERR(rdev); + } + + ret = of_property_read_u32(rdev->dev.of_node, + "x-powers,dcdc-workmode", + &workmode); + if (!ret) { + if (axp20x_set_dcdc_workmode(rdev, i, workmode)) + dev_err(&pdev->dev, "Failed to set workmode on %s\n", + rdev->desc->name); + } + + /* + * Save AXP22X DCDC1 / DCDC5 regulator names for later. + */ + if ((regulators == axp22x_regulators && i == AXP22X_DCDC1) || + (regulators == axp809_regulators && i == AXP809_DCDC1)) + of_property_read_string(rdev->dev.of_node, + "regulator-name", + &dcdc1_name); + + if ((regulators == axp22x_regulators && i == AXP22X_DCDC5) || + (regulators == axp809_regulators && i == AXP809_DCDC5)) + of_property_read_string(rdev->dev.of_node, + "regulator-name", + &dcdc5_name); + } + + if (drivevbus) { + /* Change N_VBUSEN sense pin to DRIVEVBUS output pin */ + regmap_update_bits(axp20x->regmap, AXP20X_OVER_TMP, + AXP22X_MISC_N_VBUSEN_FUNC, 0); + rdev = devm_regulator_register(&pdev->dev, + &axp22x_drivevbus_regulator, + &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "Failed to register drivevbus\n"); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static struct platform_driver axp20x_regulator_driver = { + .probe = axp20x_regulator_probe, + .driver = { + .name = "axp20x-regulator", + }, +}; + +module_platform_driver(axp20x_regulator_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); +MODULE_DESCRIPTION("Regulator Driver for AXP20X PMIC"); +MODULE_ALIAS("platform:axp20x-regulator"); diff --git a/drivers/regulator/bcm590xx-regulator.c b/drivers/regulator/bcm590xx-regulator.c new file mode 100644 index 000000000..92d6d7b10 --- /dev/null +++ b/drivers/regulator/bcm590xx-regulator.c @@ -0,0 +1,466 @@ +/* + * Broadcom BCM590xx regulator driver + * + * Copyright 2014 Linaro Limited + * Author: Matt Porter <mporter@linaro.org> + * + * 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/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mfd/bcm590xx.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/slab.h> + +/* I2C slave 0 registers */ +#define BCM590XX_RFLDOPMCTRL1 0x60 +#define BCM590XX_IOSR1PMCTRL1 0x7a +#define BCM590XX_IOSR2PMCTRL1 0x7c +#define BCM590XX_CSRPMCTRL1 0x7e +#define BCM590XX_SDSR1PMCTRL1 0x82 +#define BCM590XX_SDSR2PMCTRL1 0x86 +#define BCM590XX_MSRPMCTRL1 0x8a +#define BCM590XX_VSRPMCTRL1 0x8e +#define BCM590XX_RFLDOCTRL 0x96 +#define BCM590XX_CSRVOUT1 0xc0 + +/* I2C slave 1 registers */ +#define BCM590XX_GPLDO5PMCTRL1 0x16 +#define BCM590XX_GPLDO6PMCTRL1 0x18 +#define BCM590XX_GPLDO1CTRL 0x1a +#define BCM590XX_GPLDO2CTRL 0x1b +#define BCM590XX_GPLDO3CTRL 0x1c +#define BCM590XX_GPLDO4CTRL 0x1d +#define BCM590XX_GPLDO5CTRL 0x1e +#define BCM590XX_GPLDO6CTRL 0x1f +#define BCM590XX_OTG_CTRL 0x40 +#define BCM590XX_GPLDO1PMCTRL1 0x57 +#define BCM590XX_GPLDO2PMCTRL1 0x59 +#define BCM590XX_GPLDO3PMCTRL1 0x5b +#define BCM590XX_GPLDO4PMCTRL1 0x5d + +#define BCM590XX_REG_ENABLE BIT(7) +#define BCM590XX_VBUS_ENABLE BIT(2) +#define BCM590XX_LDO_VSEL_MASK GENMASK(5, 3) +#define BCM590XX_SR_VSEL_MASK GENMASK(5, 0) + +/* + * RFLDO to VSR regulators are + * accessed via I2C slave 0 + */ + +/* LDO regulator IDs */ +#define BCM590XX_REG_RFLDO 0 +#define BCM590XX_REG_CAMLDO1 1 +#define BCM590XX_REG_CAMLDO2 2 +#define BCM590XX_REG_SIMLDO1 3 +#define BCM590XX_REG_SIMLDO2 4 +#define BCM590XX_REG_SDLDO 5 +#define BCM590XX_REG_SDXLDO 6 +#define BCM590XX_REG_MMCLDO1 7 +#define BCM590XX_REG_MMCLDO2 8 +#define BCM590XX_REG_AUDLDO 9 +#define BCM590XX_REG_MICLDO 10 +#define BCM590XX_REG_USBLDO 11 +#define BCM590XX_REG_VIBLDO 12 + +/* DCDC regulator IDs */ +#define BCM590XX_REG_CSR 13 +#define BCM590XX_REG_IOSR1 14 +#define BCM590XX_REG_IOSR2 15 +#define BCM590XX_REG_MSR 16 +#define BCM590XX_REG_SDSR1 17 +#define BCM590XX_REG_SDSR2 18 +#define BCM590XX_REG_VSR 19 + +/* + * GPLDO1 to VBUS regulators are + * accessed via I2C slave 1 + */ + +#define BCM590XX_REG_GPLDO1 20 +#define BCM590XX_REG_GPLDO2 21 +#define BCM590XX_REG_GPLDO3 22 +#define BCM590XX_REG_GPLDO4 23 +#define BCM590XX_REG_GPLDO5 24 +#define BCM590XX_REG_GPLDO6 25 +#define BCM590XX_REG_VBUS 26 + +#define BCM590XX_NUM_REGS 27 + +#define BCM590XX_REG_IS_LDO(n) (n < BCM590XX_REG_CSR) +#define BCM590XX_REG_IS_GPLDO(n) \ + ((n > BCM590XX_REG_VSR) && (n < BCM590XX_REG_VBUS)) +#define BCM590XX_REG_IS_VBUS(n) (n == BCM590XX_REG_VBUS) + +struct bcm590xx_board { + struct regulator_init_data *bcm590xx_pmu_init_data[BCM590XX_NUM_REGS]; +}; + +/* LDO group A: supported voltages in microvolts */ +static const unsigned int ldo_a_table[] = { + 1200000, 1800000, 2500000, 2700000, 2800000, + 2900000, 3000000, 3300000, +}; + +/* LDO group C: supported voltages in microvolts */ +static const unsigned int ldo_c_table[] = { + 3100000, 1800000, 2500000, 2700000, 2800000, + 2900000, 3000000, 3300000, +}; + +static const unsigned int ldo_vbus[] = { + 5000000, +}; + +/* DCDC group CSR: supported voltages in microvolts */ +static const struct regulator_linear_range dcdc_csr_ranges[] = { + REGULATOR_LINEAR_RANGE(860000, 2, 50, 10000), + REGULATOR_LINEAR_RANGE(1360000, 51, 55, 20000), + REGULATOR_LINEAR_RANGE(900000, 56, 63, 0), +}; + +/* DCDC group IOSR1: supported voltages in microvolts */ +static const struct regulator_linear_range dcdc_iosr1_ranges[] = { + REGULATOR_LINEAR_RANGE(860000, 2, 51, 10000), + REGULATOR_LINEAR_RANGE(1500000, 52, 52, 0), + REGULATOR_LINEAR_RANGE(1800000, 53, 53, 0), + REGULATOR_LINEAR_RANGE(900000, 54, 63, 0), +}; + +/* DCDC group SDSR1: supported voltages in microvolts */ +static const struct regulator_linear_range dcdc_sdsr1_ranges[] = { + REGULATOR_LINEAR_RANGE(860000, 2, 50, 10000), + REGULATOR_LINEAR_RANGE(1340000, 51, 51, 0), + REGULATOR_LINEAR_RANGE(900000, 52, 63, 0), +}; + +struct bcm590xx_info { + const char *name; + const char *vin_name; + u8 n_voltages; + const unsigned int *volt_table; + u8 n_linear_ranges; + const struct regulator_linear_range *linear_ranges; +}; + +#define BCM590XX_REG_TABLE(_name, _table) \ + { \ + .name = #_name, \ + .n_voltages = ARRAY_SIZE(_table), \ + .volt_table = _table, \ + } + +#define BCM590XX_REG_RANGES(_name, _ranges) \ + { \ + .name = #_name, \ + .n_voltages = 64, \ + .n_linear_ranges = ARRAY_SIZE(_ranges), \ + .linear_ranges = _ranges, \ + } + +static struct bcm590xx_info bcm590xx_regs[] = { + BCM590XX_REG_TABLE(rfldo, ldo_a_table), + BCM590XX_REG_TABLE(camldo1, ldo_c_table), + BCM590XX_REG_TABLE(camldo2, ldo_c_table), + BCM590XX_REG_TABLE(simldo1, ldo_a_table), + BCM590XX_REG_TABLE(simldo2, ldo_a_table), + BCM590XX_REG_TABLE(sdldo, ldo_c_table), + BCM590XX_REG_TABLE(sdxldo, ldo_a_table), + BCM590XX_REG_TABLE(mmcldo1, ldo_a_table), + BCM590XX_REG_TABLE(mmcldo2, ldo_a_table), + BCM590XX_REG_TABLE(audldo, ldo_a_table), + BCM590XX_REG_TABLE(micldo, ldo_a_table), + BCM590XX_REG_TABLE(usbldo, ldo_a_table), + BCM590XX_REG_TABLE(vibldo, ldo_c_table), + BCM590XX_REG_RANGES(csr, dcdc_csr_ranges), + BCM590XX_REG_RANGES(iosr1, dcdc_iosr1_ranges), + BCM590XX_REG_RANGES(iosr2, dcdc_iosr1_ranges), + BCM590XX_REG_RANGES(msr, dcdc_iosr1_ranges), + BCM590XX_REG_RANGES(sdsr1, dcdc_sdsr1_ranges), + BCM590XX_REG_RANGES(sdsr2, dcdc_iosr1_ranges), + BCM590XX_REG_RANGES(vsr, dcdc_iosr1_ranges), + BCM590XX_REG_TABLE(gpldo1, ldo_a_table), + BCM590XX_REG_TABLE(gpldo2, ldo_a_table), + BCM590XX_REG_TABLE(gpldo3, ldo_a_table), + BCM590XX_REG_TABLE(gpldo4, ldo_a_table), + BCM590XX_REG_TABLE(gpldo5, ldo_a_table), + BCM590XX_REG_TABLE(gpldo6, ldo_a_table), + BCM590XX_REG_TABLE(vbus, ldo_vbus), +}; + +struct bcm590xx_reg { + struct regulator_desc *desc; + struct bcm590xx *mfd; +}; + +static int bcm590xx_get_vsel_register(int id) +{ + if (BCM590XX_REG_IS_LDO(id)) + return BCM590XX_RFLDOCTRL + id; + else if (BCM590XX_REG_IS_GPLDO(id)) + return BCM590XX_GPLDO1CTRL + id; + else + return BCM590XX_CSRVOUT1 + (id - BCM590XX_REG_CSR) * 3; +} + +static int bcm590xx_get_enable_register(int id) +{ + int reg = 0; + + if (BCM590XX_REG_IS_LDO(id)) + reg = BCM590XX_RFLDOPMCTRL1 + id * 2; + else if (BCM590XX_REG_IS_GPLDO(id)) + reg = BCM590XX_GPLDO1PMCTRL1 + id * 2; + else + switch (id) { + case BCM590XX_REG_CSR: + reg = BCM590XX_CSRPMCTRL1; + break; + case BCM590XX_REG_IOSR1: + reg = BCM590XX_IOSR1PMCTRL1; + break; + case BCM590XX_REG_IOSR2: + reg = BCM590XX_IOSR2PMCTRL1; + break; + case BCM590XX_REG_MSR: + reg = BCM590XX_MSRPMCTRL1; + break; + case BCM590XX_REG_SDSR1: + reg = BCM590XX_SDSR1PMCTRL1; + break; + case BCM590XX_REG_SDSR2: + reg = BCM590XX_SDSR2PMCTRL1; + break; + case BCM590XX_REG_VBUS: + reg = BCM590XX_OTG_CTRL; + } + + + return reg; +} + +static const struct regulator_ops bcm590xx_ops_ldo = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, +}; + +static const struct regulator_ops bcm590xx_ops_dcdc = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, +}; + +static const struct regulator_ops bcm590xx_ops_vbus = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +#define BCM590XX_MATCH(_name, _id) \ + { \ + .name = #_name, \ + .driver_data = (void *)&bcm590xx_regs[BCM590XX_REG_##_id], \ + } + +static struct of_regulator_match bcm590xx_matches[] = { + BCM590XX_MATCH(rfldo, RFLDO), + BCM590XX_MATCH(camldo1, CAMLDO1), + BCM590XX_MATCH(camldo2, CAMLDO2), + BCM590XX_MATCH(simldo1, SIMLDO1), + BCM590XX_MATCH(simldo2, SIMLDO2), + BCM590XX_MATCH(sdldo, SDLDO), + BCM590XX_MATCH(sdxldo, SDXLDO), + BCM590XX_MATCH(mmcldo1, MMCLDO1), + BCM590XX_MATCH(mmcldo2, MMCLDO2), + BCM590XX_MATCH(audldo, AUDLDO), + BCM590XX_MATCH(micldo, MICLDO), + BCM590XX_MATCH(usbldo, USBLDO), + BCM590XX_MATCH(vibldo, VIBLDO), + BCM590XX_MATCH(csr, CSR), + BCM590XX_MATCH(iosr1, IOSR1), + BCM590XX_MATCH(iosr2, IOSR2), + BCM590XX_MATCH(msr, MSR), + BCM590XX_MATCH(sdsr1, SDSR1), + BCM590XX_MATCH(sdsr2, SDSR2), + BCM590XX_MATCH(vsr, VSR), + BCM590XX_MATCH(gpldo1, GPLDO1), + BCM590XX_MATCH(gpldo2, GPLDO2), + BCM590XX_MATCH(gpldo3, GPLDO3), + BCM590XX_MATCH(gpldo4, GPLDO4), + BCM590XX_MATCH(gpldo5, GPLDO5), + BCM590XX_MATCH(gpldo6, GPLDO6), + BCM590XX_MATCH(vbus, VBUS), +}; + +static struct bcm590xx_board *bcm590xx_parse_dt_reg_data( + struct platform_device *pdev, + struct of_regulator_match **bcm590xx_reg_matches) +{ + struct bcm590xx_board *data; + struct device_node *np = pdev->dev.parent->of_node; + struct device_node *regulators; + struct of_regulator_match *matches = bcm590xx_matches; + int count = ARRAY_SIZE(bcm590xx_matches); + int idx = 0; + int ret; + + if (!np) { + dev_err(&pdev->dev, "of node not found\n"); + return NULL; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + np = of_node_get(np); + regulators = of_get_child_by_name(np, "regulators"); + if (!regulators) { + dev_warn(&pdev->dev, "regulator node not found\n"); + return NULL; + } + + ret = of_regulator_match(&pdev->dev, regulators, matches, count); + of_node_put(regulators); + if (ret < 0) { + dev_err(&pdev->dev, "Error parsing regulator init data: %d\n", + ret); + return NULL; + } + + *bcm590xx_reg_matches = matches; + + for (idx = 0; idx < count; idx++) { + if (!matches[idx].init_data || !matches[idx].of_node) + continue; + + data->bcm590xx_pmu_init_data[idx] = matches[idx].init_data; + } + + return data; +} + +static int bcm590xx_probe(struct platform_device *pdev) +{ + struct bcm590xx *bcm590xx = dev_get_drvdata(pdev->dev.parent); + struct bcm590xx_board *pmu_data = NULL; + struct bcm590xx_reg *pmu; + struct regulator_config config = { }; + struct bcm590xx_info *info; + struct regulator_init_data *reg_data; + struct regulator_dev *rdev; + struct of_regulator_match *bcm590xx_reg_matches = NULL; + int i; + + pmu_data = bcm590xx_parse_dt_reg_data(pdev, + &bcm590xx_reg_matches); + + pmu = devm_kzalloc(&pdev->dev, sizeof(*pmu), GFP_KERNEL); + if (!pmu) + return -ENOMEM; + + pmu->mfd = bcm590xx; + + platform_set_drvdata(pdev, pmu); + + pmu->desc = devm_kcalloc(&pdev->dev, + BCM590XX_NUM_REGS, + sizeof(struct regulator_desc), + GFP_KERNEL); + if (!pmu->desc) + return -ENOMEM; + + info = bcm590xx_regs; + + for (i = 0; i < BCM590XX_NUM_REGS; i++, info++) { + if (pmu_data) + reg_data = pmu_data->bcm590xx_pmu_init_data[i]; + else + reg_data = NULL; + + /* Register the regulators */ + pmu->desc[i].name = info->name; + pmu->desc[i].supply_name = info->vin_name; + pmu->desc[i].id = i; + pmu->desc[i].volt_table = info->volt_table; + pmu->desc[i].n_voltages = info->n_voltages; + pmu->desc[i].linear_ranges = info->linear_ranges; + pmu->desc[i].n_linear_ranges = info->n_linear_ranges; + + if ((BCM590XX_REG_IS_LDO(i)) || (BCM590XX_REG_IS_GPLDO(i))) { + pmu->desc[i].ops = &bcm590xx_ops_ldo; + pmu->desc[i].vsel_mask = BCM590XX_LDO_VSEL_MASK; + } else if (BCM590XX_REG_IS_VBUS(i)) + pmu->desc[i].ops = &bcm590xx_ops_vbus; + else { + pmu->desc[i].ops = &bcm590xx_ops_dcdc; + pmu->desc[i].vsel_mask = BCM590XX_SR_VSEL_MASK; + } + + if (BCM590XX_REG_IS_VBUS(i)) + pmu->desc[i].enable_mask = BCM590XX_VBUS_ENABLE; + else { + pmu->desc[i].vsel_reg = bcm590xx_get_vsel_register(i); + pmu->desc[i].enable_is_inverted = true; + pmu->desc[i].enable_mask = BCM590XX_REG_ENABLE; + } + pmu->desc[i].enable_reg = bcm590xx_get_enable_register(i); + pmu->desc[i].type = REGULATOR_VOLTAGE; + pmu->desc[i].owner = THIS_MODULE; + + config.dev = bcm590xx->dev; + config.init_data = reg_data; + config.driver_data = pmu; + if (BCM590XX_REG_IS_GPLDO(i) || BCM590XX_REG_IS_VBUS(i)) + config.regmap = bcm590xx->regmap_sec; + else + config.regmap = bcm590xx->regmap_pri; + + if (bcm590xx_reg_matches) + config.of_node = bcm590xx_reg_matches[i].of_node; + + rdev = devm_regulator_register(&pdev->dev, &pmu->desc[i], + &config); + if (IS_ERR(rdev)) { + dev_err(bcm590xx->dev, + "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static struct platform_driver bcm590xx_regulator_driver = { + .driver = { + .name = "bcm590xx-vregs", + }, + .probe = bcm590xx_probe, +}; +module_platform_driver(bcm590xx_regulator_driver); + +MODULE_AUTHOR("Matt Porter <mporter@linaro.org>"); +MODULE_DESCRIPTION("BCM590xx voltage regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bcm590xx-vregs"); diff --git a/drivers/regulator/bd71837-regulator.c b/drivers/regulator/bd71837-regulator.c new file mode 100644 index 000000000..a1bd8aaf4 --- /dev/null +++ b/drivers/regulator/bd71837-regulator.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 ROHM Semiconductors +// bd71837-regulator.c ROHM BD71837MWV regulator driver + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mfd/rohm-bd718x7.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/slab.h> + +struct bd71837_pmic { + struct regulator_desc descs[BD71837_REGULATOR_CNT]; + struct bd71837 *mfd; + struct platform_device *pdev; + struct regulator_dev *rdev[BD71837_REGULATOR_CNT]; +}; + +/* + * BUCK1/2/3/4 + * BUCK1RAMPRATE[1:0] BUCK1 DVS ramp rate setting + * 00: 10.00mV/usec 10mV 1uS + * 01: 5.00mV/usec 10mV 2uS + * 10: 2.50mV/usec 10mV 4uS + * 11: 1.25mV/usec 10mV 8uS + */ +static int bd71837_buck1234_set_ramp_delay(struct regulator_dev *rdev, + int ramp_delay) +{ + struct bd71837_pmic *pmic = rdev_get_drvdata(rdev); + struct bd71837 *mfd = pmic->mfd; + int id = rdev->desc->id; + unsigned int ramp_value = BUCK_RAMPRATE_10P00MV; + + dev_dbg(&pmic->pdev->dev, "Buck[%d] Set Ramp = %d\n", id + 1, + ramp_delay); + switch (ramp_delay) { + case 1 ... 1250: + ramp_value = BUCK_RAMPRATE_1P25MV; + break; + case 1251 ... 2500: + ramp_value = BUCK_RAMPRATE_2P50MV; + break; + case 2501 ... 5000: + ramp_value = BUCK_RAMPRATE_5P00MV; + break; + case 5001 ... 10000: + ramp_value = BUCK_RAMPRATE_10P00MV; + break; + default: + ramp_value = BUCK_RAMPRATE_10P00MV; + dev_err(&pmic->pdev->dev, + "%s: ramp_delay: %d not supported, setting 10000mV//us\n", + rdev->desc->name, ramp_delay); + } + + return regmap_update_bits(mfd->regmap, BD71837_REG_BUCK1_CTRL + id, + BUCK_RAMPRATE_MASK, ramp_value << 6); +} + +/* Bucks 1 to 4 support DVS. PWM mode is used when voltage is changed. + * Bucks 5 to 8 and LDOs can use PFM and must be disabled when voltage + * is changed. Hence we return -EBUSY for these if voltage is changed + * when BUCK/LDO is enabled. + */ +static int bd71837_set_voltage_sel_restricted(struct regulator_dev *rdev, + unsigned int sel) +{ + if (regulator_is_enabled_regmap(rdev)) + return -EBUSY; + + return regulator_set_voltage_sel_regmap(rdev, sel); +} + +static struct regulator_ops bd71837_ldo_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_sel = bd71837_set_voltage_sel_restricted, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + +static struct regulator_ops bd71837_ldo_regulator_nolinear_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_table, + .set_voltage_sel = bd71837_set_voltage_sel_restricted, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + +static struct regulator_ops bd71837_buck_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_sel = bd71837_set_voltage_sel_restricted, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, +}; + +static struct regulator_ops bd71837_buck_regulator_nolinear_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_table, + .set_voltage_sel = bd71837_set_voltage_sel_restricted, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, +}; + +static struct regulator_ops bd71837_buck1234_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = bd71837_buck1234_set_ramp_delay, +}; + +/* + * BUCK1/2/3/4 + * 0.70 to 1.30V (10mV step) + */ +static const struct regulator_linear_range bd71837_buck1234_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(700000, 0x00, 0x3C, 10000), + REGULATOR_LINEAR_RANGE(1300000, 0x3D, 0x3F, 0), +}; + +/* + * BUCK5 + * 0.9V to 1.35V () + */ +static const struct regulator_linear_range bd71837_buck5_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(700000, 0x00, 0x03, 100000), + REGULATOR_LINEAR_RANGE(1050000, 0x04, 0x05, 50000), + REGULATOR_LINEAR_RANGE(1200000, 0x06, 0x07, 150000), +}; + +/* + * BUCK6 + * 3.0V to 3.3V (step 100mV) + */ +static const struct regulator_linear_range bd71837_buck6_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(3000000, 0x00, 0x03, 100000), +}; + +/* + * BUCK7 + * 000 = 1.605V + * 001 = 1.695V + * 010 = 1.755V + * 011 = 1.8V (Initial) + * 100 = 1.845V + * 101 = 1.905V + * 110 = 1.95V + * 111 = 1.995V + */ +static const unsigned int buck_7_volts[] = { + 1605000, 1695000, 1755000, 1800000, 1845000, 1905000, 1950000, 1995000 +}; + +/* + * BUCK8 + * 0.8V to 1.40V (step 10mV) + */ +static const struct regulator_linear_range bd71837_buck8_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(800000, 0x00, 0x3C, 10000), + REGULATOR_LINEAR_RANGE(1400000, 0x3D, 0x3F, 0), +}; + +/* + * LDO1 + * 3.0 to 3.3V (100mV step) + */ +static const struct regulator_linear_range bd71837_ldo1_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(3000000, 0x00, 0x03, 100000), +}; + +/* + * LDO2 + * 0.8 or 0.9V + */ +static const unsigned int ldo_2_volts[] = { + 900000, 800000 +}; + +/* + * LDO3 + * 1.8 to 3.3V (100mV step) + */ +static const struct regulator_linear_range bd71837_ldo3_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(1800000, 0x00, 0x0F, 100000), +}; + +/* + * LDO4 + * 0.9 to 1.8V (100mV step) + */ +static const struct regulator_linear_range bd71837_ldo4_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(900000, 0x00, 0x09, 100000), + REGULATOR_LINEAR_RANGE(1800000, 0x0A, 0x0F, 0), +}; + +/* + * LDO5 + * 1.8 to 3.3V (100mV step) + */ +static const struct regulator_linear_range bd71837_ldo5_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(1800000, 0x00, 0x0F, 100000), +}; + +/* + * LDO6 + * 0.9 to 1.8V (100mV step) + */ +static const struct regulator_linear_range bd71837_ldo6_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(900000, 0x00, 0x09, 100000), + REGULATOR_LINEAR_RANGE(1800000, 0x0A, 0x0F, 0), +}; + +/* + * LDO7 + * 1.8 to 3.3V (100mV step) + */ +static const struct regulator_linear_range bd71837_ldo7_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(1800000, 0x00, 0x0F, 100000), +}; + +static const struct regulator_desc bd71837_regulators[] = { + { + .name = "buck1", + .of_match = of_match_ptr("BUCK1"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_BUCK1, + .ops = &bd71837_buck1234_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_BUCK1_VOLTAGE_NUM, + .linear_ranges = bd71837_buck1234_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_buck1234_voltage_ranges), + .vsel_reg = BD71837_REG_BUCK1_VOLT_RUN, + .vsel_mask = BUCK1_RUN_MASK, + .enable_reg = BD71837_REG_BUCK1_CTRL, + .enable_mask = BD71837_BUCK_EN, + .owner = THIS_MODULE, + }, + { + .name = "buck2", + .of_match = of_match_ptr("BUCK2"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_BUCK2, + .ops = &bd71837_buck1234_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_BUCK2_VOLTAGE_NUM, + .linear_ranges = bd71837_buck1234_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_buck1234_voltage_ranges), + .vsel_reg = BD71837_REG_BUCK2_VOLT_RUN, + .vsel_mask = BUCK2_RUN_MASK, + .enable_reg = BD71837_REG_BUCK2_CTRL, + .enable_mask = BD71837_BUCK_EN, + .owner = THIS_MODULE, + }, + { + .name = "buck3", + .of_match = of_match_ptr("BUCK3"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_BUCK3, + .ops = &bd71837_buck1234_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_BUCK3_VOLTAGE_NUM, + .linear_ranges = bd71837_buck1234_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_buck1234_voltage_ranges), + .vsel_reg = BD71837_REG_BUCK3_VOLT_RUN, + .vsel_mask = BUCK3_RUN_MASK, + .enable_reg = BD71837_REG_BUCK3_CTRL, + .enable_mask = BD71837_BUCK_EN, + .owner = THIS_MODULE, + }, + { + .name = "buck4", + .of_match = of_match_ptr("BUCK4"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_BUCK4, + .ops = &bd71837_buck1234_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_BUCK4_VOLTAGE_NUM, + .linear_ranges = bd71837_buck1234_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_buck1234_voltage_ranges), + .vsel_reg = BD71837_REG_BUCK4_VOLT_RUN, + .vsel_mask = BUCK4_RUN_MASK, + .enable_reg = BD71837_REG_BUCK4_CTRL, + .enable_mask = BD71837_BUCK_EN, + .owner = THIS_MODULE, + }, + { + .name = "buck5", + .of_match = of_match_ptr("BUCK5"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_BUCK5, + .ops = &bd71837_buck_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_BUCK5_VOLTAGE_NUM, + .linear_ranges = bd71837_buck5_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_buck5_voltage_ranges), + .vsel_reg = BD71837_REG_BUCK5_VOLT, + .vsel_mask = BUCK5_MASK, + .enable_reg = BD71837_REG_BUCK5_CTRL, + .enable_mask = BD71837_BUCK_EN, + .owner = THIS_MODULE, + }, + { + .name = "buck6", + .of_match = of_match_ptr("BUCK6"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_BUCK6, + .ops = &bd71837_buck_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_BUCK6_VOLTAGE_NUM, + .linear_ranges = bd71837_buck6_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_buck6_voltage_ranges), + .vsel_reg = BD71837_REG_BUCK6_VOLT, + .vsel_mask = BUCK6_MASK, + .enable_reg = BD71837_REG_BUCK6_CTRL, + .enable_mask = BD71837_BUCK_EN, + .owner = THIS_MODULE, + }, + { + .name = "buck7", + .of_match = of_match_ptr("BUCK7"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_BUCK7, + .ops = &bd71837_buck_regulator_nolinear_ops, + .type = REGULATOR_VOLTAGE, + .volt_table = &buck_7_volts[0], + .n_voltages = ARRAY_SIZE(buck_7_volts), + .vsel_reg = BD71837_REG_BUCK7_VOLT, + .vsel_mask = BUCK7_MASK, + .enable_reg = BD71837_REG_BUCK7_CTRL, + .enable_mask = BD71837_BUCK_EN, + .owner = THIS_MODULE, + }, + { + .name = "buck8", + .of_match = of_match_ptr("BUCK8"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_BUCK8, + .ops = &bd71837_buck_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_BUCK8_VOLTAGE_NUM, + .linear_ranges = bd71837_buck8_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_buck8_voltage_ranges), + .vsel_reg = BD71837_REG_BUCK8_VOLT, + .vsel_mask = BUCK8_MASK, + .enable_reg = BD71837_REG_BUCK8_CTRL, + .enable_mask = BD71837_BUCK_EN, + .owner = THIS_MODULE, + }, + { + .name = "ldo1", + .of_match = of_match_ptr("LDO1"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_LDO1, + .ops = &bd71837_ldo_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_LDO1_VOLTAGE_NUM, + .linear_ranges = bd71837_ldo1_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_ldo1_voltage_ranges), + .vsel_reg = BD71837_REG_LDO1_VOLT, + .vsel_mask = LDO1_MASK, + .enable_reg = BD71837_REG_LDO1_VOLT, + .enable_mask = BD71837_LDO_EN, + .owner = THIS_MODULE, + }, + { + .name = "ldo2", + .of_match = of_match_ptr("LDO2"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_LDO2, + .ops = &bd71837_ldo_regulator_nolinear_ops, + .type = REGULATOR_VOLTAGE, + .volt_table = &ldo_2_volts[0], + .vsel_reg = BD71837_REG_LDO2_VOLT, + .vsel_mask = LDO2_MASK, + .n_voltages = ARRAY_SIZE(ldo_2_volts), + .n_voltages = BD71837_LDO2_VOLTAGE_NUM, + .enable_reg = BD71837_REG_LDO2_VOLT, + .enable_mask = BD71837_LDO_EN, + .owner = THIS_MODULE, + }, + { + .name = "ldo3", + .of_match = of_match_ptr("LDO3"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_LDO3, + .ops = &bd71837_ldo_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_LDO3_VOLTAGE_NUM, + .linear_ranges = bd71837_ldo3_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_ldo3_voltage_ranges), + .vsel_reg = BD71837_REG_LDO3_VOLT, + .vsel_mask = LDO3_MASK, + .enable_reg = BD71837_REG_LDO3_VOLT, + .enable_mask = BD71837_LDO_EN, + .owner = THIS_MODULE, + }, + { + .name = "ldo4", + .of_match = of_match_ptr("LDO4"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_LDO4, + .ops = &bd71837_ldo_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_LDO4_VOLTAGE_NUM, + .linear_ranges = bd71837_ldo4_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_ldo4_voltage_ranges), + .vsel_reg = BD71837_REG_LDO4_VOLT, + .vsel_mask = LDO4_MASK, + .enable_reg = BD71837_REG_LDO4_VOLT, + .enable_mask = BD71837_LDO_EN, + .owner = THIS_MODULE, + }, + { + .name = "ldo5", + .of_match = of_match_ptr("LDO5"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_LDO5, + .ops = &bd71837_ldo_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_LDO5_VOLTAGE_NUM, + .linear_ranges = bd71837_ldo5_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_ldo5_voltage_ranges), + /* LDO5 is supplied by buck6 */ + .supply_name = "buck6", + .vsel_reg = BD71837_REG_LDO5_VOLT, + .vsel_mask = LDO5_MASK, + .enable_reg = BD71837_REG_LDO5_VOLT, + .enable_mask = BD71837_LDO_EN, + .owner = THIS_MODULE, + }, + { + .name = "ldo6", + .of_match = of_match_ptr("LDO6"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_LDO6, + .ops = &bd71837_ldo_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_LDO6_VOLTAGE_NUM, + .linear_ranges = bd71837_ldo6_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_ldo6_voltage_ranges), + /* LDO6 is supplied by buck7 */ + .supply_name = "buck7", + .vsel_reg = BD71837_REG_LDO6_VOLT, + .vsel_mask = LDO6_MASK, + .enable_reg = BD71837_REG_LDO6_VOLT, + .enable_mask = BD71837_LDO_EN, + .owner = THIS_MODULE, + }, + { + .name = "ldo7", + .of_match = of_match_ptr("LDO7"), + .regulators_node = of_match_ptr("regulators"), + .id = BD71837_LDO7, + .ops = &bd71837_ldo_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = BD71837_LDO7_VOLTAGE_NUM, + .linear_ranges = bd71837_ldo7_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(bd71837_ldo7_voltage_ranges), + .vsel_reg = BD71837_REG_LDO7_VOLT, + .vsel_mask = LDO7_MASK, + .enable_reg = BD71837_REG_LDO7_VOLT, + .enable_mask = BD71837_LDO_EN, + .owner = THIS_MODULE, + }, +}; + +struct reg_init { + unsigned int reg; + unsigned int mask; +}; + +static int bd71837_probe(struct platform_device *pdev) +{ + struct bd71837_pmic *pmic; + struct regulator_config config = { 0 }; + struct reg_init pmic_regulator_inits[] = { + { + .reg = BD71837_REG_BUCK1_CTRL, + .mask = BD71837_BUCK_SEL, + }, { + .reg = BD71837_REG_BUCK2_CTRL, + .mask = BD71837_BUCK_SEL, + }, { + .reg = BD71837_REG_BUCK3_CTRL, + .mask = BD71837_BUCK_SEL, + }, { + .reg = BD71837_REG_BUCK4_CTRL, + .mask = BD71837_BUCK_SEL, + }, { + .reg = BD71837_REG_BUCK5_CTRL, + .mask = BD71837_BUCK_SEL, + }, { + .reg = BD71837_REG_BUCK6_CTRL, + .mask = BD71837_BUCK_SEL, + }, { + .reg = BD71837_REG_BUCK7_CTRL, + .mask = BD71837_BUCK_SEL, + }, { + .reg = BD71837_REG_BUCK8_CTRL, + .mask = BD71837_BUCK_SEL, + }, { + .reg = BD71837_REG_LDO1_VOLT, + .mask = BD71837_LDO_SEL, + }, { + .reg = BD71837_REG_LDO2_VOLT, + .mask = BD71837_LDO_SEL, + }, { + .reg = BD71837_REG_LDO3_VOLT, + .mask = BD71837_LDO_SEL, + }, { + .reg = BD71837_REG_LDO4_VOLT, + .mask = BD71837_LDO_SEL, + }, { + .reg = BD71837_REG_LDO5_VOLT, + .mask = BD71837_LDO_SEL, + }, { + .reg = BD71837_REG_LDO6_VOLT, + .mask = BD71837_LDO_SEL, + }, { + .reg = BD71837_REG_LDO7_VOLT, + .mask = BD71837_LDO_SEL, + } + }; + + int i, err; + + pmic = devm_kzalloc(&pdev->dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + memcpy(pmic->descs, bd71837_regulators, sizeof(pmic->descs)); + + pmic->pdev = pdev; + pmic->mfd = dev_get_drvdata(pdev->dev.parent); + + if (!pmic->mfd) { + dev_err(&pdev->dev, "No MFD driver data\n"); + err = -EINVAL; + goto err; + } + platform_set_drvdata(pdev, pmic); + + /* Register LOCK release */ + err = regmap_update_bits(pmic->mfd->regmap, BD71837_REG_REGLOCK, + (REGLOCK_PWRSEQ | REGLOCK_VREG), 0); + if (err) { + dev_err(&pmic->pdev->dev, "Failed to unlock PMIC (%d)\n", err); + goto err; + } else { + dev_dbg(&pmic->pdev->dev, "Unlocked lock register 0x%x\n", + BD71837_REG_REGLOCK); + } + + /* + * There is a HW quirk in BD71837. The shutdown sequence timings for + * bucks/LDOs which are controlled via register interface are changed. + * At PMIC poweroff the voltage for BUCK6/7 is cut immediately at the + * beginning of shut-down sequence. As bucks 6 and 7 are parent + * supplies for LDO5 and LDO6 - this causes LDO5/6 voltage + * monitoring to errorneously detect under voltage and force PMIC to + * emergency state instead of poweroff. In order to avoid this we + * disable voltage monitoring for LDO5 and LDO6 + */ + err = regmap_update_bits(pmic->mfd->regmap, BD718XX_REG_MVRFLTMASK2, + BD718XX_LDO5_VRMON80 | BD718XX_LDO6_VRMON80, + BD718XX_LDO5_VRMON80 | BD718XX_LDO6_VRMON80); + if (err) { + dev_err(&pmic->pdev->dev, + "Failed to disable voltage monitoring\n"); + goto err; + } + + for (i = 0; i < ARRAY_SIZE(pmic_regulator_inits); i++) { + + struct regulator_desc *desc; + struct regulator_dev *rdev; + + desc = &pmic->descs[i]; + + config.dev = pdev->dev.parent; + config.driver_data = pmic; + config.regmap = pmic->mfd->regmap; + + rdev = devm_regulator_register(&pdev->dev, desc, &config); + if (IS_ERR(rdev)) { + dev_err(pmic->mfd->dev, + "failed to register %s regulator\n", + desc->name); + err = PTR_ERR(rdev); + goto err; + } + /* Regulator register gets the regulator constraints and + * applies them (set_machine_constraints). This should have + * turned the control register(s) to correct values and we + * can now switch the control from PMIC state machine to the + * register interface + */ + err = regmap_update_bits(pmic->mfd->regmap, + pmic_regulator_inits[i].reg, + pmic_regulator_inits[i].mask, + 0xFFFFFFFF); + if (err) { + dev_err(&pmic->pdev->dev, + "Failed to write BUCK/LDO SEL bit for (%s)\n", + desc->name); + goto err; + } + + pmic->rdev[i] = rdev; + } + +err: + return err; +} + +static struct platform_driver bd71837_regulator = { + .driver = { + .name = "bd71837-pmic", + }, + .probe = bd71837_probe, +}; + +module_platform_driver(bd71837_regulator); + +MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>"); +MODULE_DESCRIPTION("BD71837 voltage regulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/bd9571mwv-regulator.c b/drivers/regulator/bd9571mwv-regulator.c new file mode 100644 index 000000000..713730757 --- /dev/null +++ b/drivers/regulator/bd9571mwv-regulator.c @@ -0,0 +1,365 @@ +/* + * ROHM BD9571MWV-M regulator driver + * + * Copyright (C) 2017 Marek Vasut <marek.vasut+renesas@gmail.com> + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether expressed or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License version 2 for more details. + * + * Based on the TPS65086 driver + * + * NOTE: VD09 is missing + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> + +#include <linux/mfd/bd9571mwv.h> + +struct bd9571mwv_reg { + struct bd9571mwv *bd; + + /* DDR Backup Power */ + u8 bkup_mode_cnt_keepon; /* from "rohm,ddr-backup-power" */ + u8 bkup_mode_cnt_saved; + bool bkup_mode_enabled; + + /* Power switch type */ + bool rstbmode_level; + bool rstbmode_pulse; +}; + +enum bd9571mwv_regulators { VD09, VD18, VD25, VD33, DVFS }; + +#define BD9571MWV_REG(_name, _of, _id, _ops, _vr, _vm, _nv, _min, _step, _lmin)\ + { \ + .name = _name, \ + .of_match = of_match_ptr(_of), \ + .regulators_node = "regulators", \ + .id = _id, \ + .ops = &_ops, \ + .n_voltages = _nv, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .vsel_reg = _vr, \ + .vsel_mask = _vm, \ + .min_uV = _min, \ + .uV_step = _step, \ + .linear_min_sel = _lmin, \ + } + +static int bd9571mwv_avs_get_moni_state(struct regulator_dev *rdev) +{ + unsigned int val; + int ret; + + ret = regmap_read(rdev->regmap, BD9571MWV_AVS_SET_MONI, &val); + if (ret != 0) + return ret; + + return val & BD9571MWV_AVS_SET_MONI_MASK; +} + +static int bd9571mwv_avs_set_voltage_sel_regmap(struct regulator_dev *rdev, + unsigned int sel) +{ + int ret; + + ret = bd9571mwv_avs_get_moni_state(rdev); + if (ret < 0) + return ret; + + return regmap_write_bits(rdev->regmap, BD9571MWV_AVS_VD09_VID(ret), + rdev->desc->vsel_mask, sel); +} + +static int bd9571mwv_avs_get_voltage_sel_regmap(struct regulator_dev *rdev) +{ + unsigned int val; + int ret; + + ret = bd9571mwv_avs_get_moni_state(rdev); + if (ret < 0) + return ret; + + ret = regmap_read(rdev->regmap, BD9571MWV_AVS_VD09_VID(ret), &val); + if (ret != 0) + return ret; + + val &= rdev->desc->vsel_mask; + val >>= ffs(rdev->desc->vsel_mask) - 1; + + return val; +} + +static int bd9571mwv_reg_set_voltage_sel_regmap(struct regulator_dev *rdev, + unsigned int sel) +{ + return regmap_write_bits(rdev->regmap, BD9571MWV_DVFS_SETVID, + rdev->desc->vsel_mask, sel); +} + +/* Operations permitted on AVS voltage regulator */ +static struct regulator_ops avs_ops = { + .set_voltage_sel = bd9571mwv_avs_set_voltage_sel_regmap, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = bd9571mwv_avs_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +/* Operations permitted on voltage regulators */ +static struct regulator_ops reg_ops = { + .set_voltage_sel = bd9571mwv_reg_set_voltage_sel_regmap, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +/* Operations permitted on voltage monitors */ +static struct regulator_ops vid_ops = { + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +static struct regulator_desc regulators[] = { + BD9571MWV_REG("VD09", "vd09", VD09, avs_ops, 0, 0x7f, + 0x6f, 600000, 10000, 0x3c), + BD9571MWV_REG("VD18", "vd18", VD18, vid_ops, BD9571MWV_VD18_VID, 0xf, + 16, 1625000, 25000, 0), + BD9571MWV_REG("VD25", "vd25", VD25, vid_ops, BD9571MWV_VD25_VID, 0xf, + 16, 2150000, 50000, 0), + BD9571MWV_REG("VD33", "vd33", VD33, vid_ops, BD9571MWV_VD33_VID, 0xf, + 11, 2800000, 100000, 0), + BD9571MWV_REG("DVFS", "dvfs", DVFS, reg_ops, + BD9571MWV_DVFS_MONIVDAC, 0x7f, + 0x6f, 600000, 10000, 0x3c), +}; + +#ifdef CONFIG_PM_SLEEP +static int bd9571mwv_bkup_mode_read(struct bd9571mwv *bd, unsigned int *mode) +{ + int ret; + + ret = regmap_read(bd->regmap, BD9571MWV_BKUP_MODE_CNT, mode); + if (ret) { + dev_err(bd->dev, "failed to read backup mode (%d)\n", ret); + return ret; + } + + return 0; +} + +static int bd9571mwv_bkup_mode_write(struct bd9571mwv *bd, unsigned int mode) +{ + int ret; + + ret = regmap_write(bd->regmap, BD9571MWV_BKUP_MODE_CNT, mode); + if (ret) { + dev_err(bd->dev, "failed to configure backup mode 0x%x (%d)\n", + mode, ret); + return ret; + } + + return 0; +} + +static ssize_t backup_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bd9571mwv_reg *bdreg = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", bdreg->bkup_mode_enabled ? "on" : "off"); +} + +static ssize_t backup_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bd9571mwv_reg *bdreg = dev_get_drvdata(dev); + unsigned int mode; + int ret; + + if (!count) + return 0; + + ret = kstrtobool(buf, &bdreg->bkup_mode_enabled); + if (ret) + return ret; + + if (!bdreg->rstbmode_level) + return count; + + /* + * Configure DDR Backup Mode, to change the role of the accessory power + * switch from a power switch to a wake-up switch, or vice versa + */ + ret = bd9571mwv_bkup_mode_read(bdreg->bd, &mode); + if (ret) + return ret; + + mode &= ~BD9571MWV_BKUP_MODE_CNT_KEEPON_MASK; + if (bdreg->bkup_mode_enabled) + mode |= bdreg->bkup_mode_cnt_keepon; + + ret = bd9571mwv_bkup_mode_write(bdreg->bd, mode); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(backup_mode); + +static int bd9571mwv_suspend(struct device *dev) +{ + struct bd9571mwv_reg *bdreg = dev_get_drvdata(dev); + unsigned int mode; + int ret; + + if (!bdreg->bkup_mode_enabled) + return 0; + + /* Save DDR Backup Mode */ + ret = bd9571mwv_bkup_mode_read(bdreg->bd, &mode); + if (ret) + return ret; + + bdreg->bkup_mode_cnt_saved = mode; + + if (!bdreg->rstbmode_pulse) + return 0; + + /* Enable DDR Backup Mode */ + mode &= ~BD9571MWV_BKUP_MODE_CNT_KEEPON_MASK; + mode |= bdreg->bkup_mode_cnt_keepon; + + if (mode != bdreg->bkup_mode_cnt_saved) + return bd9571mwv_bkup_mode_write(bdreg->bd, mode); + + return 0; +} + +static int bd9571mwv_resume(struct device *dev) +{ + struct bd9571mwv_reg *bdreg = dev_get_drvdata(dev); + + if (!bdreg->bkup_mode_enabled) + return 0; + + /* Restore DDR Backup Mode */ + return bd9571mwv_bkup_mode_write(bdreg->bd, bdreg->bkup_mode_cnt_saved); +} + +static const struct dev_pm_ops bd9571mwv_pm = { + SET_SYSTEM_SLEEP_PM_OPS(bd9571mwv_suspend, bd9571mwv_resume) +}; + +static int bd9571mwv_regulator_remove(struct platform_device *pdev) +{ + device_remove_file(&pdev->dev, &dev_attr_backup_mode); + return 0; +} +#define DEV_PM_OPS &bd9571mwv_pm +#else +#define DEV_PM_OPS NULL +#define bd9571mwv_regulator_remove NULL +#endif /* CONFIG_PM_SLEEP */ + +static int bd9571mwv_regulator_probe(struct platform_device *pdev) +{ + struct bd9571mwv *bd = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = { }; + struct bd9571mwv_reg *bdreg; + struct regulator_dev *rdev; + unsigned int val; + int i; + + bdreg = devm_kzalloc(&pdev->dev, sizeof(*bdreg), GFP_KERNEL); + if (!bdreg) + return -ENOMEM; + + bdreg->bd = bd; + + platform_set_drvdata(pdev, bdreg); + + config.dev = &pdev->dev; + config.dev->of_node = bd->dev->of_node; + config.driver_data = bd; + config.regmap = bd->regmap; + + for (i = 0; i < ARRAY_SIZE(regulators); i++) { + rdev = devm_regulator_register(&pdev->dev, ®ulators[i], + &config); + if (IS_ERR(rdev)) { + dev_err(bd->dev, "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + } + + val = 0; + of_property_read_u32(bd->dev->of_node, "rohm,ddr-backup-power", &val); + if (val & ~BD9571MWV_BKUP_MODE_CNT_KEEPON_MASK) { + dev_err(bd->dev, "invalid %s mode %u\n", + "rohm,ddr-backup-power", val); + return -EINVAL; + } + bdreg->bkup_mode_cnt_keepon = val; + + bdreg->rstbmode_level = of_property_read_bool(bd->dev->of_node, + "rohm,rstbmode-level"); + bdreg->rstbmode_pulse = of_property_read_bool(bd->dev->of_node, + "rohm,rstbmode-pulse"); + if (bdreg->rstbmode_level && bdreg->rstbmode_pulse) { + dev_err(bd->dev, "only one rohm,rstbmode-* may be specified"); + return -EINVAL; + } + +#ifdef CONFIG_PM_SLEEP + if (bdreg->bkup_mode_cnt_keepon) { + int ret; + + /* + * Backup mode is enabled by default in pulse mode, but needs + * explicit user setup in level mode. + */ + bdreg->bkup_mode_enabled = bdreg->rstbmode_pulse; + + ret = device_create_file(&pdev->dev, &dev_attr_backup_mode); + if (ret) + return ret; + } +#endif /* CONFIG_PM_SLEEP */ + + return 0; +} + +static const struct platform_device_id bd9571mwv_regulator_id_table[] = { + { "bd9571mwv-regulator", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, bd9571mwv_regulator_id_table); + +static struct platform_driver bd9571mwv_regulator_driver = { + .driver = { + .name = "bd9571mwv-regulator", + .pm = DEV_PM_OPS, + }, + .probe = bd9571mwv_regulator_probe, + .remove = bd9571mwv_regulator_remove, + .id_table = bd9571mwv_regulator_id_table, +}; +module_platform_driver(bd9571mwv_regulator_driver); + +MODULE_AUTHOR("Marek Vasut <marek.vasut+renesas@gmail.com>"); +MODULE_DESCRIPTION("BD9571MWV Regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c new file mode 100644 index 000000000..045075cd2 --- /dev/null +++ b/drivers/regulator/core.c @@ -0,0 +1,4958 @@ +/* + * core.c -- Voltage/Current Regulator framework. + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * Copyright 2008 SlimLogic Ltd. + * + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * + * 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/init.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/async.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/suspend.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/module.h> + +#define CREATE_TRACE_POINTS +#include <trace/events/regulator.h> + +#include "dummy.h" +#include "internal.h" + +#define rdev_crit(rdev, fmt, ...) \ + pr_crit("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__) +#define rdev_err(rdev, fmt, ...) \ + pr_err("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__) +#define rdev_warn(rdev, fmt, ...) \ + pr_warn("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__) +#define rdev_info(rdev, fmt, ...) \ + pr_info("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__) +#define rdev_dbg(rdev, fmt, ...) \ + pr_debug("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__) + +static DEFINE_MUTEX(regulator_list_mutex); +static LIST_HEAD(regulator_map_list); +static LIST_HEAD(regulator_ena_gpio_list); +static LIST_HEAD(regulator_supply_alias_list); +static bool has_full_constraints; + +static struct dentry *debugfs_root; + +/* + * struct regulator_map + * + * Used to provide symbolic supply names to devices. + */ +struct regulator_map { + struct list_head list; + const char *dev_name; /* The dev_name() for the consumer */ + const char *supply; + struct regulator_dev *regulator; +}; + +/* + * struct regulator_enable_gpio + * + * Management for shared enable GPIO pin + */ +struct regulator_enable_gpio { + struct list_head list; + struct gpio_desc *gpiod; + u32 enable_count; /* a number of enabled shared GPIO */ + u32 request_count; /* a number of requested shared GPIO */ + unsigned int ena_gpio_invert:1; +}; + +/* + * struct regulator_supply_alias + * + * Used to map lookups for a supply onto an alternative device. + */ +struct regulator_supply_alias { + struct list_head list; + struct device *src_dev; + const char *src_supply; + struct device *alias_dev; + const char *alias_supply; +}; + +static int _regulator_is_enabled(struct regulator_dev *rdev); +static int _regulator_disable(struct regulator_dev *rdev); +static int _regulator_get_voltage(struct regulator_dev *rdev); +static int _regulator_get_current_limit(struct regulator_dev *rdev); +static unsigned int _regulator_get_mode(struct regulator_dev *rdev); +static int _notifier_call_chain(struct regulator_dev *rdev, + unsigned long event, void *data); +static int _regulator_do_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV); +static struct regulator *create_regulator(struct regulator_dev *rdev, + struct device *dev, + const char *supply_name); +static void _regulator_put(struct regulator *regulator); + +static const char *rdev_get_name(struct regulator_dev *rdev) +{ + if (rdev->constraints && rdev->constraints->name) + return rdev->constraints->name; + else if (rdev->desc->name) + return rdev->desc->name; + else + return ""; +} + +static bool have_full_constraints(void) +{ + return has_full_constraints || of_have_populated_dt(); +} + +static bool regulator_ops_is_valid(struct regulator_dev *rdev, int ops) +{ + if (!rdev->constraints) { + rdev_err(rdev, "no constraints\n"); + return false; + } + + if (rdev->constraints->valid_ops_mask & ops) + return true; + + return false; +} + +static inline struct regulator_dev *rdev_get_supply(struct regulator_dev *rdev) +{ + if (rdev && rdev->supply) + return rdev->supply->rdev; + + return NULL; +} + +/** + * regulator_lock_nested - lock a single regulator + * @rdev: regulator source + * @subclass: mutex subclass used for lockdep + * + * This function can be called many times by one task on + * a single regulator and its mutex will be locked only + * once. If a task, which is calling this function is other + * than the one, which initially locked the mutex, it will + * wait on mutex. + */ +static void regulator_lock_nested(struct regulator_dev *rdev, + unsigned int subclass) +{ + if (!mutex_trylock(&rdev->mutex)) { + if (rdev->mutex_owner == current) { + rdev->ref_cnt++; + return; + } + mutex_lock_nested(&rdev->mutex, subclass); + } + + rdev->ref_cnt = 1; + rdev->mutex_owner = current; +} + +static inline void regulator_lock(struct regulator_dev *rdev) +{ + regulator_lock_nested(rdev, 0); +} + +/** + * regulator_unlock - unlock a single regulator + * @rdev: regulator_source + * + * This function unlocks the mutex when the + * reference counter reaches 0. + */ +static void regulator_unlock(struct regulator_dev *rdev) +{ + if (rdev->ref_cnt != 0) { + rdev->ref_cnt--; + + if (!rdev->ref_cnt) { + rdev->mutex_owner = NULL; + mutex_unlock(&rdev->mutex); + } + } +} + +/** + * regulator_lock_supply - lock a regulator and its supplies + * @rdev: regulator source + */ +static void regulator_lock_supply(struct regulator_dev *rdev) +{ + int i; + + for (i = 0; rdev; rdev = rdev_get_supply(rdev), i++) + regulator_lock_nested(rdev, i); +} + +/** + * regulator_unlock_supply - unlock a regulator and its supplies + * @rdev: regulator source + */ +static void regulator_unlock_supply(struct regulator_dev *rdev) +{ + struct regulator *supply; + + while (1) { + regulator_unlock(rdev); + supply = rdev->supply; + + if (!rdev->supply) + return; + + rdev = supply->rdev; + } +} + +/** + * of_get_regulator - get a regulator device node based on supply name + * @dev: Device pointer for the consumer (of regulator) device + * @supply: regulator supply name + * + * Extract the regulator device node corresponding to the supply name. + * returns the device node corresponding to the regulator if found, else + * returns NULL. + */ +static struct device_node *of_get_regulator(struct device *dev, const char *supply) +{ + struct device_node *regnode = NULL; + char prop_name[32]; /* 32 is max size of property name */ + + dev_dbg(dev, "Looking up %s-supply from device tree\n", supply); + + snprintf(prop_name, 32, "%s-supply", supply); + regnode = of_parse_phandle(dev->of_node, prop_name, 0); + + if (!regnode) { + dev_dbg(dev, "Looking up %s property in node %pOF failed\n", + prop_name, dev->of_node); + return NULL; + } + return regnode; +} + +/* Platform voltage constraint check */ +static int regulator_check_voltage(struct regulator_dev *rdev, + int *min_uV, int *max_uV) +{ + BUG_ON(*min_uV > *max_uV); + + if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_VOLTAGE)) { + rdev_err(rdev, "voltage operation not allowed\n"); + return -EPERM; + } + + if (*max_uV > rdev->constraints->max_uV) + *max_uV = rdev->constraints->max_uV; + if (*min_uV < rdev->constraints->min_uV) + *min_uV = rdev->constraints->min_uV; + + if (*min_uV > *max_uV) { + rdev_err(rdev, "unsupportable voltage range: %d-%duV\n", + *min_uV, *max_uV); + return -EINVAL; + } + + return 0; +} + +/* return 0 if the state is valid */ +static int regulator_check_states(suspend_state_t state) +{ + return (state > PM_SUSPEND_MAX || state == PM_SUSPEND_TO_IDLE); +} + +/* Make sure we select a voltage that suits the needs of all + * regulator consumers + */ +static int regulator_check_consumers(struct regulator_dev *rdev, + int *min_uV, int *max_uV, + suspend_state_t state) +{ + struct regulator *regulator; + struct regulator_voltage *voltage; + + list_for_each_entry(regulator, &rdev->consumer_list, list) { + voltage = ®ulator->voltage[state]; + /* + * Assume consumers that didn't say anything are OK + * with anything in the constraint range. + */ + if (!voltage->min_uV && !voltage->max_uV) + continue; + + if (*max_uV > voltage->max_uV) + *max_uV = voltage->max_uV; + if (*min_uV < voltage->min_uV) + *min_uV = voltage->min_uV; + } + + if (*min_uV > *max_uV) { + rdev_err(rdev, "Restricting voltage, %u-%uuV\n", + *min_uV, *max_uV); + return -EINVAL; + } + + return 0; +} + +/* current constraint check */ +static int regulator_check_current_limit(struct regulator_dev *rdev, + int *min_uA, int *max_uA) +{ + BUG_ON(*min_uA > *max_uA); + + if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_CURRENT)) { + rdev_err(rdev, "current operation not allowed\n"); + return -EPERM; + } + + if (*max_uA > rdev->constraints->max_uA) + *max_uA = rdev->constraints->max_uA; + if (*min_uA < rdev->constraints->min_uA) + *min_uA = rdev->constraints->min_uA; + + if (*min_uA > *max_uA) { + rdev_err(rdev, "unsupportable current range: %d-%duA\n", + *min_uA, *max_uA); + return -EINVAL; + } + + return 0; +} + +/* operating mode constraint check */ +static int regulator_mode_constrain(struct regulator_dev *rdev, + unsigned int *mode) +{ + switch (*mode) { + case REGULATOR_MODE_FAST: + case REGULATOR_MODE_NORMAL: + case REGULATOR_MODE_IDLE: + case REGULATOR_MODE_STANDBY: + break; + default: + rdev_err(rdev, "invalid mode %x specified\n", *mode); + return -EINVAL; + } + + if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_MODE)) { + rdev_err(rdev, "mode operation not allowed\n"); + return -EPERM; + } + + /* The modes are bitmasks, the most power hungry modes having + * the lowest values. If the requested mode isn't supported + * try higher modes. */ + while (*mode) { + if (rdev->constraints->valid_modes_mask & *mode) + return 0; + *mode /= 2; + } + + return -EINVAL; +} + +static inline struct regulator_state * +regulator_get_suspend_state(struct regulator_dev *rdev, suspend_state_t state) +{ + if (rdev->constraints == NULL) + return NULL; + + switch (state) { + case PM_SUSPEND_STANDBY: + return &rdev->constraints->state_standby; + case PM_SUSPEND_MEM: + return &rdev->constraints->state_mem; + case PM_SUSPEND_MAX: + return &rdev->constraints->state_disk; + default: + return NULL; + } +} + +static ssize_t regulator_uV_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + ssize_t ret; + + regulator_lock(rdev); + ret = sprintf(buf, "%d\n", _regulator_get_voltage(rdev)); + regulator_unlock(rdev); + + return ret; +} +static DEVICE_ATTR(microvolts, 0444, regulator_uV_show, NULL); + +static ssize_t regulator_uA_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", _regulator_get_current_limit(rdev)); +} +static DEVICE_ATTR(microamps, 0444, regulator_uA_show, NULL); + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", rdev_get_name(rdev)); +} +static DEVICE_ATTR_RO(name); + +static ssize_t regulator_print_opmode(char *buf, int mode) +{ + switch (mode) { + case REGULATOR_MODE_FAST: + return sprintf(buf, "fast\n"); + case REGULATOR_MODE_NORMAL: + return sprintf(buf, "normal\n"); + case REGULATOR_MODE_IDLE: + return sprintf(buf, "idle\n"); + case REGULATOR_MODE_STANDBY: + return sprintf(buf, "standby\n"); + } + return sprintf(buf, "unknown\n"); +} + +static ssize_t regulator_opmode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return regulator_print_opmode(buf, _regulator_get_mode(rdev)); +} +static DEVICE_ATTR(opmode, 0444, regulator_opmode_show, NULL); + +static ssize_t regulator_print_state(char *buf, int state) +{ + if (state > 0) + return sprintf(buf, "enabled\n"); + else if (state == 0) + return sprintf(buf, "disabled\n"); + else + return sprintf(buf, "unknown\n"); +} + +static ssize_t regulator_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + ssize_t ret; + + regulator_lock(rdev); + ret = regulator_print_state(buf, _regulator_is_enabled(rdev)); + regulator_unlock(rdev); + + return ret; +} +static DEVICE_ATTR(state, 0444, regulator_state_show, NULL); + +static ssize_t regulator_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + int status; + char *label; + + status = rdev->desc->ops->get_status(rdev); + if (status < 0) + return status; + + switch (status) { + case REGULATOR_STATUS_OFF: + label = "off"; + break; + case REGULATOR_STATUS_ON: + label = "on"; + break; + case REGULATOR_STATUS_ERROR: + label = "error"; + break; + case REGULATOR_STATUS_FAST: + label = "fast"; + break; + case REGULATOR_STATUS_NORMAL: + label = "normal"; + break; + case REGULATOR_STATUS_IDLE: + label = "idle"; + break; + case REGULATOR_STATUS_STANDBY: + label = "standby"; + break; + case REGULATOR_STATUS_BYPASS: + label = "bypass"; + break; + case REGULATOR_STATUS_UNDEFINED: + label = "undefined"; + break; + default: + return -ERANGE; + } + + return sprintf(buf, "%s\n", label); +} +static DEVICE_ATTR(status, 0444, regulator_status_show, NULL); + +static ssize_t regulator_min_uA_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + if (!rdev->constraints) + return sprintf(buf, "constraint not defined\n"); + + return sprintf(buf, "%d\n", rdev->constraints->min_uA); +} +static DEVICE_ATTR(min_microamps, 0444, regulator_min_uA_show, NULL); + +static ssize_t regulator_max_uA_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + if (!rdev->constraints) + return sprintf(buf, "constraint not defined\n"); + + return sprintf(buf, "%d\n", rdev->constraints->max_uA); +} +static DEVICE_ATTR(max_microamps, 0444, regulator_max_uA_show, NULL); + +static ssize_t regulator_min_uV_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + if (!rdev->constraints) + return sprintf(buf, "constraint not defined\n"); + + return sprintf(buf, "%d\n", rdev->constraints->min_uV); +} +static DEVICE_ATTR(min_microvolts, 0444, regulator_min_uV_show, NULL); + +static ssize_t regulator_max_uV_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + if (!rdev->constraints) + return sprintf(buf, "constraint not defined\n"); + + return sprintf(buf, "%d\n", rdev->constraints->max_uV); +} +static DEVICE_ATTR(max_microvolts, 0444, regulator_max_uV_show, NULL); + +static ssize_t regulator_total_uA_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + struct regulator *regulator; + int uA = 0; + + regulator_lock(rdev); + list_for_each_entry(regulator, &rdev->consumer_list, list) + uA += regulator->uA_load; + regulator_unlock(rdev); + return sprintf(buf, "%d\n", uA); +} +static DEVICE_ATTR(requested_microamps, 0444, regulator_total_uA_show, NULL); + +static ssize_t num_users_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", rdev->use_count); +} +static DEVICE_ATTR_RO(num_users); + +static ssize_t type_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + switch (rdev->desc->type) { + case REGULATOR_VOLTAGE: + return sprintf(buf, "voltage\n"); + case REGULATOR_CURRENT: + return sprintf(buf, "current\n"); + } + return sprintf(buf, "unknown\n"); +} +static DEVICE_ATTR_RO(type); + +static ssize_t regulator_suspend_mem_uV_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", rdev->constraints->state_mem.uV); +} +static DEVICE_ATTR(suspend_mem_microvolts, 0444, + regulator_suspend_mem_uV_show, NULL); + +static ssize_t regulator_suspend_disk_uV_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", rdev->constraints->state_disk.uV); +} +static DEVICE_ATTR(suspend_disk_microvolts, 0444, + regulator_suspend_disk_uV_show, NULL); + +static ssize_t regulator_suspend_standby_uV_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", rdev->constraints->state_standby.uV); +} +static DEVICE_ATTR(suspend_standby_microvolts, 0444, + regulator_suspend_standby_uV_show, NULL); + +static ssize_t regulator_suspend_mem_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return regulator_print_opmode(buf, + rdev->constraints->state_mem.mode); +} +static DEVICE_ATTR(suspend_mem_mode, 0444, + regulator_suspend_mem_mode_show, NULL); + +static ssize_t regulator_suspend_disk_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return regulator_print_opmode(buf, + rdev->constraints->state_disk.mode); +} +static DEVICE_ATTR(suspend_disk_mode, 0444, + regulator_suspend_disk_mode_show, NULL); + +static ssize_t regulator_suspend_standby_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return regulator_print_opmode(buf, + rdev->constraints->state_standby.mode); +} +static DEVICE_ATTR(suspend_standby_mode, 0444, + regulator_suspend_standby_mode_show, NULL); + +static ssize_t regulator_suspend_mem_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return regulator_print_state(buf, + rdev->constraints->state_mem.enabled); +} +static DEVICE_ATTR(suspend_mem_state, 0444, + regulator_suspend_mem_state_show, NULL); + +static ssize_t regulator_suspend_disk_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return regulator_print_state(buf, + rdev->constraints->state_disk.enabled); +} +static DEVICE_ATTR(suspend_disk_state, 0444, + regulator_suspend_disk_state_show, NULL); + +static ssize_t regulator_suspend_standby_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + return regulator_print_state(buf, + rdev->constraints->state_standby.enabled); +} +static DEVICE_ATTR(suspend_standby_state, 0444, + regulator_suspend_standby_state_show, NULL); + +static ssize_t regulator_bypass_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + const char *report; + bool bypass; + int ret; + + ret = rdev->desc->ops->get_bypass(rdev, &bypass); + + if (ret != 0) + report = "unknown"; + else if (bypass) + report = "enabled"; + else + report = "disabled"; + + return sprintf(buf, "%s\n", report); +} +static DEVICE_ATTR(bypass, 0444, + regulator_bypass_show, NULL); + +/* Calculate the new optimum regulator operating mode based on the new total + * consumer load. All locks held by caller */ +static int drms_uA_update(struct regulator_dev *rdev) +{ + struct regulator *sibling; + int current_uA = 0, output_uV, input_uV, err; + unsigned int mode; + + lockdep_assert_held_once(&rdev->mutex); + + /* + * first check to see if we can set modes at all, otherwise just + * tell the consumer everything is OK. + */ + if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_DRMS)) + return 0; + + if (!rdev->desc->ops->get_optimum_mode && + !rdev->desc->ops->set_load) + return 0; + + if (!rdev->desc->ops->set_mode && + !rdev->desc->ops->set_load) + return -EINVAL; + + /* calc total requested load */ + list_for_each_entry(sibling, &rdev->consumer_list, list) + current_uA += sibling->uA_load; + + current_uA += rdev->constraints->system_load; + + if (rdev->desc->ops->set_load) { + /* set the optimum mode for our new total regulator load */ + err = rdev->desc->ops->set_load(rdev, current_uA); + if (err < 0) + rdev_err(rdev, "failed to set load %d\n", current_uA); + } else { + /* get output voltage */ + output_uV = _regulator_get_voltage(rdev); + if (output_uV <= 0) { + rdev_err(rdev, "invalid output voltage found\n"); + return -EINVAL; + } + + /* get input voltage */ + input_uV = 0; + if (rdev->supply) + input_uV = regulator_get_voltage(rdev->supply); + if (input_uV <= 0) + input_uV = rdev->constraints->input_uV; + if (input_uV <= 0) { + rdev_err(rdev, "invalid input voltage found\n"); + return -EINVAL; + } + + /* now get the optimum mode for our new total regulator load */ + mode = rdev->desc->ops->get_optimum_mode(rdev, input_uV, + output_uV, current_uA); + + /* check the new mode is allowed */ + err = regulator_mode_constrain(rdev, &mode); + if (err < 0) { + rdev_err(rdev, "failed to get optimum mode @ %d uA %d -> %d uV\n", + current_uA, input_uV, output_uV); + return err; + } + + err = rdev->desc->ops->set_mode(rdev, mode); + if (err < 0) + rdev_err(rdev, "failed to set optimum mode %x\n", mode); + } + + return err; +} + +static int suspend_set_state(struct regulator_dev *rdev, + suspend_state_t state) +{ + int ret = 0; + struct regulator_state *rstate; + + rstate = regulator_get_suspend_state(rdev, state); + if (rstate == NULL) + return 0; + + /* If we have no suspend mode configration don't set anything; + * only warn if the driver implements set_suspend_voltage or + * set_suspend_mode callback. + */ + if (rstate->enabled != ENABLE_IN_SUSPEND && + rstate->enabled != DISABLE_IN_SUSPEND) { + if (rdev->desc->ops->set_suspend_voltage || + rdev->desc->ops->set_suspend_mode) + rdev_warn(rdev, "No configuration\n"); + return 0; + } + + if (rstate->enabled == ENABLE_IN_SUSPEND && + rdev->desc->ops->set_suspend_enable) + ret = rdev->desc->ops->set_suspend_enable(rdev); + else if (rstate->enabled == DISABLE_IN_SUSPEND && + rdev->desc->ops->set_suspend_disable) + ret = rdev->desc->ops->set_suspend_disable(rdev); + else /* OK if set_suspend_enable or set_suspend_disable is NULL */ + ret = 0; + + if (ret < 0) { + rdev_err(rdev, "failed to enabled/disable\n"); + return ret; + } + + if (rdev->desc->ops->set_suspend_voltage && rstate->uV > 0) { + ret = rdev->desc->ops->set_suspend_voltage(rdev, rstate->uV); + if (ret < 0) { + rdev_err(rdev, "failed to set voltage\n"); + return ret; + } + } + + if (rdev->desc->ops->set_suspend_mode && rstate->mode > 0) { + ret = rdev->desc->ops->set_suspend_mode(rdev, rstate->mode); + if (ret < 0) { + rdev_err(rdev, "failed to set mode\n"); + return ret; + } + } + + return ret; +} + +static void print_constraints(struct regulator_dev *rdev) +{ + struct regulation_constraints *constraints = rdev->constraints; + char buf[160] = ""; + size_t len = sizeof(buf) - 1; + int count = 0; + int ret; + + if (constraints->min_uV && constraints->max_uV) { + if (constraints->min_uV == constraints->max_uV) + count += scnprintf(buf + count, len - count, "%d mV ", + constraints->min_uV / 1000); + else + count += scnprintf(buf + count, len - count, + "%d <--> %d mV ", + constraints->min_uV / 1000, + constraints->max_uV / 1000); + } + + if (!constraints->min_uV || + constraints->min_uV != constraints->max_uV) { + ret = _regulator_get_voltage(rdev); + if (ret > 0) + count += scnprintf(buf + count, len - count, + "at %d mV ", ret / 1000); + } + + if (constraints->uV_offset) + count += scnprintf(buf + count, len - count, "%dmV offset ", + constraints->uV_offset / 1000); + + if (constraints->min_uA && constraints->max_uA) { + if (constraints->min_uA == constraints->max_uA) + count += scnprintf(buf + count, len - count, "%d mA ", + constraints->min_uA / 1000); + else + count += scnprintf(buf + count, len - count, + "%d <--> %d mA ", + constraints->min_uA / 1000, + constraints->max_uA / 1000); + } + + if (!constraints->min_uA || + constraints->min_uA != constraints->max_uA) { + ret = _regulator_get_current_limit(rdev); + if (ret > 0) + count += scnprintf(buf + count, len - count, + "at %d mA ", ret / 1000); + } + + if (constraints->valid_modes_mask & REGULATOR_MODE_FAST) + count += scnprintf(buf + count, len - count, "fast "); + if (constraints->valid_modes_mask & REGULATOR_MODE_NORMAL) + count += scnprintf(buf + count, len - count, "normal "); + if (constraints->valid_modes_mask & REGULATOR_MODE_IDLE) + count += scnprintf(buf + count, len - count, "idle "); + if (constraints->valid_modes_mask & REGULATOR_MODE_STANDBY) + count += scnprintf(buf + count, len - count, "standby"); + + if (!count) + scnprintf(buf, len, "no parameters"); + + rdev_dbg(rdev, "%s\n", buf); + + if ((constraints->min_uV != constraints->max_uV) && + !regulator_ops_is_valid(rdev, REGULATOR_CHANGE_VOLTAGE)) + rdev_warn(rdev, + "Voltage range but no REGULATOR_CHANGE_VOLTAGE\n"); +} + +static int machine_constraints_voltage(struct regulator_dev *rdev, + struct regulation_constraints *constraints) +{ + const struct regulator_ops *ops = rdev->desc->ops; + int ret; + + /* do we need to apply the constraint voltage */ + if (rdev->constraints->apply_uV && + rdev->constraints->min_uV && rdev->constraints->max_uV) { + int target_min, target_max; + int current_uV = _regulator_get_voltage(rdev); + + if (current_uV == -ENOTRECOVERABLE) { + /* This regulator can't be read and must be initted */ + rdev_info(rdev, "Setting %d-%duV\n", + rdev->constraints->min_uV, + rdev->constraints->max_uV); + _regulator_do_set_voltage(rdev, + rdev->constraints->min_uV, + rdev->constraints->max_uV); + current_uV = _regulator_get_voltage(rdev); + } + + if (current_uV < 0) { + rdev_err(rdev, + "failed to get the current voltage(%d)\n", + current_uV); + return current_uV; + } + + /* + * If we're below the minimum voltage move up to the + * minimum voltage, if we're above the maximum voltage + * then move down to the maximum. + */ + target_min = current_uV; + target_max = current_uV; + + if (current_uV < rdev->constraints->min_uV) { + target_min = rdev->constraints->min_uV; + target_max = rdev->constraints->min_uV; + } + + if (current_uV > rdev->constraints->max_uV) { + target_min = rdev->constraints->max_uV; + target_max = rdev->constraints->max_uV; + } + + if (target_min != current_uV || target_max != current_uV) { + rdev_info(rdev, "Bringing %duV into %d-%duV\n", + current_uV, target_min, target_max); + ret = _regulator_do_set_voltage( + rdev, target_min, target_max); + if (ret < 0) { + rdev_err(rdev, + "failed to apply %d-%duV constraint(%d)\n", + target_min, target_max, ret); + return ret; + } + } + } + + /* constrain machine-level voltage specs to fit + * the actual range supported by this regulator. + */ + if (ops->list_voltage && rdev->desc->n_voltages) { + int count = rdev->desc->n_voltages; + int i; + int min_uV = INT_MAX; + int max_uV = INT_MIN; + int cmin = constraints->min_uV; + int cmax = constraints->max_uV; + + /* it's safe to autoconfigure fixed-voltage supplies + and the constraints are used by list_voltage. */ + if (count == 1 && !cmin) { + cmin = 1; + cmax = INT_MAX; + constraints->min_uV = cmin; + constraints->max_uV = cmax; + } + + /* voltage constraints are optional */ + if ((cmin == 0) && (cmax == 0)) + return 0; + + /* else require explicit machine-level constraints */ + if (cmin <= 0 || cmax <= 0 || cmax < cmin) { + rdev_err(rdev, "invalid voltage constraints\n"); + return -EINVAL; + } + + /* initial: [cmin..cmax] valid, [min_uV..max_uV] not */ + for (i = 0; i < count; i++) { + int value; + + value = ops->list_voltage(rdev, i); + if (value <= 0) + continue; + + /* maybe adjust [min_uV..max_uV] */ + if (value >= cmin && value < min_uV) + min_uV = value; + if (value <= cmax && value > max_uV) + max_uV = value; + } + + /* final: [min_uV..max_uV] valid iff constraints valid */ + if (max_uV < min_uV) { + rdev_err(rdev, + "unsupportable voltage constraints %u-%uuV\n", + min_uV, max_uV); + return -EINVAL; + } + + /* use regulator's subset of machine constraints */ + if (constraints->min_uV < min_uV) { + rdev_dbg(rdev, "override min_uV, %d -> %d\n", + constraints->min_uV, min_uV); + constraints->min_uV = min_uV; + } + if (constraints->max_uV > max_uV) { + rdev_dbg(rdev, "override max_uV, %d -> %d\n", + constraints->max_uV, max_uV); + constraints->max_uV = max_uV; + } + } + + return 0; +} + +static int machine_constraints_current(struct regulator_dev *rdev, + struct regulation_constraints *constraints) +{ + const struct regulator_ops *ops = rdev->desc->ops; + int ret; + + if (!constraints->min_uA && !constraints->max_uA) + return 0; + + if (constraints->min_uA > constraints->max_uA) { + rdev_err(rdev, "Invalid current constraints\n"); + return -EINVAL; + } + + if (!ops->set_current_limit || !ops->get_current_limit) { + rdev_warn(rdev, "Operation of current configuration missing\n"); + return 0; + } + + /* Set regulator current in constraints range */ + ret = ops->set_current_limit(rdev, constraints->min_uA, + constraints->max_uA); + if (ret < 0) { + rdev_err(rdev, "Failed to set current constraint, %d\n", ret); + return ret; + } + + return 0; +} + +static int _regulator_do_enable(struct regulator_dev *rdev); + +/** + * set_machine_constraints - sets regulator constraints + * @rdev: regulator source + * + * Allows platform initialisation code to define and constrain + * regulator circuits e.g. valid voltage/current ranges, etc. NOTE: + * Constraints *must* be set by platform code in order for some + * regulator operations to proceed i.e. set_voltage, set_current_limit, + * set_mode. + */ +static int set_machine_constraints(struct regulator_dev *rdev) +{ + int ret = 0; + const struct regulator_ops *ops = rdev->desc->ops; + + ret = machine_constraints_voltage(rdev, rdev->constraints); + if (ret != 0) + return ret; + + ret = machine_constraints_current(rdev, rdev->constraints); + if (ret != 0) + return ret; + + if (rdev->constraints->ilim_uA && ops->set_input_current_limit) { + ret = ops->set_input_current_limit(rdev, + rdev->constraints->ilim_uA); + if (ret < 0) { + rdev_err(rdev, "failed to set input limit\n"); + return ret; + } + } + + /* do we need to setup our suspend state */ + if (rdev->constraints->initial_state) { + ret = suspend_set_state(rdev, rdev->constraints->initial_state); + if (ret < 0) { + rdev_err(rdev, "failed to set suspend state\n"); + return ret; + } + } + + if (rdev->constraints->initial_mode) { + if (!ops->set_mode) { + rdev_err(rdev, "no set_mode operation\n"); + return -EINVAL; + } + + ret = ops->set_mode(rdev, rdev->constraints->initial_mode); + if (ret < 0) { + rdev_err(rdev, "failed to set initial mode: %d\n", ret); + return ret; + } + } + + if ((rdev->constraints->ramp_delay || rdev->constraints->ramp_disable) + && ops->set_ramp_delay) { + ret = ops->set_ramp_delay(rdev, rdev->constraints->ramp_delay); + if (ret < 0) { + rdev_err(rdev, "failed to set ramp_delay\n"); + return ret; + } + } + + if (rdev->constraints->pull_down && ops->set_pull_down) { + ret = ops->set_pull_down(rdev); + if (ret < 0) { + rdev_err(rdev, "failed to set pull down\n"); + return ret; + } + } + + if (rdev->constraints->soft_start && ops->set_soft_start) { + ret = ops->set_soft_start(rdev); + if (ret < 0) { + rdev_err(rdev, "failed to set soft start\n"); + return ret; + } + } + + if (rdev->constraints->over_current_protection + && ops->set_over_current_protection) { + ret = ops->set_over_current_protection(rdev); + if (ret < 0) { + rdev_err(rdev, "failed to set over current protection\n"); + return ret; + } + } + + if (rdev->constraints->active_discharge && ops->set_active_discharge) { + bool ad_state = (rdev->constraints->active_discharge == + REGULATOR_ACTIVE_DISCHARGE_ENABLE) ? true : false; + + ret = ops->set_active_discharge(rdev, ad_state); + if (ret < 0) { + rdev_err(rdev, "failed to set active discharge\n"); + return ret; + } + } + + /* If the constraints say the regulator should be on at this point + * and we have control then make sure it is enabled. + */ + if (rdev->constraints->always_on || rdev->constraints->boot_on) { + /* If we want to enable this regulator, make sure that we know + * the supplying regulator. + */ + if (rdev->supply_name && !rdev->supply) + return -EPROBE_DEFER; + + if (rdev->supply) { + ret = regulator_enable(rdev->supply); + if (ret < 0) { + _regulator_put(rdev->supply); + rdev->supply = NULL; + return ret; + } + } + + ret = _regulator_do_enable(rdev); + if (ret < 0 && ret != -EINVAL) { + rdev_err(rdev, "failed to enable\n"); + return ret; + } + + if (rdev->constraints->always_on) + rdev->use_count++; + } + + print_constraints(rdev); + return 0; +} + +/** + * set_supply - set regulator supply regulator + * @rdev: regulator name + * @supply_rdev: supply regulator name + * + * Called by platform initialisation code to set the supply regulator for this + * regulator. This ensures that a regulators supply will also be enabled by the + * core if it's child is enabled. + */ +static int set_supply(struct regulator_dev *rdev, + struct regulator_dev *supply_rdev) +{ + int err; + + rdev_info(rdev, "supplied by %s\n", rdev_get_name(supply_rdev)); + + if (!try_module_get(supply_rdev->owner)) + return -ENODEV; + + rdev->supply = create_regulator(supply_rdev, &rdev->dev, "SUPPLY"); + if (rdev->supply == NULL) { + err = -ENOMEM; + return err; + } + supply_rdev->open_count++; + + return 0; +} + +/** + * set_consumer_device_supply - Bind a regulator to a symbolic supply + * @rdev: regulator source + * @consumer_dev_name: dev_name() string for device supply applies to + * @supply: symbolic name for supply + * + * Allows platform initialisation code to map physical regulator + * sources to symbolic names for supplies for use by devices. Devices + * should use these symbolic names to request regulators, avoiding the + * need to provide board-specific regulator names as platform data. + */ +static int set_consumer_device_supply(struct regulator_dev *rdev, + const char *consumer_dev_name, + const char *supply) +{ + struct regulator_map *node, *new_node; + int has_dev; + + if (supply == NULL) + return -EINVAL; + + if (consumer_dev_name != NULL) + has_dev = 1; + else + has_dev = 0; + + new_node = kzalloc(sizeof(struct regulator_map), GFP_KERNEL); + if (new_node == NULL) + return -ENOMEM; + + new_node->regulator = rdev; + new_node->supply = supply; + + if (has_dev) { + new_node->dev_name = kstrdup(consumer_dev_name, GFP_KERNEL); + if (new_node->dev_name == NULL) { + kfree(new_node); + return -ENOMEM; + } + } + + mutex_lock(®ulator_list_mutex); + list_for_each_entry(node, ®ulator_map_list, list) { + if (node->dev_name && consumer_dev_name) { + if (strcmp(node->dev_name, consumer_dev_name) != 0) + continue; + } else if (node->dev_name || consumer_dev_name) { + continue; + } + + if (strcmp(node->supply, supply) != 0) + continue; + + pr_debug("%s: %s/%s is '%s' supply; fail %s/%s\n", + consumer_dev_name, + dev_name(&node->regulator->dev), + node->regulator->desc->name, + supply, + dev_name(&rdev->dev), rdev_get_name(rdev)); + goto fail; + } + + list_add(&new_node->list, ®ulator_map_list); + mutex_unlock(®ulator_list_mutex); + + return 0; + +fail: + mutex_unlock(®ulator_list_mutex); + kfree(new_node->dev_name); + kfree(new_node); + return -EBUSY; +} + +static void unset_regulator_supplies(struct regulator_dev *rdev) +{ + struct regulator_map *node, *n; + + list_for_each_entry_safe(node, n, ®ulator_map_list, list) { + if (rdev == node->regulator) { + list_del(&node->list); + kfree(node->dev_name); + kfree(node); + } + } +} + +#ifdef CONFIG_DEBUG_FS +static ssize_t constraint_flags_read_file(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + const struct regulator *regulator = file->private_data; + const struct regulation_constraints *c = regulator->rdev->constraints; + char *buf; + ssize_t ret; + + if (!c) + return 0; + + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = snprintf(buf, PAGE_SIZE, + "always_on: %u\n" + "boot_on: %u\n" + "apply_uV: %u\n" + "ramp_disable: %u\n" + "soft_start: %u\n" + "pull_down: %u\n" + "over_current_protection: %u\n", + c->always_on, + c->boot_on, + c->apply_uV, + c->ramp_disable, + c->soft_start, + c->pull_down, + c->over_current_protection); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + kfree(buf); + + return ret; +} + +#endif + +static const struct file_operations constraint_flags_fops = { +#ifdef CONFIG_DEBUG_FS + .open = simple_open, + .read = constraint_flags_read_file, + .llseek = default_llseek, +#endif +}; + +#define REG_STR_SIZE 64 + +static struct regulator *create_regulator(struct regulator_dev *rdev, + struct device *dev, + const char *supply_name) +{ + struct regulator *regulator; + char buf[REG_STR_SIZE]; + int err, size; + + regulator = kzalloc(sizeof(*regulator), GFP_KERNEL); + if (regulator == NULL) + return NULL; + + regulator_lock(rdev); + regulator->rdev = rdev; + list_add(®ulator->list, &rdev->consumer_list); + + if (dev) { + regulator->dev = dev; + + /* Add a link to the device sysfs entry */ + size = snprintf(buf, REG_STR_SIZE, "%s-%s", + dev->kobj.name, supply_name); + if (size >= REG_STR_SIZE) + goto overflow_err; + + regulator->supply_name = kstrdup(buf, GFP_KERNEL); + if (regulator->supply_name == NULL) + goto overflow_err; + + err = sysfs_create_link_nowarn(&rdev->dev.kobj, &dev->kobj, + buf); + if (err) { + rdev_dbg(rdev, "could not add device link %s err %d\n", + dev->kobj.name, err); + /* non-fatal */ + } + } else { + regulator->supply_name = kstrdup_const(supply_name, GFP_KERNEL); + if (regulator->supply_name == NULL) + goto overflow_err; + } + + regulator->debugfs = debugfs_create_dir(regulator->supply_name, + rdev->debugfs); + if (!regulator->debugfs) { + rdev_dbg(rdev, "Failed to create debugfs directory\n"); + } else { + debugfs_create_u32("uA_load", 0444, regulator->debugfs, + ®ulator->uA_load); + debugfs_create_u32("min_uV", 0444, regulator->debugfs, + ®ulator->voltage[PM_SUSPEND_ON].min_uV); + debugfs_create_u32("max_uV", 0444, regulator->debugfs, + ®ulator->voltage[PM_SUSPEND_ON].max_uV); + debugfs_create_file("constraint_flags", 0444, + regulator->debugfs, regulator, + &constraint_flags_fops); + } + + /* + * Check now if the regulator is an always on regulator - if + * it is then we don't need to do nearly so much work for + * enable/disable calls. + */ + if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_STATUS) && + _regulator_is_enabled(rdev)) + regulator->always_on = true; + + regulator_unlock(rdev); + return regulator; +overflow_err: + list_del(®ulator->list); + kfree(regulator); + regulator_unlock(rdev); + return NULL; +} + +static int _regulator_get_enable_time(struct regulator_dev *rdev) +{ + if (rdev->constraints && rdev->constraints->enable_time) + return rdev->constraints->enable_time; + if (!rdev->desc->ops->enable_time) + return rdev->desc->enable_time; + return rdev->desc->ops->enable_time(rdev); +} + +static struct regulator_supply_alias *regulator_find_supply_alias( + struct device *dev, const char *supply) +{ + struct regulator_supply_alias *map; + + list_for_each_entry(map, ®ulator_supply_alias_list, list) + if (map->src_dev == dev && strcmp(map->src_supply, supply) == 0) + return map; + + return NULL; +} + +static void regulator_supply_alias(struct device **dev, const char **supply) +{ + struct regulator_supply_alias *map; + + map = regulator_find_supply_alias(*dev, *supply); + if (map) { + dev_dbg(*dev, "Mapping supply %s to %s,%s\n", + *supply, map->alias_supply, + dev_name(map->alias_dev)); + *dev = map->alias_dev; + *supply = map->alias_supply; + } +} + +static int regulator_match(struct device *dev, const void *data) +{ + struct regulator_dev *r = dev_to_rdev(dev); + + return strcmp(rdev_get_name(r), data) == 0; +} + +static struct regulator_dev *regulator_lookup_by_name(const char *name) +{ + struct device *dev; + + dev = class_find_device(®ulator_class, NULL, name, regulator_match); + + return dev ? dev_to_rdev(dev) : NULL; +} + +/** + * regulator_dev_lookup - lookup a regulator device. + * @dev: device for regulator "consumer". + * @supply: Supply name or regulator ID. + * + * If successful, returns a struct regulator_dev that corresponds to the name + * @supply and with the embedded struct device refcount incremented by one. + * The refcount must be dropped by calling put_device(). + * On failure one of the following ERR-PTR-encoded values is returned: + * -ENODEV if lookup fails permanently, -EPROBE_DEFER if lookup could succeed + * in the future. + */ +static struct regulator_dev *regulator_dev_lookup(struct device *dev, + const char *supply) +{ + struct regulator_dev *r = NULL; + struct device_node *node; + struct regulator_map *map; + const char *devname = NULL; + + regulator_supply_alias(&dev, &supply); + + /* first do a dt based lookup */ + if (dev && dev->of_node) { + node = of_get_regulator(dev, supply); + if (node) { + r = of_find_regulator_by_node(node); + if (r) + return r; + + /* + * We have a node, but there is no device. + * assume it has not registered yet. + */ + return ERR_PTR(-EPROBE_DEFER); + } + } + + /* if not found, try doing it non-dt way */ + if (dev) + devname = dev_name(dev); + + mutex_lock(®ulator_list_mutex); + list_for_each_entry(map, ®ulator_map_list, list) { + /* If the mapping has a device set up it must match */ + if (map->dev_name && + (!devname || strcmp(map->dev_name, devname))) + continue; + + if (strcmp(map->supply, supply) == 0 && + get_device(&map->regulator->dev)) { + r = map->regulator; + break; + } + } + mutex_unlock(®ulator_list_mutex); + + if (r) + return r; + + r = regulator_lookup_by_name(supply); + if (r) + return r; + + return ERR_PTR(-ENODEV); +} + +static int regulator_resolve_supply(struct regulator_dev *rdev) +{ + struct regulator_dev *r; + struct device *dev = rdev->dev.parent; + int ret = 0; + + /* No supply to resovle? */ + if (!rdev->supply_name) + return 0; + + /* Supply already resolved? (fast-path without locking contention) */ + if (rdev->supply) + return 0; + + r = regulator_dev_lookup(dev, rdev->supply_name); + if (IS_ERR(r)) { + ret = PTR_ERR(r); + + /* Did the lookup explicitly defer for us? */ + if (ret == -EPROBE_DEFER) + goto out; + + if (have_full_constraints()) { + r = dummy_regulator_rdev; + get_device(&r->dev); + } else { + dev_err(dev, "Failed to resolve %s-supply for %s\n", + rdev->supply_name, rdev->desc->name); + ret = -EPROBE_DEFER; + goto out; + } + } + + if (r == rdev) { + dev_err(dev, "Supply for %s (%s) resolved to itself\n", + rdev->desc->name, rdev->supply_name); + if (!have_full_constraints()) { + ret = -EINVAL; + goto out; + } + r = dummy_regulator_rdev; + get_device(&r->dev); + } + + /* + * If the supply's parent device is not the same as the + * regulator's parent device, then ensure the parent device + * is bound before we resolve the supply, in case the parent + * device get probe deferred and unregisters the supply. + */ + if (r->dev.parent && r->dev.parent != rdev->dev.parent) { + if (!device_is_bound(r->dev.parent)) { + put_device(&r->dev); + ret = -EPROBE_DEFER; + goto out; + } + } + + /* Recursively resolve the supply of the supply */ + ret = regulator_resolve_supply(r); + if (ret < 0) { + put_device(&r->dev); + goto out; + } + + /* + * Recheck rdev->supply with rdev->mutex lock held to avoid a race + * between rdev->supply null check and setting rdev->supply in + * set_supply() from concurrent tasks. + */ + regulator_lock(rdev); + + /* Supply just resolved by a concurrent task? */ + if (rdev->supply) { + regulator_unlock(rdev); + put_device(&r->dev); + goto out; + } + + ret = set_supply(rdev, r); + if (ret < 0) { + regulator_unlock(rdev); + put_device(&r->dev); + goto out; + } + + regulator_unlock(rdev); + + /* + * In set_machine_constraints() we may have turned this regulator on + * but we couldn't propagate to the supply if it hadn't been resolved + * yet. Do it now. + */ + if (rdev->use_count) { + ret = regulator_enable(rdev->supply); + if (ret < 0) { + _regulator_put(rdev->supply); + rdev->supply = NULL; + goto out; + } + } + +out: + return ret; +} + +/* Internal regulator request function */ +struct regulator *_regulator_get(struct device *dev, const char *id, + enum regulator_get_type get_type) +{ + struct regulator_dev *rdev; + struct regulator *regulator; + const char *devname = dev ? dev_name(dev) : "deviceless"; + int ret; + + if (get_type >= MAX_GET_TYPE) { + dev_err(dev, "invalid type %d in %s\n", get_type, __func__); + return ERR_PTR(-EINVAL); + } + + if (id == NULL) { + pr_err("get() with no identifier\n"); + return ERR_PTR(-EINVAL); + } + + rdev = regulator_dev_lookup(dev, id); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + + /* + * If regulator_dev_lookup() fails with error other + * than -ENODEV our job here is done, we simply return it. + */ + if (ret != -ENODEV) + return ERR_PTR(ret); + + if (!have_full_constraints()) { + dev_warn(dev, + "incomplete constraints, dummy supplies not allowed\n"); + return ERR_PTR(-ENODEV); + } + + switch (get_type) { + case NORMAL_GET: + /* + * Assume that a regulator is physically present and + * enabled, even if it isn't hooked up, and just + * provide a dummy. + */ + dev_warn(dev, + "%s supply %s not found, using dummy regulator\n", + devname, id); + rdev = dummy_regulator_rdev; + get_device(&rdev->dev); + break; + + case EXCLUSIVE_GET: + dev_warn(dev, + "dummy supplies not allowed for exclusive requests\n"); + /* fall through */ + + default: + return ERR_PTR(-ENODEV); + } + } + + if (rdev->exclusive) { + regulator = ERR_PTR(-EPERM); + put_device(&rdev->dev); + return regulator; + } + + if (get_type == EXCLUSIVE_GET && rdev->open_count) { + regulator = ERR_PTR(-EBUSY); + put_device(&rdev->dev); + return regulator; + } + + ret = regulator_resolve_supply(rdev); + if (ret < 0) { + regulator = ERR_PTR(ret); + put_device(&rdev->dev); + return regulator; + } + + if (!try_module_get(rdev->owner)) { + regulator = ERR_PTR(-EPROBE_DEFER); + put_device(&rdev->dev); + return regulator; + } + + regulator = create_regulator(rdev, dev, id); + if (regulator == NULL) { + regulator = ERR_PTR(-ENOMEM); + module_put(rdev->owner); + put_device(&rdev->dev); + return regulator; + } + + rdev->open_count++; + if (get_type == EXCLUSIVE_GET) { + rdev->exclusive = 1; + + ret = _regulator_is_enabled(rdev); + if (ret > 0) + rdev->use_count = 1; + else + rdev->use_count = 0; + } + + device_link_add(dev, &rdev->dev, DL_FLAG_STATELESS); + + return regulator; +} + +/** + * regulator_get - lookup and obtain a reference to a regulator. + * @dev: device for regulator "consumer" + * @id: Supply name or regulator ID. + * + * Returns a struct regulator corresponding to the regulator producer, + * or IS_ERR() condition containing errno. + * + * Use of supply names configured via regulator_set_device_supply() is + * strongly encouraged. It is recommended that the supply name used + * should match the name used for the supply and/or the relevant + * device pins in the datasheet. + */ +struct regulator *regulator_get(struct device *dev, const char *id) +{ + return _regulator_get(dev, id, NORMAL_GET); +} +EXPORT_SYMBOL_GPL(regulator_get); + +/** + * regulator_get_exclusive - obtain exclusive access to a regulator. + * @dev: device for regulator "consumer" + * @id: Supply name or regulator ID. + * + * Returns a struct regulator corresponding to the regulator producer, + * or IS_ERR() condition containing errno. Other consumers will be + * unable to obtain this regulator while this reference is held and the + * use count for the regulator will be initialised to reflect the current + * state of the regulator. + * + * This is intended for use by consumers which cannot tolerate shared + * use of the regulator such as those which need to force the + * regulator off for correct operation of the hardware they are + * controlling. + * + * Use of supply names configured via regulator_set_device_supply() is + * strongly encouraged. It is recommended that the supply name used + * should match the name used for the supply and/or the relevant + * device pins in the datasheet. + */ +struct regulator *regulator_get_exclusive(struct device *dev, const char *id) +{ + return _regulator_get(dev, id, EXCLUSIVE_GET); +} +EXPORT_SYMBOL_GPL(regulator_get_exclusive); + +/** + * regulator_get_optional - obtain optional access to a regulator. + * @dev: device for regulator "consumer" + * @id: Supply name or regulator ID. + * + * Returns a struct regulator corresponding to the regulator producer, + * or IS_ERR() condition containing errno. + * + * This is intended for use by consumers for devices which can have + * some supplies unconnected in normal use, such as some MMC devices. + * It can allow the regulator core to provide stub supplies for other + * supplies requested using normal regulator_get() calls without + * disrupting the operation of drivers that can handle absent + * supplies. + * + * Use of supply names configured via regulator_set_device_supply() is + * strongly encouraged. It is recommended that the supply name used + * should match the name used for the supply and/or the relevant + * device pins in the datasheet. + */ +struct regulator *regulator_get_optional(struct device *dev, const char *id) +{ + return _regulator_get(dev, id, OPTIONAL_GET); +} +EXPORT_SYMBOL_GPL(regulator_get_optional); + +/* regulator_list_mutex lock held by regulator_put() */ +static void _regulator_put(struct regulator *regulator) +{ + struct regulator_dev *rdev; + + if (IS_ERR_OR_NULL(regulator)) + return; + + lockdep_assert_held_once(®ulator_list_mutex); + + rdev = regulator->rdev; + + debugfs_remove_recursive(regulator->debugfs); + + if (regulator->dev) { + int count = 0; + struct regulator *r; + + list_for_each_entry(r, &rdev->consumer_list, list) + if (r->dev == regulator->dev) + count++; + + if (count == 1) + device_link_remove(regulator->dev, &rdev->dev); + + /* remove any sysfs entries */ + sysfs_remove_link(&rdev->dev.kobj, regulator->supply_name); + } + + regulator_lock(rdev); + list_del(®ulator->list); + + rdev->open_count--; + rdev->exclusive = 0; + regulator_unlock(rdev); + + kfree_const(regulator->supply_name); + kfree(regulator); + + module_put(rdev->owner); + put_device(&rdev->dev); +} + +/** + * regulator_put - "free" the regulator source + * @regulator: regulator source + * + * Note: drivers must ensure that all regulator_enable calls made on this + * regulator source are balanced by regulator_disable calls prior to calling + * this function. + */ +void regulator_put(struct regulator *regulator) +{ + mutex_lock(®ulator_list_mutex); + _regulator_put(regulator); + mutex_unlock(®ulator_list_mutex); +} +EXPORT_SYMBOL_GPL(regulator_put); + +/** + * regulator_register_supply_alias - Provide device alias for supply lookup + * + * @dev: device that will be given as the regulator "consumer" + * @id: Supply name or regulator ID + * @alias_dev: device that should be used to lookup the supply + * @alias_id: Supply name or regulator ID that should be used to lookup the + * supply + * + * All lookups for id on dev will instead be conducted for alias_id on + * alias_dev. + */ +int regulator_register_supply_alias(struct device *dev, const char *id, + struct device *alias_dev, + const char *alias_id) +{ + struct regulator_supply_alias *map; + + map = regulator_find_supply_alias(dev, id); + if (map) + return -EEXIST; + + map = kzalloc(sizeof(struct regulator_supply_alias), GFP_KERNEL); + if (!map) + return -ENOMEM; + + map->src_dev = dev; + map->src_supply = id; + map->alias_dev = alias_dev; + map->alias_supply = alias_id; + + list_add(&map->list, ®ulator_supply_alias_list); + + pr_info("Adding alias for supply %s,%s -> %s,%s\n", + id, dev_name(dev), alias_id, dev_name(alias_dev)); + + return 0; +} +EXPORT_SYMBOL_GPL(regulator_register_supply_alias); + +/** + * regulator_unregister_supply_alias - Remove device alias + * + * @dev: device that will be given as the regulator "consumer" + * @id: Supply name or regulator ID + * + * Remove a lookup alias if one exists for id on dev. + */ +void regulator_unregister_supply_alias(struct device *dev, const char *id) +{ + struct regulator_supply_alias *map; + + map = regulator_find_supply_alias(dev, id); + if (map) { + list_del(&map->list); + kfree(map); + } +} +EXPORT_SYMBOL_GPL(regulator_unregister_supply_alias); + +/** + * regulator_bulk_register_supply_alias - register multiple aliases + * + * @dev: device that will be given as the regulator "consumer" + * @id: List of supply names or regulator IDs + * @alias_dev: device that should be used to lookup the supply + * @alias_id: List of supply names or regulator IDs that should be used to + * lookup the supply + * @num_id: Number of aliases to register + * + * @return 0 on success, an errno on failure. + * + * This helper function allows drivers to register several supply + * aliases in one operation. If any of the aliases cannot be + * registered any aliases that were registered will be removed + * before returning to the caller. + */ +int regulator_bulk_register_supply_alias(struct device *dev, + const char *const *id, + struct device *alias_dev, + const char *const *alias_id, + int num_id) +{ + int i; + int ret; + + for (i = 0; i < num_id; ++i) { + ret = regulator_register_supply_alias(dev, id[i], alias_dev, + alias_id[i]); + if (ret < 0) + goto err; + } + + return 0; + +err: + dev_err(dev, + "Failed to create supply alias %s,%s -> %s,%s\n", + id[i], dev_name(dev), alias_id[i], dev_name(alias_dev)); + + while (--i >= 0) + regulator_unregister_supply_alias(dev, id[i]); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_bulk_register_supply_alias); + +/** + * regulator_bulk_unregister_supply_alias - unregister multiple aliases + * + * @dev: device that will be given as the regulator "consumer" + * @id: List of supply names or regulator IDs + * @num_id: Number of aliases to unregister + * + * This helper function allows drivers to unregister several supply + * aliases in one operation. + */ +void regulator_bulk_unregister_supply_alias(struct device *dev, + const char *const *id, + int num_id) +{ + int i; + + for (i = 0; i < num_id; ++i) + regulator_unregister_supply_alias(dev, id[i]); +} +EXPORT_SYMBOL_GPL(regulator_bulk_unregister_supply_alias); + + +/* Manage enable GPIO list. Same GPIO pin can be shared among regulators */ +static int regulator_ena_gpio_request(struct regulator_dev *rdev, + const struct regulator_config *config) +{ + struct regulator_enable_gpio *pin; + struct gpio_desc *gpiod; + int ret; + + if (config->ena_gpiod) + gpiod = config->ena_gpiod; + else + gpiod = gpio_to_desc(config->ena_gpio); + + list_for_each_entry(pin, ®ulator_ena_gpio_list, list) { + if (pin->gpiod == gpiod) { + rdev_dbg(rdev, "GPIO %d is already used\n", + config->ena_gpio); + goto update_ena_gpio_to_rdev; + } + } + + if (!config->ena_gpiod) { + ret = gpio_request_one(config->ena_gpio, + GPIOF_DIR_OUT | config->ena_gpio_flags, + rdev_get_name(rdev)); + if (ret) + return ret; + } + + pin = kzalloc(sizeof(struct regulator_enable_gpio), GFP_KERNEL); + if (pin == NULL) { + if (!config->ena_gpiod) + gpio_free(config->ena_gpio); + return -ENOMEM; + } + + pin->gpiod = gpiod; + pin->ena_gpio_invert = config->ena_gpio_invert; + list_add(&pin->list, ®ulator_ena_gpio_list); + +update_ena_gpio_to_rdev: + pin->request_count++; + rdev->ena_pin = pin; + return 0; +} + +static void regulator_ena_gpio_free(struct regulator_dev *rdev) +{ + struct regulator_enable_gpio *pin, *n; + + if (!rdev->ena_pin) + return; + + /* Free the GPIO only in case of no use */ + list_for_each_entry_safe(pin, n, ®ulator_ena_gpio_list, list) { + if (pin->gpiod == rdev->ena_pin->gpiod) { + if (pin->request_count <= 1) { + pin->request_count = 0; + gpiod_put(pin->gpiod); + list_del(&pin->list); + kfree(pin); + rdev->ena_pin = NULL; + return; + } else { + pin->request_count--; + } + } + } +} + +/** + * regulator_ena_gpio_ctrl - balance enable_count of each GPIO and actual GPIO pin control + * @rdev: regulator_dev structure + * @enable: enable GPIO at initial use? + * + * GPIO is enabled in case of initial use. (enable_count is 0) + * GPIO is disabled when it is not shared any more. (enable_count <= 1) + */ +static int regulator_ena_gpio_ctrl(struct regulator_dev *rdev, bool enable) +{ + struct regulator_enable_gpio *pin = rdev->ena_pin; + + if (!pin) + return -EINVAL; + + if (enable) { + /* Enable GPIO at initial use */ + if (pin->enable_count == 0) + gpiod_set_value_cansleep(pin->gpiod, + !pin->ena_gpio_invert); + + pin->enable_count++; + } else { + if (pin->enable_count > 1) { + pin->enable_count--; + return 0; + } + + /* Disable GPIO if not used */ + if (pin->enable_count <= 1) { + gpiod_set_value_cansleep(pin->gpiod, + pin->ena_gpio_invert); + pin->enable_count = 0; + } + } + + return 0; +} + +/** + * _regulator_enable_delay - a delay helper function + * @delay: time to delay in microseconds + * + * Delay for the requested amount of time as per the guidelines in: + * + * Documentation/timers/timers-howto.txt + * + * The assumption here is that regulators will never be enabled in + * atomic context and therefore sleeping functions can be used. + */ +static void _regulator_enable_delay(unsigned int delay) +{ + unsigned int ms = delay / 1000; + unsigned int us = delay % 1000; + + if (ms > 0) { + /* + * For small enough values, handle super-millisecond + * delays in the usleep_range() call below. + */ + if (ms < 20) + us += ms * 1000; + else + msleep(ms); + } + + /* + * Give the scheduler some room to coalesce with any other + * wakeup sources. For delays shorter than 10 us, don't even + * bother setting up high-resolution timers and just busy- + * loop. + */ + if (us >= 10) + usleep_range(us, us + 100); + else + udelay(us); +} + +static int _regulator_do_enable(struct regulator_dev *rdev) +{ + int ret, delay; + + /* Query before enabling in case configuration dependent. */ + ret = _regulator_get_enable_time(rdev); + if (ret >= 0) { + delay = ret; + } else { + rdev_warn(rdev, "enable_time() failed: %d\n", ret); + delay = 0; + } + + trace_regulator_enable(rdev_get_name(rdev)); + + if (rdev->desc->off_on_delay) { + /* if needed, keep a distance of off_on_delay from last time + * this regulator was disabled. + */ + unsigned long start_jiffy = jiffies; + unsigned long intended, max_delay, remaining; + + max_delay = usecs_to_jiffies(rdev->desc->off_on_delay); + intended = rdev->last_off_jiffy + max_delay; + + if (time_before(start_jiffy, intended)) { + /* calc remaining jiffies to deal with one-time + * timer wrapping. + * in case of multiple timer wrapping, either it can be + * detected by out-of-range remaining, or it cannot be + * detected and we gets a panelty of + * _regulator_enable_delay(). + */ + remaining = intended - start_jiffy; + if (remaining <= max_delay) + _regulator_enable_delay( + jiffies_to_usecs(remaining)); + } + } + + if (rdev->ena_pin) { + if (!rdev->ena_gpio_state) { + ret = regulator_ena_gpio_ctrl(rdev, true); + if (ret < 0) + return ret; + rdev->ena_gpio_state = 1; + } + } else if (rdev->desc->ops->enable) { + ret = rdev->desc->ops->enable(rdev); + if (ret < 0) + return ret; + } else { + return -EINVAL; + } + + /* Allow the regulator to ramp; it would be useful to extend + * this for bulk operations so that the regulators can ramp + * together. */ + trace_regulator_enable_delay(rdev_get_name(rdev)); + + _regulator_enable_delay(delay); + + trace_regulator_enable_complete(rdev_get_name(rdev)); + + return 0; +} + +/* locks held by regulator_enable() */ +static int _regulator_enable(struct regulator_dev *rdev) +{ + int ret; + + lockdep_assert_held_once(&rdev->mutex); + + /* check voltage and requested load before enabling */ + if (regulator_ops_is_valid(rdev, REGULATOR_CHANGE_DRMS)) + drms_uA_update(rdev); + + if (rdev->use_count == 0) { + /* The regulator may on if it's not switchable or left on */ + ret = _regulator_is_enabled(rdev); + if (ret == -EINVAL || ret == 0) { + if (!regulator_ops_is_valid(rdev, + REGULATOR_CHANGE_STATUS)) + return -EPERM; + + ret = _regulator_do_enable(rdev); + if (ret < 0) + return ret; + + _notifier_call_chain(rdev, REGULATOR_EVENT_ENABLE, + NULL); + } else if (ret < 0) { + rdev_err(rdev, "is_enabled() failed: %d\n", ret); + return ret; + } + /* Fallthrough on positive return values - already enabled */ + } + + rdev->use_count++; + + return 0; +} + +/** + * regulator_enable - enable regulator output + * @regulator: regulator source + * + * Request that the regulator be enabled with the regulator output at + * the predefined voltage or current value. Calls to regulator_enable() + * must be balanced with calls to regulator_disable(). + * + * NOTE: the output value can be set by other drivers, boot loader or may be + * hardwired in the regulator. + */ +int regulator_enable(struct regulator *regulator) +{ + struct regulator_dev *rdev = regulator->rdev; + int ret = 0; + + if (regulator->always_on) + return 0; + + if (rdev->supply) { + ret = regulator_enable(rdev->supply); + if (ret != 0) + return ret; + } + + mutex_lock(&rdev->mutex); + ret = _regulator_enable(rdev); + mutex_unlock(&rdev->mutex); + + if (ret != 0 && rdev->supply) + regulator_disable(rdev->supply); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_enable); + +static int _regulator_do_disable(struct regulator_dev *rdev) +{ + int ret; + + trace_regulator_disable(rdev_get_name(rdev)); + + if (rdev->ena_pin) { + if (rdev->ena_gpio_state) { + ret = regulator_ena_gpio_ctrl(rdev, false); + if (ret < 0) + return ret; + rdev->ena_gpio_state = 0; + } + + } else if (rdev->desc->ops->disable) { + ret = rdev->desc->ops->disable(rdev); + if (ret != 0) + return ret; + } + + /* cares about last_off_jiffy only if off_on_delay is required by + * device. + */ + if (rdev->desc->off_on_delay) + rdev->last_off_jiffy = jiffies; + + trace_regulator_disable_complete(rdev_get_name(rdev)); + + return 0; +} + +/* locks held by regulator_disable() */ +static int _regulator_disable(struct regulator_dev *rdev) +{ + int ret = 0; + + lockdep_assert_held_once(&rdev->mutex); + + if (WARN(rdev->use_count <= 0, + "unbalanced disables for %s\n", rdev_get_name(rdev))) + return -EIO; + + /* are we the last user and permitted to disable ? */ + if (rdev->use_count == 1 && + (rdev->constraints && !rdev->constraints->always_on)) { + + /* we are last user */ + if (regulator_ops_is_valid(rdev, REGULATOR_CHANGE_STATUS)) { + ret = _notifier_call_chain(rdev, + REGULATOR_EVENT_PRE_DISABLE, + NULL); + if (ret & NOTIFY_STOP_MASK) + return -EINVAL; + + ret = _regulator_do_disable(rdev); + if (ret < 0) { + rdev_err(rdev, "failed to disable\n"); + _notifier_call_chain(rdev, + REGULATOR_EVENT_ABORT_DISABLE, + NULL); + return ret; + } + _notifier_call_chain(rdev, REGULATOR_EVENT_DISABLE, + NULL); + } + + rdev->use_count = 0; + } else if (rdev->use_count > 1) { + if (regulator_ops_is_valid(rdev, REGULATOR_CHANGE_DRMS)) + drms_uA_update(rdev); + + rdev->use_count--; + } + + return ret; +} + +/** + * regulator_disable - disable regulator output + * @regulator: regulator source + * + * Disable the regulator output voltage or current. Calls to + * regulator_enable() must be balanced with calls to + * regulator_disable(). + * + * NOTE: this will only disable the regulator output if no other consumer + * devices have it enabled, the regulator device supports disabling and + * machine constraints permit this operation. + */ +int regulator_disable(struct regulator *regulator) +{ + struct regulator_dev *rdev = regulator->rdev; + int ret = 0; + + if (regulator->always_on) + return 0; + + mutex_lock(&rdev->mutex); + ret = _regulator_disable(rdev); + mutex_unlock(&rdev->mutex); + + if (ret == 0 && rdev->supply) + regulator_disable(rdev->supply); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_disable); + +/* locks held by regulator_force_disable() */ +static int _regulator_force_disable(struct regulator_dev *rdev) +{ + int ret = 0; + + lockdep_assert_held_once(&rdev->mutex); + + ret = _notifier_call_chain(rdev, REGULATOR_EVENT_FORCE_DISABLE | + REGULATOR_EVENT_PRE_DISABLE, NULL); + if (ret & NOTIFY_STOP_MASK) + return -EINVAL; + + ret = _regulator_do_disable(rdev); + if (ret < 0) { + rdev_err(rdev, "failed to force disable\n"); + _notifier_call_chain(rdev, REGULATOR_EVENT_FORCE_DISABLE | + REGULATOR_EVENT_ABORT_DISABLE, NULL); + return ret; + } + + _notifier_call_chain(rdev, REGULATOR_EVENT_FORCE_DISABLE | + REGULATOR_EVENT_DISABLE, NULL); + + return 0; +} + +/** + * regulator_force_disable - force disable regulator output + * @regulator: regulator source + * + * Forcibly disable the regulator output voltage or current. + * NOTE: this *will* disable the regulator output even if other consumer + * devices have it enabled. This should be used for situations when device + * damage will likely occur if the regulator is not disabled (e.g. over temp). + */ +int regulator_force_disable(struct regulator *regulator) +{ + struct regulator_dev *rdev = regulator->rdev; + int ret; + + mutex_lock(&rdev->mutex); + regulator->uA_load = 0; + ret = _regulator_force_disable(regulator->rdev); + mutex_unlock(&rdev->mutex); + + if (rdev->supply) + while (rdev->open_count--) + regulator_disable(rdev->supply); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_force_disable); + +static void regulator_disable_work(struct work_struct *work) +{ + struct regulator_dev *rdev = container_of(work, struct regulator_dev, + disable_work.work); + int count, i, ret; + + regulator_lock(rdev); + + BUG_ON(!rdev->deferred_disables); + + count = rdev->deferred_disables; + rdev->deferred_disables = 0; + + /* + * Workqueue functions queue the new work instance while the previous + * work instance is being processed. Cancel the queued work instance + * as the work instance under processing does the job of the queued + * work instance. + */ + cancel_delayed_work(&rdev->disable_work); + + for (i = 0; i < count; i++) { + ret = _regulator_disable(rdev); + if (ret != 0) + rdev_err(rdev, "Deferred disable failed: %d\n", ret); + } + + regulator_unlock(rdev); + + if (rdev->supply) { + for (i = 0; i < count; i++) { + ret = regulator_disable(rdev->supply); + if (ret != 0) { + rdev_err(rdev, + "Supply disable failed: %d\n", ret); + } + } + } +} + +/** + * regulator_disable_deferred - disable regulator output with delay + * @regulator: regulator source + * @ms: miliseconds until the regulator is disabled + * + * Execute regulator_disable() on the regulator after a delay. This + * is intended for use with devices that require some time to quiesce. + * + * NOTE: this will only disable the regulator output if no other consumer + * devices have it enabled, the regulator device supports disabling and + * machine constraints permit this operation. + */ +int regulator_disable_deferred(struct regulator *regulator, int ms) +{ + struct regulator_dev *rdev = regulator->rdev; + + if (regulator->always_on) + return 0; + + if (!ms) + return regulator_disable(regulator); + + regulator_lock(rdev); + rdev->deferred_disables++; + mod_delayed_work(system_power_efficient_wq, &rdev->disable_work, + msecs_to_jiffies(ms)); + regulator_unlock(rdev); + + return 0; +} +EXPORT_SYMBOL_GPL(regulator_disable_deferred); + +static int _regulator_is_enabled(struct regulator_dev *rdev) +{ + /* A GPIO control always takes precedence */ + if (rdev->ena_pin) + return rdev->ena_gpio_state; + + /* If we don't know then assume that the regulator is always on */ + if (!rdev->desc->ops->is_enabled) + return 1; + + return rdev->desc->ops->is_enabled(rdev); +} + +static int _regulator_list_voltage(struct regulator_dev *rdev, + unsigned selector, int lock) +{ + const struct regulator_ops *ops = rdev->desc->ops; + int ret; + + if (rdev->desc->fixed_uV && rdev->desc->n_voltages == 1 && !selector) + return rdev->desc->fixed_uV; + + if (ops->list_voltage) { + if (selector >= rdev->desc->n_voltages) + return -EINVAL; + if (lock) + regulator_lock(rdev); + ret = ops->list_voltage(rdev, selector); + if (lock) + regulator_unlock(rdev); + } else if (rdev->is_switch && rdev->supply) { + ret = _regulator_list_voltage(rdev->supply->rdev, + selector, lock); + } else { + return -EINVAL; + } + + if (ret > 0) { + if (ret < rdev->constraints->min_uV) + ret = 0; + else if (ret > rdev->constraints->max_uV) + ret = 0; + } + + return ret; +} + +/** + * regulator_is_enabled - is the regulator output enabled + * @regulator: regulator source + * + * Returns positive if the regulator driver backing the source/client + * has requested that the device be enabled, zero if it hasn't, else a + * negative errno code. + * + * Note that the device backing this regulator handle can have multiple + * users, so it might be enabled even if regulator_enable() was never + * called for this particular source. + */ +int regulator_is_enabled(struct regulator *regulator) +{ + int ret; + + if (regulator->always_on) + return 1; + + mutex_lock(®ulator->rdev->mutex); + ret = _regulator_is_enabled(regulator->rdev); + mutex_unlock(®ulator->rdev->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_is_enabled); + +/** + * regulator_count_voltages - count regulator_list_voltage() selectors + * @regulator: regulator source + * + * Returns number of selectors, or negative errno. Selectors are + * numbered starting at zero, and typically correspond to bitfields + * in hardware registers. + */ +int regulator_count_voltages(struct regulator *regulator) +{ + struct regulator_dev *rdev = regulator->rdev; + + if (rdev->desc->n_voltages) + return rdev->desc->n_voltages; + + if (!rdev->is_switch || !rdev->supply) + return -EINVAL; + + return regulator_count_voltages(rdev->supply); +} +EXPORT_SYMBOL_GPL(regulator_count_voltages); + +/** + * regulator_list_voltage - enumerate supported voltages + * @regulator: regulator source + * @selector: identify voltage to list + * Context: can sleep + * + * Returns a voltage that can be passed to @regulator_set_voltage(), + * zero if this selector code can't be used on this system, or a + * negative errno. + */ +int regulator_list_voltage(struct regulator *regulator, unsigned selector) +{ + return _regulator_list_voltage(regulator->rdev, selector, 1); +} +EXPORT_SYMBOL_GPL(regulator_list_voltage); + +/** + * regulator_get_regmap - get the regulator's register map + * @regulator: regulator source + * + * Returns the register map for the given regulator, or an ERR_PTR value + * if the regulator doesn't use regmap. + */ +struct regmap *regulator_get_regmap(struct regulator *regulator) +{ + struct regmap *map = regulator->rdev->regmap; + + return map ? map : ERR_PTR(-EOPNOTSUPP); +} + +/** + * regulator_get_hardware_vsel_register - get the HW voltage selector register + * @regulator: regulator source + * @vsel_reg: voltage selector register, output parameter + * @vsel_mask: mask for voltage selector bitfield, output parameter + * + * Returns the hardware register offset and bitmask used for setting the + * regulator voltage. This might be useful when configuring voltage-scaling + * hardware or firmware that can make I2C requests behind the kernel's back, + * for example. + * + * On success, the output parameters @vsel_reg and @vsel_mask are filled in + * and 0 is returned, otherwise a negative errno is returned. + */ +int regulator_get_hardware_vsel_register(struct regulator *regulator, + unsigned *vsel_reg, + unsigned *vsel_mask) +{ + struct regulator_dev *rdev = regulator->rdev; + const struct regulator_ops *ops = rdev->desc->ops; + + if (ops->set_voltage_sel != regulator_set_voltage_sel_regmap) + return -EOPNOTSUPP; + + *vsel_reg = rdev->desc->vsel_reg; + *vsel_mask = rdev->desc->vsel_mask; + + return 0; +} +EXPORT_SYMBOL_GPL(regulator_get_hardware_vsel_register); + +/** + * regulator_list_hardware_vsel - get the HW-specific register value for a selector + * @regulator: regulator source + * @selector: identify voltage to list + * + * Converts the selector to a hardware-specific voltage selector that can be + * directly written to the regulator registers. The address of the voltage + * register can be determined by calling @regulator_get_hardware_vsel_register. + * + * On error a negative errno is returned. + */ +int regulator_list_hardware_vsel(struct regulator *regulator, + unsigned selector) +{ + struct regulator_dev *rdev = regulator->rdev; + const struct regulator_ops *ops = rdev->desc->ops; + + if (selector >= rdev->desc->n_voltages) + return -EINVAL; + if (ops->set_voltage_sel != regulator_set_voltage_sel_regmap) + return -EOPNOTSUPP; + + return selector; +} +EXPORT_SYMBOL_GPL(regulator_list_hardware_vsel); + +/** + * regulator_get_linear_step - return the voltage step size between VSEL values + * @regulator: regulator source + * + * Returns the voltage step size between VSEL values for linear + * regulators, or return 0 if the regulator isn't a linear regulator. + */ +unsigned int regulator_get_linear_step(struct regulator *regulator) +{ + struct regulator_dev *rdev = regulator->rdev; + + return rdev->desc->uV_step; +} +EXPORT_SYMBOL_GPL(regulator_get_linear_step); + +/** + * regulator_is_supported_voltage - check if a voltage range can be supported + * + * @regulator: Regulator to check. + * @min_uV: Minimum required voltage in uV. + * @max_uV: Maximum required voltage in uV. + * + * Returns a boolean or a negative error code. + */ +int regulator_is_supported_voltage(struct regulator *regulator, + int min_uV, int max_uV) +{ + struct regulator_dev *rdev = regulator->rdev; + int i, voltages, ret; + + /* If we can't change voltage check the current voltage */ + if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_VOLTAGE)) { + ret = regulator_get_voltage(regulator); + if (ret >= 0) + return min_uV <= ret && ret <= max_uV; + else + return ret; + } + + /* Any voltage within constrains range is fine? */ + if (rdev->desc->continuous_voltage_range) + return min_uV >= rdev->constraints->min_uV && + max_uV <= rdev->constraints->max_uV; + + ret = regulator_count_voltages(regulator); + if (ret < 0) + return ret; + voltages = ret; + + for (i = 0; i < voltages; i++) { + ret = regulator_list_voltage(regulator, i); + + if (ret >= min_uV && ret <= max_uV) + return 1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(regulator_is_supported_voltage); + +static int regulator_map_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV) +{ + const struct regulator_desc *desc = rdev->desc; + + if (desc->ops->map_voltage) + return desc->ops->map_voltage(rdev, min_uV, max_uV); + + if (desc->ops->list_voltage == regulator_list_voltage_linear) + return regulator_map_voltage_linear(rdev, min_uV, max_uV); + + if (desc->ops->list_voltage == regulator_list_voltage_linear_range) + return regulator_map_voltage_linear_range(rdev, min_uV, max_uV); + + return regulator_map_voltage_iterate(rdev, min_uV, max_uV); +} + +static int _regulator_call_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, + unsigned *selector) +{ + struct pre_voltage_change_data data; + int ret; + + data.old_uV = _regulator_get_voltage(rdev); + data.min_uV = min_uV; + data.max_uV = max_uV; + ret = _notifier_call_chain(rdev, REGULATOR_EVENT_PRE_VOLTAGE_CHANGE, + &data); + if (ret & NOTIFY_STOP_MASK) + return -EINVAL; + + ret = rdev->desc->ops->set_voltage(rdev, min_uV, max_uV, selector); + if (ret >= 0) + return ret; + + _notifier_call_chain(rdev, REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE, + (void *)data.old_uV); + + return ret; +} + +static int _regulator_call_set_voltage_sel(struct regulator_dev *rdev, + int uV, unsigned selector) +{ + struct pre_voltage_change_data data; + int ret; + + data.old_uV = _regulator_get_voltage(rdev); + data.min_uV = uV; + data.max_uV = uV; + ret = _notifier_call_chain(rdev, REGULATOR_EVENT_PRE_VOLTAGE_CHANGE, + &data); + if (ret & NOTIFY_STOP_MASK) + return -EINVAL; + + ret = rdev->desc->ops->set_voltage_sel(rdev, selector); + if (ret >= 0) + return ret; + + _notifier_call_chain(rdev, REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE, + (void *)data.old_uV); + + return ret; +} + +static int _regulator_set_voltage_time(struct regulator_dev *rdev, + int old_uV, int new_uV) +{ + unsigned int ramp_delay = 0; + + if (rdev->constraints->ramp_delay) + ramp_delay = rdev->constraints->ramp_delay; + else if (rdev->desc->ramp_delay) + ramp_delay = rdev->desc->ramp_delay; + else if (rdev->constraints->settling_time) + return rdev->constraints->settling_time; + else if (rdev->constraints->settling_time_up && + (new_uV > old_uV)) + return rdev->constraints->settling_time_up; + else if (rdev->constraints->settling_time_down && + (new_uV < old_uV)) + return rdev->constraints->settling_time_down; + + if (ramp_delay == 0) { + rdev_dbg(rdev, "ramp_delay not set\n"); + return 0; + } + + return DIV_ROUND_UP(abs(new_uV - old_uV), ramp_delay); +} + +static int _regulator_do_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + int ret; + int delay = 0; + int best_val = 0; + unsigned int selector; + int old_selector = -1; + const struct regulator_ops *ops = rdev->desc->ops; + int old_uV = _regulator_get_voltage(rdev); + + trace_regulator_set_voltage(rdev_get_name(rdev), min_uV, max_uV); + + min_uV += rdev->constraints->uV_offset; + max_uV += rdev->constraints->uV_offset; + + /* + * If we can't obtain the old selector there is not enough + * info to call set_voltage_time_sel(). + */ + if (_regulator_is_enabled(rdev) && + ops->set_voltage_time_sel && ops->get_voltage_sel) { + old_selector = ops->get_voltage_sel(rdev); + if (old_selector < 0) + return old_selector; + } + + if (ops->set_voltage) { + ret = _regulator_call_set_voltage(rdev, min_uV, max_uV, + &selector); + + if (ret >= 0) { + if (ops->list_voltage) + best_val = ops->list_voltage(rdev, + selector); + else + best_val = _regulator_get_voltage(rdev); + } + + } else if (ops->set_voltage_sel) { + ret = regulator_map_voltage(rdev, min_uV, max_uV); + if (ret >= 0) { + best_val = ops->list_voltage(rdev, ret); + if (min_uV <= best_val && max_uV >= best_val) { + selector = ret; + if (old_selector == selector) + ret = 0; + else + ret = _regulator_call_set_voltage_sel( + rdev, best_val, selector); + } else { + ret = -EINVAL; + } + } + } else { + ret = -EINVAL; + } + + if (ret) + goto out; + + if (ops->set_voltage_time_sel) { + /* + * Call set_voltage_time_sel if successfully obtained + * old_selector + */ + if (old_selector >= 0 && old_selector != selector) + delay = ops->set_voltage_time_sel(rdev, old_selector, + selector); + } else { + if (old_uV != best_val) { + if (ops->set_voltage_time) + delay = ops->set_voltage_time(rdev, old_uV, + best_val); + else + delay = _regulator_set_voltage_time(rdev, + old_uV, + best_val); + } + } + + if (delay < 0) { + rdev_warn(rdev, "failed to get delay: %d\n", delay); + delay = 0; + } + + /* Insert any necessary delays */ + if (delay >= 1000) { + mdelay(delay / 1000); + udelay(delay % 1000); + } else if (delay) { + udelay(delay); + } + + if (best_val >= 0) { + unsigned long data = best_val; + + _notifier_call_chain(rdev, REGULATOR_EVENT_VOLTAGE_CHANGE, + (void *)data); + } + +out: + trace_regulator_set_voltage_complete(rdev_get_name(rdev), best_val); + + return ret; +} + +static int _regulator_do_set_suspend_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, suspend_state_t state) +{ + struct regulator_state *rstate; + int uV, sel; + + rstate = regulator_get_suspend_state(rdev, state); + if (rstate == NULL) + return -EINVAL; + + if (min_uV < rstate->min_uV) + min_uV = rstate->min_uV; + if (max_uV > rstate->max_uV) + max_uV = rstate->max_uV; + + sel = regulator_map_voltage(rdev, min_uV, max_uV); + if (sel < 0) + return sel; + + uV = rdev->desc->ops->list_voltage(rdev, sel); + if (uV >= min_uV && uV <= max_uV) + rstate->uV = uV; + + return 0; +} + +static int regulator_set_voltage_unlocked(struct regulator *regulator, + int min_uV, int max_uV, + suspend_state_t state) +{ + struct regulator_dev *rdev = regulator->rdev; + struct regulator_voltage *voltage = ®ulator->voltage[state]; + int ret = 0; + int old_min_uV, old_max_uV; + int current_uV; + int best_supply_uV = 0; + int supply_change_uV = 0; + + /* If we're setting the same range as last time the change + * should be a noop (some cpufreq implementations use the same + * voltage for multiple frequencies, for example). + */ + if (voltage->min_uV == min_uV && voltage->max_uV == max_uV) + goto out; + + /* If we're trying to set a range that overlaps the current voltage, + * return successfully even though the regulator does not support + * changing the voltage. + */ + if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_VOLTAGE)) { + current_uV = _regulator_get_voltage(rdev); + if (min_uV <= current_uV && current_uV <= max_uV) { + voltage->min_uV = min_uV; + voltage->max_uV = max_uV; + goto out; + } + } + + /* sanity check */ + if (!rdev->desc->ops->set_voltage && + !rdev->desc->ops->set_voltage_sel) { + ret = -EINVAL; + goto out; + } + + /* constraints check */ + ret = regulator_check_voltage(rdev, &min_uV, &max_uV); + if (ret < 0) + goto out; + + /* restore original values in case of error */ + old_min_uV = voltage->min_uV; + old_max_uV = voltage->max_uV; + voltage->min_uV = min_uV; + voltage->max_uV = max_uV; + + ret = regulator_check_consumers(rdev, &min_uV, &max_uV, state); + if (ret < 0) + goto out2; + + if (rdev->supply && + regulator_ops_is_valid(rdev->supply->rdev, + REGULATOR_CHANGE_VOLTAGE) && + (rdev->desc->min_dropout_uV || !(rdev->desc->ops->get_voltage || + rdev->desc->ops->get_voltage_sel))) { + int current_supply_uV; + int selector; + + selector = regulator_map_voltage(rdev, min_uV, max_uV); + if (selector < 0) { + ret = selector; + goto out2; + } + + best_supply_uV = _regulator_list_voltage(rdev, selector, 0); + if (best_supply_uV < 0) { + ret = best_supply_uV; + goto out2; + } + + best_supply_uV += rdev->desc->min_dropout_uV; + + current_supply_uV = _regulator_get_voltage(rdev->supply->rdev); + if (current_supply_uV < 0) { + ret = current_supply_uV; + goto out2; + } + + supply_change_uV = best_supply_uV - current_supply_uV; + } + + if (supply_change_uV > 0) { + ret = regulator_set_voltage_unlocked(rdev->supply, + best_supply_uV, INT_MAX, state); + if (ret) { + dev_err(&rdev->dev, "Failed to increase supply voltage: %d\n", + ret); + goto out2; + } + } + + if (state == PM_SUSPEND_ON) + ret = _regulator_do_set_voltage(rdev, min_uV, max_uV); + else + ret = _regulator_do_set_suspend_voltage(rdev, min_uV, + max_uV, state); + if (ret < 0) + goto out2; + + if (supply_change_uV < 0) { + ret = regulator_set_voltage_unlocked(rdev->supply, + best_supply_uV, INT_MAX, state); + if (ret) + dev_warn(&rdev->dev, "Failed to decrease supply voltage: %d\n", + ret); + /* No need to fail here */ + ret = 0; + } + +out: + return ret; +out2: + voltage->min_uV = old_min_uV; + voltage->max_uV = old_max_uV; + + return ret; +} + +/** + * regulator_set_voltage - set regulator output voltage + * @regulator: regulator source + * @min_uV: Minimum required voltage in uV + * @max_uV: Maximum acceptable voltage in uV + * + * Sets a voltage regulator to the desired output voltage. This can be set + * during any regulator state. IOW, regulator can be disabled or enabled. + * + * If the regulator is enabled then the voltage will change to the new value + * immediately otherwise if the regulator is disabled the regulator will + * output at the new voltage when enabled. + * + * NOTE: If the regulator is shared between several devices then the lowest + * request voltage that meets the system constraints will be used. + * Regulator system constraints must be set for this regulator before + * calling this function otherwise this call will fail. + */ +int regulator_set_voltage(struct regulator *regulator, int min_uV, int max_uV) +{ + int ret = 0; + + regulator_lock_supply(regulator->rdev); + + ret = regulator_set_voltage_unlocked(regulator, min_uV, max_uV, + PM_SUSPEND_ON); + + regulator_unlock_supply(regulator->rdev); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_set_voltage); + +static inline int regulator_suspend_toggle(struct regulator_dev *rdev, + suspend_state_t state, bool en) +{ + struct regulator_state *rstate; + + rstate = regulator_get_suspend_state(rdev, state); + if (rstate == NULL) + return -EINVAL; + + if (!rstate->changeable) + return -EPERM; + + rstate->enabled = (en) ? ENABLE_IN_SUSPEND : DISABLE_IN_SUSPEND; + + return 0; +} + +int regulator_suspend_enable(struct regulator_dev *rdev, + suspend_state_t state) +{ + return regulator_suspend_toggle(rdev, state, true); +} +EXPORT_SYMBOL_GPL(regulator_suspend_enable); + +int regulator_suspend_disable(struct regulator_dev *rdev, + suspend_state_t state) +{ + struct regulator *regulator; + struct regulator_voltage *voltage; + + /* + * if any consumer wants this regulator device keeping on in + * suspend states, don't set it as disabled. + */ + list_for_each_entry(regulator, &rdev->consumer_list, list) { + voltage = ®ulator->voltage[state]; + if (voltage->min_uV || voltage->max_uV) + return 0; + } + + return regulator_suspend_toggle(rdev, state, false); +} +EXPORT_SYMBOL_GPL(regulator_suspend_disable); + +static int _regulator_set_suspend_voltage(struct regulator *regulator, + int min_uV, int max_uV, + suspend_state_t state) +{ + struct regulator_dev *rdev = regulator->rdev; + struct regulator_state *rstate; + + rstate = regulator_get_suspend_state(rdev, state); + if (rstate == NULL) + return -EINVAL; + + if (rstate->min_uV == rstate->max_uV) { + rdev_err(rdev, "The suspend voltage can't be changed!\n"); + return -EPERM; + } + + return regulator_set_voltage_unlocked(regulator, min_uV, max_uV, state); +} + +int regulator_set_suspend_voltage(struct regulator *regulator, int min_uV, + int max_uV, suspend_state_t state) +{ + int ret = 0; + + /* PM_SUSPEND_ON is handled by regulator_set_voltage() */ + if (regulator_check_states(state) || state == PM_SUSPEND_ON) + return -EINVAL; + + regulator_lock_supply(regulator->rdev); + + ret = _regulator_set_suspend_voltage(regulator, min_uV, + max_uV, state); + + regulator_unlock_supply(regulator->rdev); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_set_suspend_voltage); + +/** + * regulator_set_voltage_time - get raise/fall time + * @regulator: regulator source + * @old_uV: starting voltage in microvolts + * @new_uV: target voltage in microvolts + * + * Provided with the starting and ending voltage, this function attempts to + * calculate the time in microseconds required to rise or fall to this new + * voltage. + */ +int regulator_set_voltage_time(struct regulator *regulator, + int old_uV, int new_uV) +{ + struct regulator_dev *rdev = regulator->rdev; + const struct regulator_ops *ops = rdev->desc->ops; + int old_sel = -1; + int new_sel = -1; + int voltage; + int i; + + if (ops->set_voltage_time) + return ops->set_voltage_time(rdev, old_uV, new_uV); + else if (!ops->set_voltage_time_sel) + return _regulator_set_voltage_time(rdev, old_uV, new_uV); + + /* Currently requires operations to do this */ + if (!ops->list_voltage || !rdev->desc->n_voltages) + return -EINVAL; + + for (i = 0; i < rdev->desc->n_voltages; i++) { + /* We only look for exact voltage matches here */ + voltage = regulator_list_voltage(regulator, i); + if (voltage < 0) + return -EINVAL; + if (voltage == 0) + continue; + if (voltage == old_uV) + old_sel = i; + if (voltage == new_uV) + new_sel = i; + } + + if (old_sel < 0 || new_sel < 0) + return -EINVAL; + + return ops->set_voltage_time_sel(rdev, old_sel, new_sel); +} +EXPORT_SYMBOL_GPL(regulator_set_voltage_time); + +/** + * regulator_set_voltage_time_sel - get raise/fall time + * @rdev: regulator source device + * @old_selector: selector for starting voltage + * @new_selector: selector for target voltage + * + * Provided with the starting and target voltage selectors, this function + * returns time in microseconds required to rise or fall to this new voltage + * + * Drivers providing ramp_delay in regulation_constraints can use this as their + * set_voltage_time_sel() operation. + */ +int regulator_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + int old_volt, new_volt; + + /* sanity check */ + if (!rdev->desc->ops->list_voltage) + return -EINVAL; + + old_volt = rdev->desc->ops->list_voltage(rdev, old_selector); + new_volt = rdev->desc->ops->list_voltage(rdev, new_selector); + + if (rdev->desc->ops->set_voltage_time) + return rdev->desc->ops->set_voltage_time(rdev, old_volt, + new_volt); + else + return _regulator_set_voltage_time(rdev, old_volt, new_volt); +} +EXPORT_SYMBOL_GPL(regulator_set_voltage_time_sel); + +/** + * regulator_sync_voltage - re-apply last regulator output voltage + * @regulator: regulator source + * + * Re-apply the last configured voltage. This is intended to be used + * where some external control source the consumer is cooperating with + * has caused the configured voltage to change. + */ +int regulator_sync_voltage(struct regulator *regulator) +{ + struct regulator_dev *rdev = regulator->rdev; + struct regulator_voltage *voltage = ®ulator->voltage[PM_SUSPEND_ON]; + int ret, min_uV, max_uV; + + regulator_lock(rdev); + + if (!rdev->desc->ops->set_voltage && + !rdev->desc->ops->set_voltage_sel) { + ret = -EINVAL; + goto out; + } + + /* This is only going to work if we've had a voltage configured. */ + if (!voltage->min_uV && !voltage->max_uV) { + ret = -EINVAL; + goto out; + } + + min_uV = voltage->min_uV; + max_uV = voltage->max_uV; + + /* This should be a paranoia check... */ + ret = regulator_check_voltage(rdev, &min_uV, &max_uV); + if (ret < 0) + goto out; + + ret = regulator_check_consumers(rdev, &min_uV, &max_uV, 0); + if (ret < 0) + goto out; + + ret = _regulator_do_set_voltage(rdev, min_uV, max_uV); + +out: + regulator_unlock(rdev); + return ret; +} +EXPORT_SYMBOL_GPL(regulator_sync_voltage); + +static int _regulator_get_voltage(struct regulator_dev *rdev) +{ + int sel, ret; + bool bypassed; + + if (rdev->desc->ops->get_bypass) { + ret = rdev->desc->ops->get_bypass(rdev, &bypassed); + if (ret < 0) + return ret; + if (bypassed) { + /* if bypassed the regulator must have a supply */ + if (!rdev->supply) { + rdev_err(rdev, + "bypassed regulator has no supply!\n"); + return -EPROBE_DEFER; + } + + return _regulator_get_voltage(rdev->supply->rdev); + } + } + + if (rdev->desc->ops->get_voltage_sel) { + sel = rdev->desc->ops->get_voltage_sel(rdev); + if (sel < 0) + return sel; + ret = rdev->desc->ops->list_voltage(rdev, sel); + } else if (rdev->desc->ops->get_voltage) { + ret = rdev->desc->ops->get_voltage(rdev); + } else if (rdev->desc->ops->list_voltage) { + ret = rdev->desc->ops->list_voltage(rdev, 0); + } else if (rdev->desc->fixed_uV && (rdev->desc->n_voltages == 1)) { + ret = rdev->desc->fixed_uV; + } else if (rdev->supply) { + ret = _regulator_get_voltage(rdev->supply->rdev); + } else if (rdev->supply_name) { + return -EPROBE_DEFER; + } else { + return -EINVAL; + } + + if (ret < 0) + return ret; + return ret - rdev->constraints->uV_offset; +} + +/** + * regulator_get_voltage - get regulator output voltage + * @regulator: regulator source + * + * This returns the current regulator voltage in uV. + * + * NOTE: If the regulator is disabled it will return the voltage value. This + * function should not be used to determine regulator state. + */ +int regulator_get_voltage(struct regulator *regulator) +{ + int ret; + + regulator_lock_supply(regulator->rdev); + + ret = _regulator_get_voltage(regulator->rdev); + + regulator_unlock_supply(regulator->rdev); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_get_voltage); + +/** + * regulator_set_current_limit - set regulator output current limit + * @regulator: regulator source + * @min_uA: Minimum supported current in uA + * @max_uA: Maximum supported current in uA + * + * Sets current sink to the desired output current. This can be set during + * any regulator state. IOW, regulator can be disabled or enabled. + * + * If the regulator is enabled then the current will change to the new value + * immediately otherwise if the regulator is disabled the regulator will + * output at the new current when enabled. + * + * NOTE: Regulator system constraints must be set for this regulator before + * calling this function otherwise this call will fail. + */ +int regulator_set_current_limit(struct regulator *regulator, + int min_uA, int max_uA) +{ + struct regulator_dev *rdev = regulator->rdev; + int ret; + + regulator_lock(rdev); + + /* sanity check */ + if (!rdev->desc->ops->set_current_limit) { + ret = -EINVAL; + goto out; + } + + /* constraints check */ + ret = regulator_check_current_limit(rdev, &min_uA, &max_uA); + if (ret < 0) + goto out; + + ret = rdev->desc->ops->set_current_limit(rdev, min_uA, max_uA); +out: + regulator_unlock(rdev); + return ret; +} +EXPORT_SYMBOL_GPL(regulator_set_current_limit); + +static int _regulator_get_current_limit(struct regulator_dev *rdev) +{ + int ret; + + regulator_lock(rdev); + + /* sanity check */ + if (!rdev->desc->ops->get_current_limit) { + ret = -EINVAL; + goto out; + } + + ret = rdev->desc->ops->get_current_limit(rdev); +out: + regulator_unlock(rdev); + return ret; +} + +/** + * regulator_get_current_limit - get regulator output current + * @regulator: regulator source + * + * This returns the current supplied by the specified current sink in uA. + * + * NOTE: If the regulator is disabled it will return the current value. This + * function should not be used to determine regulator state. + */ +int regulator_get_current_limit(struct regulator *regulator) +{ + return _regulator_get_current_limit(regulator->rdev); +} +EXPORT_SYMBOL_GPL(regulator_get_current_limit); + +/** + * regulator_set_mode - set regulator operating mode + * @regulator: regulator source + * @mode: operating mode - one of the REGULATOR_MODE constants + * + * Set regulator operating mode to increase regulator efficiency or improve + * regulation performance. + * + * NOTE: Regulator system constraints must be set for this regulator before + * calling this function otherwise this call will fail. + */ +int regulator_set_mode(struct regulator *regulator, unsigned int mode) +{ + struct regulator_dev *rdev = regulator->rdev; + int ret; + int regulator_curr_mode; + + regulator_lock(rdev); + + /* sanity check */ + if (!rdev->desc->ops->set_mode) { + ret = -EINVAL; + goto out; + } + + /* return if the same mode is requested */ + if (rdev->desc->ops->get_mode) { + regulator_curr_mode = rdev->desc->ops->get_mode(rdev); + if (regulator_curr_mode == mode) { + ret = 0; + goto out; + } + } + + /* constraints check */ + ret = regulator_mode_constrain(rdev, &mode); + if (ret < 0) + goto out; + + ret = rdev->desc->ops->set_mode(rdev, mode); +out: + regulator_unlock(rdev); + return ret; +} +EXPORT_SYMBOL_GPL(regulator_set_mode); + +static unsigned int _regulator_get_mode(struct regulator_dev *rdev) +{ + int ret; + + regulator_lock(rdev); + + /* sanity check */ + if (!rdev->desc->ops->get_mode) { + ret = -EINVAL; + goto out; + } + + ret = rdev->desc->ops->get_mode(rdev); +out: + regulator_unlock(rdev); + return ret; +} + +/** + * regulator_get_mode - get regulator operating mode + * @regulator: regulator source + * + * Get the current regulator operating mode. + */ +unsigned int regulator_get_mode(struct regulator *regulator) +{ + return _regulator_get_mode(regulator->rdev); +} +EXPORT_SYMBOL_GPL(regulator_get_mode); + +static int _regulator_get_error_flags(struct regulator_dev *rdev, + unsigned int *flags) +{ + int ret; + + regulator_lock(rdev); + + /* sanity check */ + if (!rdev->desc->ops->get_error_flags) { + ret = -EINVAL; + goto out; + } + + ret = rdev->desc->ops->get_error_flags(rdev, flags); +out: + regulator_unlock(rdev); + return ret; +} + +/** + * regulator_get_error_flags - get regulator error information + * @regulator: regulator source + * @flags: pointer to store error flags + * + * Get the current regulator error information. + */ +int regulator_get_error_flags(struct regulator *regulator, + unsigned int *flags) +{ + return _regulator_get_error_flags(regulator->rdev, flags); +} +EXPORT_SYMBOL_GPL(regulator_get_error_flags); + +/** + * regulator_set_load - set regulator load + * @regulator: regulator source + * @uA_load: load current + * + * Notifies the regulator core of a new device load. This is then used by + * DRMS (if enabled by constraints) to set the most efficient regulator + * operating mode for the new regulator loading. + * + * Consumer devices notify their supply regulator of the maximum power + * they will require (can be taken from device datasheet in the power + * consumption tables) when they change operational status and hence power + * state. Examples of operational state changes that can affect power + * consumption are :- + * + * o Device is opened / closed. + * o Device I/O is about to begin or has just finished. + * o Device is idling in between work. + * + * This information is also exported via sysfs to userspace. + * + * DRMS will sum the total requested load on the regulator and change + * to the most efficient operating mode if platform constraints allow. + * + * On error a negative errno is returned. + */ +int regulator_set_load(struct regulator *regulator, int uA_load) +{ + struct regulator_dev *rdev = regulator->rdev; + int ret; + + regulator_lock(rdev); + regulator->uA_load = uA_load; + ret = drms_uA_update(rdev); + regulator_unlock(rdev); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_set_load); + +/** + * regulator_allow_bypass - allow the regulator to go into bypass mode + * + * @regulator: Regulator to configure + * @enable: enable or disable bypass mode + * + * Allow the regulator to go into bypass mode if all other consumers + * for the regulator also enable bypass mode and the machine + * constraints allow this. Bypass mode means that the regulator is + * simply passing the input directly to the output with no regulation. + */ +int regulator_allow_bypass(struct regulator *regulator, bool enable) +{ + struct regulator_dev *rdev = regulator->rdev; + int ret = 0; + + if (!rdev->desc->ops->set_bypass) + return 0; + + if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_BYPASS)) + return 0; + + regulator_lock(rdev); + + if (enable && !regulator->bypass) { + rdev->bypass_count++; + + if (rdev->bypass_count == rdev->open_count) { + ret = rdev->desc->ops->set_bypass(rdev, enable); + if (ret != 0) + rdev->bypass_count--; + } + + } else if (!enable && regulator->bypass) { + rdev->bypass_count--; + + if (rdev->bypass_count != rdev->open_count) { + ret = rdev->desc->ops->set_bypass(rdev, enable); + if (ret != 0) + rdev->bypass_count++; + } + } + + if (ret == 0) + regulator->bypass = enable; + + regulator_unlock(rdev); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_allow_bypass); + +/** + * regulator_register_notifier - register regulator event notifier + * @regulator: regulator source + * @nb: notifier block + * + * Register notifier block to receive regulator events. + */ +int regulator_register_notifier(struct regulator *regulator, + struct notifier_block *nb) +{ + return blocking_notifier_chain_register(®ulator->rdev->notifier, + nb); +} +EXPORT_SYMBOL_GPL(regulator_register_notifier); + +/** + * regulator_unregister_notifier - unregister regulator event notifier + * @regulator: regulator source + * @nb: notifier block + * + * Unregister regulator event notifier block. + */ +int regulator_unregister_notifier(struct regulator *regulator, + struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(®ulator->rdev->notifier, + nb); +} +EXPORT_SYMBOL_GPL(regulator_unregister_notifier); + +/* notify regulator consumers and downstream regulator consumers. + * Note mutex must be held by caller. + */ +static int _notifier_call_chain(struct regulator_dev *rdev, + unsigned long event, void *data) +{ + /* call rdev chain first */ + return blocking_notifier_call_chain(&rdev->notifier, event, data); +} + +/** + * regulator_bulk_get - get multiple regulator consumers + * + * @dev: Device to supply + * @num_consumers: Number of consumers to register + * @consumers: Configuration of consumers; clients are stored here. + * + * @return 0 on success, an errno on failure. + * + * This helper function allows drivers to get several regulator + * consumers in one operation. If any of the regulators cannot be + * acquired then any regulators that were allocated will be freed + * before returning to the caller. + */ +int regulator_bulk_get(struct device *dev, int num_consumers, + struct regulator_bulk_data *consumers) +{ + int i; + int ret; + + for (i = 0; i < num_consumers; i++) + consumers[i].consumer = NULL; + + for (i = 0; i < num_consumers; i++) { + consumers[i].consumer = regulator_get(dev, + consumers[i].supply); + if (IS_ERR(consumers[i].consumer)) { + ret = PTR_ERR(consumers[i].consumer); + dev_err(dev, "Failed to get supply '%s': %d\n", + consumers[i].supply, ret); + consumers[i].consumer = NULL; + goto err; + } + } + + return 0; + +err: + while (--i >= 0) + regulator_put(consumers[i].consumer); + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_bulk_get); + +static void regulator_bulk_enable_async(void *data, async_cookie_t cookie) +{ + struct regulator_bulk_data *bulk = data; + + bulk->ret = regulator_enable(bulk->consumer); +} + +/** + * regulator_bulk_enable - enable multiple regulator consumers + * + * @num_consumers: Number of consumers + * @consumers: Consumer data; clients are stored here. + * @return 0 on success, an errno on failure + * + * This convenience API allows consumers to enable multiple regulator + * clients in a single API call. If any consumers cannot be enabled + * then any others that were enabled will be disabled again prior to + * return. + */ +int regulator_bulk_enable(int num_consumers, + struct regulator_bulk_data *consumers) +{ + ASYNC_DOMAIN_EXCLUSIVE(async_domain); + int i; + int ret = 0; + + for (i = 0; i < num_consumers; i++) { + if (consumers[i].consumer->always_on) + consumers[i].ret = 0; + else + async_schedule_domain(regulator_bulk_enable_async, + &consumers[i], &async_domain); + } + + async_synchronize_full_domain(&async_domain); + + /* If any consumer failed we need to unwind any that succeeded */ + for (i = 0; i < num_consumers; i++) { + if (consumers[i].ret != 0) { + ret = consumers[i].ret; + goto err; + } + } + + return 0; + +err: + for (i = 0; i < num_consumers; i++) { + if (consumers[i].ret < 0) + pr_err("Failed to enable %s: %d\n", consumers[i].supply, + consumers[i].ret); + else + regulator_disable(consumers[i].consumer); + } + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_bulk_enable); + +/** + * regulator_bulk_disable - disable multiple regulator consumers + * + * @num_consumers: Number of consumers + * @consumers: Consumer data; clients are stored here. + * @return 0 on success, an errno on failure + * + * This convenience API allows consumers to disable multiple regulator + * clients in a single API call. If any consumers cannot be disabled + * then any others that were disabled will be enabled again prior to + * return. + */ +int regulator_bulk_disable(int num_consumers, + struct regulator_bulk_data *consumers) +{ + int i; + int ret, r; + + for (i = num_consumers - 1; i >= 0; --i) { + ret = regulator_disable(consumers[i].consumer); + if (ret != 0) + goto err; + } + + return 0; + +err: + pr_err("Failed to disable %s: %d\n", consumers[i].supply, ret); + for (++i; i < num_consumers; ++i) { + r = regulator_enable(consumers[i].consumer); + if (r != 0) + pr_err("Failed to re-enable %s: %d\n", + consumers[i].supply, r); + } + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_bulk_disable); + +/** + * regulator_bulk_force_disable - force disable multiple regulator consumers + * + * @num_consumers: Number of consumers + * @consumers: Consumer data; clients are stored here. + * @return 0 on success, an errno on failure + * + * This convenience API allows consumers to forcibly disable multiple regulator + * clients in a single API call. + * NOTE: This should be used for situations when device damage will + * likely occur if the regulators are not disabled (e.g. over temp). + * Although regulator_force_disable function call for some consumers can + * return error numbers, the function is called for all consumers. + */ +int regulator_bulk_force_disable(int num_consumers, + struct regulator_bulk_data *consumers) +{ + int i; + int ret = 0; + + for (i = 0; i < num_consumers; i++) { + consumers[i].ret = + regulator_force_disable(consumers[i].consumer); + + /* Store first error for reporting */ + if (consumers[i].ret && !ret) + ret = consumers[i].ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_bulk_force_disable); + +/** + * regulator_bulk_free - free multiple regulator consumers + * + * @num_consumers: Number of consumers + * @consumers: Consumer data; clients are stored here. + * + * This convenience API allows consumers to free multiple regulator + * clients in a single API call. + */ +void regulator_bulk_free(int num_consumers, + struct regulator_bulk_data *consumers) +{ + int i; + + for (i = 0; i < num_consumers; i++) { + regulator_put(consumers[i].consumer); + consumers[i].consumer = NULL; + } +} +EXPORT_SYMBOL_GPL(regulator_bulk_free); + +/** + * regulator_notifier_call_chain - call regulator event notifier + * @rdev: regulator source + * @event: notifier block + * @data: callback-specific data. + * + * Called by regulator drivers to notify clients a regulator event has + * occurred. We also notify regulator clients downstream. + * Note lock must be held by caller. + */ +int regulator_notifier_call_chain(struct regulator_dev *rdev, + unsigned long event, void *data) +{ + lockdep_assert_held_once(&rdev->mutex); + + _notifier_call_chain(rdev, event, data); + return NOTIFY_DONE; + +} +EXPORT_SYMBOL_GPL(regulator_notifier_call_chain); + +/** + * regulator_mode_to_status - convert a regulator mode into a status + * + * @mode: Mode to convert + * + * Convert a regulator mode into a status. + */ +int regulator_mode_to_status(unsigned int mode) +{ + switch (mode) { + case REGULATOR_MODE_FAST: + return REGULATOR_STATUS_FAST; + case REGULATOR_MODE_NORMAL: + return REGULATOR_STATUS_NORMAL; + case REGULATOR_MODE_IDLE: + return REGULATOR_STATUS_IDLE; + case REGULATOR_MODE_STANDBY: + return REGULATOR_STATUS_STANDBY; + default: + return REGULATOR_STATUS_UNDEFINED; + } +} +EXPORT_SYMBOL_GPL(regulator_mode_to_status); + +static struct attribute *regulator_dev_attrs[] = { + &dev_attr_name.attr, + &dev_attr_num_users.attr, + &dev_attr_type.attr, + &dev_attr_microvolts.attr, + &dev_attr_microamps.attr, + &dev_attr_opmode.attr, + &dev_attr_state.attr, + &dev_attr_status.attr, + &dev_attr_bypass.attr, + &dev_attr_requested_microamps.attr, + &dev_attr_min_microvolts.attr, + &dev_attr_max_microvolts.attr, + &dev_attr_min_microamps.attr, + &dev_attr_max_microamps.attr, + &dev_attr_suspend_standby_state.attr, + &dev_attr_suspend_mem_state.attr, + &dev_attr_suspend_disk_state.attr, + &dev_attr_suspend_standby_microvolts.attr, + &dev_attr_suspend_mem_microvolts.attr, + &dev_attr_suspend_disk_microvolts.attr, + &dev_attr_suspend_standby_mode.attr, + &dev_attr_suspend_mem_mode.attr, + &dev_attr_suspend_disk_mode.attr, + NULL +}; + +/* + * To avoid cluttering sysfs (and memory) with useless state, only + * create attributes that can be meaningfully displayed. + */ +static umode_t regulator_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct regulator_dev *rdev = dev_to_rdev(dev); + const struct regulator_ops *ops = rdev->desc->ops; + umode_t mode = attr->mode; + + /* these three are always present */ + if (attr == &dev_attr_name.attr || + attr == &dev_attr_num_users.attr || + attr == &dev_attr_type.attr) + return mode; + + /* some attributes need specific methods to be displayed */ + if (attr == &dev_attr_microvolts.attr) { + if ((ops->get_voltage && ops->get_voltage(rdev) >= 0) || + (ops->get_voltage_sel && ops->get_voltage_sel(rdev) >= 0) || + (ops->list_voltage && ops->list_voltage(rdev, 0) >= 0) || + (rdev->desc->fixed_uV && rdev->desc->n_voltages == 1)) + return mode; + return 0; + } + + if (attr == &dev_attr_microamps.attr) + return ops->get_current_limit ? mode : 0; + + if (attr == &dev_attr_opmode.attr) + return ops->get_mode ? mode : 0; + + if (attr == &dev_attr_state.attr) + return (rdev->ena_pin || ops->is_enabled) ? mode : 0; + + if (attr == &dev_attr_status.attr) + return ops->get_status ? mode : 0; + + if (attr == &dev_attr_bypass.attr) + return ops->get_bypass ? mode : 0; + + /* some attributes are type-specific */ + if (attr == &dev_attr_requested_microamps.attr) + return rdev->desc->type == REGULATOR_CURRENT ? mode : 0; + + /* constraints need specific supporting methods */ + if (attr == &dev_attr_min_microvolts.attr || + attr == &dev_attr_max_microvolts.attr) + return (ops->set_voltage || ops->set_voltage_sel) ? mode : 0; + + if (attr == &dev_attr_min_microamps.attr || + attr == &dev_attr_max_microamps.attr) + return ops->set_current_limit ? mode : 0; + + if (attr == &dev_attr_suspend_standby_state.attr || + attr == &dev_attr_suspend_mem_state.attr || + attr == &dev_attr_suspend_disk_state.attr) + return mode; + + if (attr == &dev_attr_suspend_standby_microvolts.attr || + attr == &dev_attr_suspend_mem_microvolts.attr || + attr == &dev_attr_suspend_disk_microvolts.attr) + return ops->set_suspend_voltage ? mode : 0; + + if (attr == &dev_attr_suspend_standby_mode.attr || + attr == &dev_attr_suspend_mem_mode.attr || + attr == &dev_attr_suspend_disk_mode.attr) + return ops->set_suspend_mode ? mode : 0; + + return mode; +} + +static const struct attribute_group regulator_dev_group = { + .attrs = regulator_dev_attrs, + .is_visible = regulator_attr_is_visible, +}; + +static const struct attribute_group *regulator_dev_groups[] = { + ®ulator_dev_group, + NULL +}; + +static void regulator_dev_release(struct device *dev) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + + kfree(rdev->constraints); + of_node_put(rdev->dev.of_node); + kfree(rdev); +} + +static void rdev_init_debugfs(struct regulator_dev *rdev) +{ + struct device *parent = rdev->dev.parent; + const char *rname = rdev_get_name(rdev); + char name[NAME_MAX]; + + /* Avoid duplicate debugfs directory names */ + if (parent && rname == rdev->desc->name) { + snprintf(name, sizeof(name), "%s-%s", dev_name(parent), + rname); + rname = name; + } + + rdev->debugfs = debugfs_create_dir(rname, debugfs_root); + if (!rdev->debugfs) { + rdev_warn(rdev, "Failed to create debugfs directory\n"); + return; + } + + debugfs_create_u32("use_count", 0444, rdev->debugfs, + &rdev->use_count); + debugfs_create_u32("open_count", 0444, rdev->debugfs, + &rdev->open_count); + debugfs_create_u32("bypass_count", 0444, rdev->debugfs, + &rdev->bypass_count); +} + +static int regulator_register_resolve_supply(struct device *dev, void *data) +{ + struct regulator_dev *rdev = dev_to_rdev(dev); + + if (regulator_resolve_supply(rdev)) + rdev_dbg(rdev, "unable to resolve supply\n"); + + return 0; +} + +static int regulator_fill_coupling_array(struct regulator_dev *rdev) +{ + struct coupling_desc *c_desc = &rdev->coupling_desc; + int n_coupled = c_desc->n_coupled; + struct regulator_dev *c_rdev; + int i; + + for (i = 1; i < n_coupled; i++) { + /* already resolved */ + if (c_desc->coupled_rdevs[i]) + continue; + + c_rdev = of_parse_coupled_regulator(rdev, i - 1); + + if (c_rdev) { + c_desc->coupled_rdevs[i] = c_rdev; + c_desc->n_resolved++; + } + } + + if (rdev->coupling_desc.n_resolved < n_coupled) + return -1; + else + return 0; +} + +static int regulator_register_fill_coupling_array(struct device *dev, + void *data) +{ + struct regulator_dev *rdev = dev_to_rdev(dev); + + if (!IS_ENABLED(CONFIG_OF)) + return 0; + + if (regulator_fill_coupling_array(rdev)) + rdev_dbg(rdev, "unable to resolve coupling\n"); + + return 0; +} + +static int regulator_resolve_coupling(struct regulator_dev *rdev) +{ + int n_phandles; + + if (!IS_ENABLED(CONFIG_OF)) + n_phandles = 0; + else + n_phandles = of_get_n_coupled(rdev); + + if (n_phandles + 1 > MAX_COUPLED) { + rdev_err(rdev, "too many regulators coupled\n"); + return -EPERM; + } + + /* + * Every regulator should always have coupling descriptor filled with + * at least pointer to itself. + */ + rdev->coupling_desc.coupled_rdevs[0] = rdev; + rdev->coupling_desc.n_coupled = n_phandles + 1; + rdev->coupling_desc.n_resolved++; + + /* regulator isn't coupled */ + if (n_phandles == 0) + return 0; + + /* regulator, which can't change its voltage, can't be coupled */ + if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_VOLTAGE)) { + rdev_err(rdev, "voltage operation not allowed\n"); + return -EPERM; + } + + if (rdev->constraints->max_spread <= 0) { + rdev_err(rdev, "wrong max_spread value\n"); + return -EPERM; + } + + if (!of_check_coupling_data(rdev)) + return -EPERM; + + /* + * After everything has been checked, try to fill rdevs array + * with pointers to regulators parsed from device tree. If some + * regulators are not registered yet, retry in late init call + */ + regulator_fill_coupling_array(rdev); + + return 0; +} + +/** + * regulator_register - register regulator + * @regulator_desc: regulator to register + * @cfg: runtime configuration for regulator + * + * Called by regulator drivers to register a regulator. + * Returns a valid pointer to struct regulator_dev on success + * or an ERR_PTR() on error. + */ +struct regulator_dev * +regulator_register(const struct regulator_desc *regulator_desc, + const struct regulator_config *cfg) +{ + const struct regulator_init_data *init_data; + struct regulator_config *config = NULL; + static atomic_t regulator_no = ATOMIC_INIT(-1); + struct regulator_dev *rdev; + struct device *dev; + int ret, i; + + if (regulator_desc == NULL || cfg == NULL) + return ERR_PTR(-EINVAL); + + dev = cfg->dev; + WARN_ON(!dev); + + if (regulator_desc->name == NULL || regulator_desc->ops == NULL) + return ERR_PTR(-EINVAL); + + if (regulator_desc->type != REGULATOR_VOLTAGE && + regulator_desc->type != REGULATOR_CURRENT) + return ERR_PTR(-EINVAL); + + /* Only one of each should be implemented */ + WARN_ON(regulator_desc->ops->get_voltage && + regulator_desc->ops->get_voltage_sel); + WARN_ON(regulator_desc->ops->set_voltage && + regulator_desc->ops->set_voltage_sel); + + /* If we're using selectors we must implement list_voltage. */ + if (regulator_desc->ops->get_voltage_sel && + !regulator_desc->ops->list_voltage) { + return ERR_PTR(-EINVAL); + } + if (regulator_desc->ops->set_voltage_sel && + !regulator_desc->ops->list_voltage) { + return ERR_PTR(-EINVAL); + } + + rdev = kzalloc(sizeof(struct regulator_dev), GFP_KERNEL); + if (rdev == NULL) + return ERR_PTR(-ENOMEM); + + /* + * Duplicate the config so the driver could override it after + * parsing init data. + */ + config = kmemdup(cfg, sizeof(*cfg), GFP_KERNEL); + if (config == NULL) { + kfree(rdev); + return ERR_PTR(-ENOMEM); + } + + init_data = regulator_of_get_init_data(dev, regulator_desc, config, + &rdev->dev.of_node); + if (!init_data) { + init_data = config->init_data; + rdev->dev.of_node = of_node_get(config->of_node); + } + + mutex_init(&rdev->mutex); + rdev->reg_data = config->driver_data; + rdev->owner = regulator_desc->owner; + rdev->desc = regulator_desc; + if (config->regmap) + rdev->regmap = config->regmap; + else if (dev_get_regmap(dev, NULL)) + rdev->regmap = dev_get_regmap(dev, NULL); + else if (dev->parent) + rdev->regmap = dev_get_regmap(dev->parent, NULL); + INIT_LIST_HEAD(&rdev->consumer_list); + INIT_LIST_HEAD(&rdev->list); + BLOCKING_INIT_NOTIFIER_HEAD(&rdev->notifier); + INIT_DELAYED_WORK(&rdev->disable_work, regulator_disable_work); + + /* preform any regulator specific init */ + if (init_data && init_data->regulator_init) { + ret = init_data->regulator_init(rdev->reg_data); + if (ret < 0) + goto clean; + } + + if (config->ena_gpiod || + ((config->ena_gpio || config->ena_gpio_initialized) && + gpio_is_valid(config->ena_gpio))) { + mutex_lock(®ulator_list_mutex); + ret = regulator_ena_gpio_request(rdev, config); + mutex_unlock(®ulator_list_mutex); + if (ret != 0) { + rdev_err(rdev, "Failed to request enable GPIO%d: %d\n", + config->ena_gpio, ret); + goto clean; + } + } + + /* register with sysfs */ + rdev->dev.class = ®ulator_class; + rdev->dev.parent = dev; + dev_set_name(&rdev->dev, "regulator.%lu", + (unsigned long) atomic_inc_return(®ulator_no)); + + /* set regulator constraints */ + if (init_data) + rdev->constraints = kmemdup(&init_data->constraints, + sizeof(*rdev->constraints), + GFP_KERNEL); + else + rdev->constraints = kzalloc(sizeof(*rdev->constraints), + GFP_KERNEL); + if (!rdev->constraints) { + ret = -ENOMEM; + goto wash; + } + + if (init_data && init_data->supply_regulator) + rdev->supply_name = init_data->supply_regulator; + else if (regulator_desc->supply_name) + rdev->supply_name = regulator_desc->supply_name; + + ret = set_machine_constraints(rdev); + if (ret == -EPROBE_DEFER) { + /* Regulator might be in bypass mode and so needs its supply + * to set the constraints */ + /* FIXME: this currently triggers a chicken-and-egg problem + * when creating -SUPPLY symlink in sysfs to a regulator + * that is just being created */ + ret = regulator_resolve_supply(rdev); + if (!ret) + ret = set_machine_constraints(rdev); + else + rdev_dbg(rdev, "unable to resolve supply early: %pe\n", + ERR_PTR(ret)); + } + if (ret < 0) + goto wash; + + mutex_lock(®ulator_list_mutex); + ret = regulator_resolve_coupling(rdev); + mutex_unlock(®ulator_list_mutex); + + if (ret != 0) + goto wash; + + /* add consumers devices */ + if (init_data) { + for (i = 0; i < init_data->num_consumer_supplies; i++) { + ret = set_consumer_device_supply(rdev, + init_data->consumer_supplies[i].dev_name, + init_data->consumer_supplies[i].supply); + if (ret < 0) { + dev_err(dev, "Failed to set supply %s\n", + init_data->consumer_supplies[i].supply); + goto unset_supplies; + } + } + } + + if (!rdev->desc->ops->get_voltage && + !rdev->desc->ops->list_voltage && + !rdev->desc->fixed_uV) + rdev->is_switch = true; + + dev_set_drvdata(&rdev->dev, rdev); + ret = device_register(&rdev->dev); + if (ret != 0) { + put_device(&rdev->dev); + goto unset_supplies; + } + + rdev_init_debugfs(rdev); + + /* try to resolve regulators supply since a new one was registered */ + class_for_each_device(®ulator_class, NULL, NULL, + regulator_register_resolve_supply); + kfree(config); + return rdev; + +unset_supplies: + mutex_lock(®ulator_list_mutex); + unset_regulator_supplies(rdev); + mutex_unlock(®ulator_list_mutex); +wash: + kfree(rdev->constraints); + mutex_lock(®ulator_list_mutex); + regulator_ena_gpio_free(rdev); + mutex_unlock(®ulator_list_mutex); +clean: + kfree(rdev); + kfree(config); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(regulator_register); + +/** + * regulator_unregister - unregister regulator + * @rdev: regulator to unregister + * + * Called by regulator drivers to unregister a regulator. + */ +void regulator_unregister(struct regulator_dev *rdev) +{ + if (rdev == NULL) + return; + + if (rdev->supply) { + while (rdev->use_count--) + regulator_disable(rdev->supply); + regulator_put(rdev->supply); + } + mutex_lock(®ulator_list_mutex); + debugfs_remove_recursive(rdev->debugfs); + flush_work(&rdev->disable_work.work); + WARN_ON(rdev->open_count); + unset_regulator_supplies(rdev); + list_del(&rdev->list); + regulator_ena_gpio_free(rdev); + mutex_unlock(®ulator_list_mutex); + device_unregister(&rdev->dev); +} +EXPORT_SYMBOL_GPL(regulator_unregister); + +#ifdef CONFIG_SUSPEND +static int _regulator_suspend(struct device *dev, void *data) +{ + struct regulator_dev *rdev = dev_to_rdev(dev); + suspend_state_t *state = data; + int ret; + + regulator_lock(rdev); + ret = suspend_set_state(rdev, *state); + regulator_unlock(rdev); + + return ret; +} + +/** + * regulator_suspend - prepare regulators for system wide suspend + * @state: system suspend state + * + * Configure each regulator with it's suspend operating parameters for state. + */ +static int regulator_suspend(struct device *dev) +{ + suspend_state_t state = pm_suspend_target_state; + + return class_for_each_device(®ulator_class, NULL, &state, + _regulator_suspend); +} + +static int _regulator_resume(struct device *dev, void *data) +{ + int ret = 0; + struct regulator_dev *rdev = dev_to_rdev(dev); + suspend_state_t *state = data; + struct regulator_state *rstate; + + rstate = regulator_get_suspend_state(rdev, *state); + if (rstate == NULL) + return 0; + + regulator_lock(rdev); + + if (rdev->desc->ops->resume && + (rstate->enabled == ENABLE_IN_SUSPEND || + rstate->enabled == DISABLE_IN_SUSPEND)) + ret = rdev->desc->ops->resume(rdev); + + regulator_unlock(rdev); + + return ret; +} + +static int regulator_resume(struct device *dev) +{ + suspend_state_t state = pm_suspend_target_state; + + return class_for_each_device(®ulator_class, NULL, &state, + _regulator_resume); +} + +#else /* !CONFIG_SUSPEND */ + +#define regulator_suspend NULL +#define regulator_resume NULL + +#endif /* !CONFIG_SUSPEND */ + +#ifdef CONFIG_PM +static const struct dev_pm_ops __maybe_unused regulator_pm_ops = { + .suspend = regulator_suspend, + .resume = regulator_resume, +}; +#endif + +struct class regulator_class = { + .name = "regulator", + .dev_release = regulator_dev_release, + .dev_groups = regulator_dev_groups, +#ifdef CONFIG_PM + .pm = ®ulator_pm_ops, +#endif +}; +/** + * regulator_has_full_constraints - the system has fully specified constraints + * + * Calling this function will cause the regulator API to disable all + * regulators which have a zero use count and don't have an always_on + * constraint in a late_initcall. + * + * The intention is that this will become the default behaviour in a + * future kernel release so users are encouraged to use this facility + * now. + */ +void regulator_has_full_constraints(void) +{ + has_full_constraints = 1; +} +EXPORT_SYMBOL_GPL(regulator_has_full_constraints); + +/** + * rdev_get_drvdata - get rdev regulator driver data + * @rdev: regulator + * + * Get rdev regulator driver private data. This call can be used in the + * regulator driver context. + */ +void *rdev_get_drvdata(struct regulator_dev *rdev) +{ + return rdev->reg_data; +} +EXPORT_SYMBOL_GPL(rdev_get_drvdata); + +/** + * regulator_get_drvdata - get regulator driver data + * @regulator: regulator + * + * Get regulator driver private data. This call can be used in the consumer + * driver context when non API regulator specific functions need to be called. + */ +void *regulator_get_drvdata(struct regulator *regulator) +{ + return regulator->rdev->reg_data; +} +EXPORT_SYMBOL_GPL(regulator_get_drvdata); + +/** + * regulator_set_drvdata - set regulator driver data + * @regulator: regulator + * @data: data + */ +void regulator_set_drvdata(struct regulator *regulator, void *data) +{ + regulator->rdev->reg_data = data; +} +EXPORT_SYMBOL_GPL(regulator_set_drvdata); + +/** + * regulator_get_id - get regulator ID + * @rdev: regulator + */ +int rdev_get_id(struct regulator_dev *rdev) +{ + return rdev->desc->id; +} +EXPORT_SYMBOL_GPL(rdev_get_id); + +struct device *rdev_get_dev(struct regulator_dev *rdev) +{ + return &rdev->dev; +} +EXPORT_SYMBOL_GPL(rdev_get_dev); + +void *regulator_get_init_drvdata(struct regulator_init_data *reg_init_data) +{ + return reg_init_data->driver_data; +} +EXPORT_SYMBOL_GPL(regulator_get_init_drvdata); + +#ifdef CONFIG_DEBUG_FS +static int supply_map_show(struct seq_file *sf, void *data) +{ + struct regulator_map *map; + + list_for_each_entry(map, ®ulator_map_list, list) { + seq_printf(sf, "%s -> %s.%s\n", + rdev_get_name(map->regulator), map->dev_name, + map->supply); + } + + return 0; +} + +static int supply_map_open(struct inode *inode, struct file *file) +{ + return single_open(file, supply_map_show, inode->i_private); +} +#endif + +static const struct file_operations supply_map_fops = { +#ifdef CONFIG_DEBUG_FS + .open = supply_map_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +#endif +}; + +#ifdef CONFIG_DEBUG_FS +struct summary_data { + struct seq_file *s; + struct regulator_dev *parent; + int level; +}; + +static void regulator_summary_show_subtree(struct seq_file *s, + struct regulator_dev *rdev, + int level); + +static int regulator_summary_show_children(struct device *dev, void *data) +{ + struct regulator_dev *rdev = dev_to_rdev(dev); + struct summary_data *summary_data = data; + + if (rdev->supply && rdev->supply->rdev == summary_data->parent) + regulator_summary_show_subtree(summary_data->s, rdev, + summary_data->level + 1); + + return 0; +} + +static void regulator_summary_show_subtree(struct seq_file *s, + struct regulator_dev *rdev, + int level) +{ + struct regulation_constraints *c; + struct regulator *consumer; + struct summary_data summary_data; + + if (!rdev) + return; + + seq_printf(s, "%*s%-*s %3d %4d %6d ", + level * 3 + 1, "", + 30 - level * 3, rdev_get_name(rdev), + rdev->use_count, rdev->open_count, rdev->bypass_count); + + seq_printf(s, "%5dmV ", _regulator_get_voltage(rdev) / 1000); + seq_printf(s, "%5dmA ", _regulator_get_current_limit(rdev) / 1000); + + c = rdev->constraints; + if (c) { + switch (rdev->desc->type) { + case REGULATOR_VOLTAGE: + seq_printf(s, "%5dmV %5dmV ", + c->min_uV / 1000, c->max_uV / 1000); + break; + case REGULATOR_CURRENT: + seq_printf(s, "%5dmA %5dmA ", + c->min_uA / 1000, c->max_uA / 1000); + break; + } + } + + seq_puts(s, "\n"); + + list_for_each_entry(consumer, &rdev->consumer_list, list) { + if (consumer->dev && consumer->dev->class == ®ulator_class) + continue; + + seq_printf(s, "%*s%-*s ", + (level + 1) * 3 + 1, "", + 30 - (level + 1) * 3, + consumer->dev ? dev_name(consumer->dev) : "deviceless"); + + switch (rdev->desc->type) { + case REGULATOR_VOLTAGE: + seq_printf(s, "%37dmV %5dmV", + consumer->voltage[PM_SUSPEND_ON].min_uV / 1000, + consumer->voltage[PM_SUSPEND_ON].max_uV / 1000); + break; + case REGULATOR_CURRENT: + break; + } + + seq_puts(s, "\n"); + } + + summary_data.s = s; + summary_data.level = level; + summary_data.parent = rdev; + + class_for_each_device(®ulator_class, NULL, &summary_data, + regulator_summary_show_children); +} + +static int regulator_summary_show_roots(struct device *dev, void *data) +{ + struct regulator_dev *rdev = dev_to_rdev(dev); + struct seq_file *s = data; + + if (!rdev->supply) + regulator_summary_show_subtree(s, rdev, 0); + + return 0; +} + +static int regulator_summary_show(struct seq_file *s, void *data) +{ + seq_puts(s, " regulator use open bypass voltage current min max\n"); + seq_puts(s, "-------------------------------------------------------------------------------\n"); + + class_for_each_device(®ulator_class, NULL, s, + regulator_summary_show_roots); + + return 0; +} + +static int regulator_summary_open(struct inode *inode, struct file *file) +{ + return single_open(file, regulator_summary_show, inode->i_private); +} +#endif + +static const struct file_operations regulator_summary_fops = { +#ifdef CONFIG_DEBUG_FS + .open = regulator_summary_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +#endif +}; + +static int __init regulator_init(void) +{ + int ret; + + ret = class_register(®ulator_class); + + debugfs_root = debugfs_create_dir("regulator", NULL); + if (!debugfs_root) + pr_warn("regulator: Failed to create debugfs directory\n"); + + debugfs_create_file("supply_map", 0444, debugfs_root, NULL, + &supply_map_fops); + + debugfs_create_file("regulator_summary", 0444, debugfs_root, + NULL, ®ulator_summary_fops); + + regulator_dummy_init(); + + return ret; +} + +/* init early to allow our consumers to complete system booting */ +core_initcall(regulator_init); + +static int regulator_late_cleanup(struct device *dev, void *data) +{ + struct regulator_dev *rdev = dev_to_rdev(dev); + const struct regulator_ops *ops = rdev->desc->ops; + struct regulation_constraints *c = rdev->constraints; + int enabled, ret; + + if (c && c->always_on) + return 0; + + if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_STATUS)) + return 0; + + regulator_lock(rdev); + + if (rdev->use_count) + goto unlock; + + /* If we can't read the status assume it's on. */ + if (ops->is_enabled) + enabled = ops->is_enabled(rdev); + else + enabled = 1; + + if (!enabled) + goto unlock; + + if (have_full_constraints()) { + /* We log since this may kill the system if it goes + * wrong. */ + rdev_info(rdev, "disabling\n"); + ret = _regulator_do_disable(rdev); + if (ret != 0) + rdev_err(rdev, "couldn't disable: %d\n", ret); + } else { + /* The intention is that in future we will + * assume that full constraints are provided + * so warn even if we aren't going to do + * anything here. + */ + rdev_warn(rdev, "incomplete constraints, leaving on\n"); + } + +unlock: + regulator_unlock(rdev); + + return 0; +} + +static void regulator_init_complete_work_function(struct work_struct *work) +{ + /* + * Regulators may had failed to resolve their input supplies + * when were registered, either because the input supply was + * not registered yet or because its parent device was not + * bound yet. So attempt to resolve the input supplies for + * pending regulators before trying to disable unused ones. + */ + class_for_each_device(®ulator_class, NULL, NULL, + regulator_register_resolve_supply); + + /* If we have a full configuration then disable any regulators + * we have permission to change the status for and which are + * not in use or always_on. This is effectively the default + * for DT and ACPI as they have full constraints. + */ + class_for_each_device(®ulator_class, NULL, NULL, + regulator_late_cleanup); +} + +static DECLARE_DELAYED_WORK(regulator_init_complete_work, + regulator_init_complete_work_function); + +static int __init regulator_init_complete(void) +{ + /* + * Since DT doesn't provide an idiomatic mechanism for + * enabling full constraints and since it's much more natural + * with DT to provide them just assume that a DT enabled + * system has full constraints. + */ + if (of_have_populated_dt()) + has_full_constraints = true; + + /* + * We punt completion for an arbitrary amount of time since + * systems like distros will load many drivers from userspace + * so consumers might not always be ready yet, this is + * particularly an issue with laptops where this might bounce + * the display off then on. Ideally we'd get a notification + * from userspace when this happens but we don't so just wait + * a bit and hope we waited long enough. It'd be better if + * we'd only do this on systems that need it, and a kernel + * command line option might be useful. + */ + schedule_delayed_work(®ulator_init_complete_work, + msecs_to_jiffies(30000)); + + class_for_each_device(®ulator_class, NULL, NULL, + regulator_register_fill_coupling_array); + + return 0; +} +late_initcall_sync(regulator_init_complete); diff --git a/drivers/regulator/cpcap-regulator.c b/drivers/regulator/cpcap-regulator.c new file mode 100644 index 000000000..213145793 --- /dev/null +++ b/drivers/regulator/cpcap-regulator.c @@ -0,0 +1,582 @@ +/* + * Motorola CPCAP PMIC regulator driver + * + * Based on cpcap-regulator.c from Motorola Linux kernel tree + * Copyright (C) 2009-2011 Motorola, Inc. + * + * Rewritten for mainline kernel to use device tree and regmap + * Copyright (C) 2017 Tony Lindgren <tony@atomide.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/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/motorola-cpcap.h> + +/* + * Resource assignment register bits. These seem to control the state + * idle modes adn are used at least for omap4. + */ + +/* CPCAP_REG_ASSIGN2 bits - Resource Assignment 2 */ +#define CPCAP_BIT_VSDIO_SEL BIT(15) +#define CPCAP_BIT_VDIG_SEL BIT(14) +#define CPCAP_BIT_VCAM_SEL BIT(13) +#define CPCAP_BIT_SW6_SEL BIT(12) +#define CPCAP_BIT_SW5_SEL BIT(11) +#define CPCAP_BIT_SW4_SEL BIT(10) +#define CPCAP_BIT_SW3_SEL BIT(9) +#define CPCAP_BIT_SW2_SEL BIT(8) +#define CPCAP_BIT_SW1_SEL BIT(7) + +/* CPCAP_REG_ASSIGN3 bits - Resource Assignment 3 */ +#define CPCAP_BIT_VUSBINT2_SEL BIT(15) +#define CPCAP_BIT_VUSBINT1_SEL BIT(14) +#define CPCAP_BIT_VVIB_SEL BIT(13) +#define CPCAP_BIT_VWLAN1_SEL BIT(12) +#define CPCAP_BIT_VRF1_SEL BIT(11) +#define CPCAP_BIT_VHVIO_SEL BIT(10) +#define CPCAP_BIT_VDAC_SEL BIT(9) +#define CPCAP_BIT_VUSB_SEL BIT(8) +#define CPCAP_BIT_VSIM_SEL BIT(7) +#define CPCAP_BIT_VRFREF_SEL BIT(6) +#define CPCAP_BIT_VPLL_SEL BIT(5) +#define CPCAP_BIT_VFUSE_SEL BIT(4) +#define CPCAP_BIT_VCSI_SEL BIT(3) +#define CPCAP_BIT_SPARE_14_2 BIT(2) +#define CPCAP_BIT_VWLAN2_SEL BIT(1) +#define CPCAP_BIT_VRF2_SEL BIT(0) + +/* CPCAP_REG_ASSIGN4 bits - Resource Assignment 4 */ +#define CPCAP_BIT_VAUDIO_SEL BIT(0) + +/* + * Enable register bits. At least CPCAP_BIT_AUDIO_LOW_PWR is generic, + * and not limited to audio regulator. Let's use the Motorola kernel + * naming for now until we have a better understanding of the other + * enable register bits. No idea why BIT(3) is not defined. + */ +#define CPCAP_BIT_AUDIO_LOW_PWR BIT(6) +#define CPCAP_BIT_AUD_LOWPWR_SPEED BIT(5) +#define CPCAP_BIT_VAUDIOPRISTBY BIT(4) +#define CPCAP_BIT_VAUDIO_MODE1 BIT(2) +#define CPCAP_BIT_VAUDIO_MODE0 BIT(1) +#define CPCAP_BIT_V_AUDIO_EN BIT(0) + +#define CPCAP_BIT_AUDIO_NORMAL_MODE 0x00 + +/* + * Off mode configuration bit. Used currently only by SW5 on omap4. There's + * the following comment in Motorola Linux kernel tree for it: + * + * When set in the regulator mode, the regulator assignment will be changed + * to secondary when the regulator is disabled. The mode will be set back to + * primary when the regulator is turned on. + */ +#define CPCAP_REG_OFF_MODE_SEC BIT(15) + +/** + * SoC specific configuraion for CPCAP regulator. There are at least three + * different SoCs each with their own parameters: omap3, omap4 and tegra2. + * + * The assign_reg and assign_mask seem to allow toggling between primary + * and secondary mode that at least omap4 uses for off mode. + */ +struct cpcap_regulator { + struct regulator_desc rdesc; + const u16 assign_reg; + const u16 assign_mask; + const u16 vsel_shift; +}; + +#define CPCAP_REG(_ID, reg, assignment_reg, assignment_mask, val_tbl, \ + mode_mask, volt_mask, volt_shft, \ + mode_val, off_val, volt_trans_time) { \ + .rdesc = { \ + .name = #_ID, \ + .of_match = of_match_ptr(#_ID), \ + .ops = &cpcap_regulator_ops, \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = CPCAP_##_ID, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(val_tbl), \ + .volt_table = (val_tbl), \ + .vsel_reg = (reg), \ + .vsel_mask = (volt_mask), \ + .enable_reg = (reg), \ + .enable_mask = (mode_mask), \ + .enable_val = (mode_val), \ + .disable_val = (off_val), \ + .ramp_delay = (volt_trans_time), \ + .of_map_mode = cpcap_map_mode, \ + }, \ + .assign_reg = (assignment_reg), \ + .assign_mask = (assignment_mask), \ + .vsel_shift = (volt_shft), \ +} + +struct cpcap_ddata { + struct regmap *reg; + struct device *dev; + const struct cpcap_regulator *soc; +}; + +enum cpcap_regulator_id { + CPCAP_SW1, + CPCAP_SW2, + CPCAP_SW3, + CPCAP_SW4, + CPCAP_SW5, + CPCAP_SW6, + CPCAP_VCAM, + CPCAP_VCSI, + CPCAP_VDAC, + CPCAP_VDIG, + CPCAP_VFUSE, + CPCAP_VHVIO, + CPCAP_VSDIO, + CPCAP_VPLL, + CPCAP_VRF1, + CPCAP_VRF2, + CPCAP_VRFREF, + CPCAP_VWLAN1, + CPCAP_VWLAN2, + CPCAP_VSIM, + CPCAP_VSIMCARD, + CPCAP_VVIB, + CPCAP_VUSB, + CPCAP_VAUDIO, + CPCAP_NR_REGULATORS, +}; + +/* + * We need to also configure regulator idle mode for SoC off mode if + * CPCAP_REG_OFF_MODE_SEC is set. + */ +static int cpcap_regulator_enable(struct regulator_dev *rdev) +{ + struct cpcap_regulator *regulator = rdev_get_drvdata(rdev); + int error, ignore; + + error = regulator_enable_regmap(rdev); + if (error) + return error; + + if (rdev->desc->enable_val & CPCAP_REG_OFF_MODE_SEC) { + error = regmap_update_bits(rdev->regmap, regulator->assign_reg, + regulator->assign_mask, + regulator->assign_mask); + if (error) + ignore = regulator_disable_regmap(rdev); + } + + return error; +} + +/* + * We need to also configure regulator idle mode for SoC off mode if + * CPCAP_REG_OFF_MODE_SEC is set. + */ +static int cpcap_regulator_disable(struct regulator_dev *rdev) +{ + struct cpcap_regulator *regulator = rdev_get_drvdata(rdev); + int error, ignore; + + if (rdev->desc->enable_val & CPCAP_REG_OFF_MODE_SEC) { + error = regmap_update_bits(rdev->regmap, regulator->assign_reg, + regulator->assign_mask, 0); + if (error) + return error; + } + + error = regulator_disable_regmap(rdev); + if (error && (rdev->desc->enable_val & CPCAP_REG_OFF_MODE_SEC)) { + ignore = regmap_update_bits(rdev->regmap, regulator->assign_reg, + regulator->assign_mask, + regulator->assign_mask); + } + + return error; +} + +static unsigned int cpcap_map_mode(unsigned int mode) +{ + switch (mode) { + case CPCAP_BIT_AUDIO_NORMAL_MODE: + return REGULATOR_MODE_NORMAL; + case CPCAP_BIT_AUDIO_LOW_PWR: + return REGULATOR_MODE_STANDBY; + default: + return REGULATOR_MODE_INVALID; + } +} + +static unsigned int cpcap_regulator_get_mode(struct regulator_dev *rdev) +{ + int value; + + regmap_read(rdev->regmap, rdev->desc->enable_reg, &value); + + if (value & CPCAP_BIT_AUDIO_LOW_PWR) + return REGULATOR_MODE_STANDBY; + + return REGULATOR_MODE_NORMAL; +} + +static int cpcap_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + int value; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + value = CPCAP_BIT_AUDIO_NORMAL_MODE; + break; + case REGULATOR_MODE_STANDBY: + value = CPCAP_BIT_AUDIO_LOW_PWR; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + CPCAP_BIT_AUDIO_LOW_PWR, value); +} + +static struct regulator_ops cpcap_regulator_ops = { + .enable = cpcap_regulator_enable, + .disable = cpcap_regulator_disable, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_mode = cpcap_regulator_get_mode, + .set_mode = cpcap_regulator_set_mode, +}; + +static const unsigned int unknown_val_tbl[] = { 0, }; +static const unsigned int sw2_sw4_val_tbl[] = { 612500, 625000, 637500, + 650000, 662500, 675000, + 687500, 700000, 712500, + 725000, 737500, 750000, + 762500, 775000, 787500, + 800000, 812500, 825000, + 837500, 850000, 862500, + 875000, 887500, 900000, + 912500, 925000, 937500, + 950000, 962500, 975000, + 987500, 1000000, 1012500, + 1025000, 1037500, 1050000, + 1062500, 1075000, 1087500, + 1100000, 1112500, 1125000, + 1137500, 1150000, 1162500, + 1175000, 1187500, 1200000, + 1212500, 1225000, 1237500, + 1250000, 1262500, 1275000, + 1287500, 1300000, 1312500, + 1325000, 1337500, 1350000, + 1362500, 1375000, 1387500, + 1400000, 1412500, 1425000, + 1437500, 1450000, 1462500, }; +static const unsigned int sw5_val_tbl[] = { 0, 5050000, }; +static const unsigned int vcam_val_tbl[] = { 2600000, 2700000, 2800000, + 2900000, }; +static const unsigned int vcsi_val_tbl[] = { 1200000, 1800000, }; +static const unsigned int vdac_val_tbl[] = { 1200000, 1500000, 1800000, + 2500000,}; +static const unsigned int vdig_val_tbl[] = { 1200000, 1350000, 1500000, + 1875000, }; +static const unsigned int vfuse_val_tbl[] = { 1500000, 1600000, 1700000, + 1800000, 1900000, 2000000, + 2100000, 2200000, 2300000, + 2400000, 2500000, 2600000, + 2700000, 3150000, }; +static const unsigned int vhvio_val_tbl[] = { 2775000, }; +static const unsigned int vsdio_val_tbl[] = { 1500000, 1600000, 1800000, + 2600000, 2700000, 2800000, + 2900000, 3000000, }; +static const unsigned int vpll_val_tbl[] = { 1200000, 1300000, 1400000, + 1800000, }; +/* Quirk: 2775000 is before 2500000 for vrf1 regulator */ +static const unsigned int vrf1_val_tbl[] = { 2775000, 2500000, }; +static const unsigned int vrf2_val_tbl[] = { 0, 2775000, }; +static const unsigned int vrfref_val_tbl[] = { 2500000, 2775000, }; +static const unsigned int vwlan1_val_tbl[] = { 1800000, 1900000, }; +static const unsigned int vwlan2_val_tbl[] = { 2775000, 3000000, 3300000, + 3300000, }; +static const unsigned int vsim_val_tbl[] = { 1800000, 2900000, }; +static const unsigned int vsimcard_val_tbl[] = { 1800000, 2900000, }; +static const unsigned int vvib_val_tbl[] = { 1300000, 1800000, 2000000, + 3000000, }; +static const unsigned int vusb_val_tbl[] = { 0, 3300000, }; +static const unsigned int vaudio_val_tbl[] = { 0, 2775000, }; + +/** + * SoC specific configuration for omap4. The data below is comes from Motorola + * Linux kernel tree. It's basically the values of cpcap_regltr_data, + * cpcap_regulator_mode_values and cpcap_regulator_off_mode_values, see + * CPCAP_REG macro above. + * + * SW1 to SW4 and SW6 seems to be unused for mapphone. Note that VSIM and + * VSIMCARD have a shared resource assignment bit. + */ +static struct cpcap_regulator omap4_regulators[] = { + CPCAP_REG(SW1, CPCAP_REG_S1C1, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW1_SEL, unknown_val_tbl, + 0, 0, 0, 0, 0, 0), + CPCAP_REG(SW2, CPCAP_REG_S2C1, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW2_SEL, unknown_val_tbl, + 0, 0, 0, 0, 0, 0), + CPCAP_REG(SW3, CPCAP_REG_S3C, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW3_SEL, unknown_val_tbl, + 0, 0, 0, 0, 0, 0), + CPCAP_REG(SW4, CPCAP_REG_S4C1, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW4_SEL, unknown_val_tbl, + 0, 0, 0, 0, 0, 0), + CPCAP_REG(SW5, CPCAP_REG_S5C, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW5_SEL, sw5_val_tbl, + 0x28, 0, 0, 0x20 | CPCAP_REG_OFF_MODE_SEC, 0, 0), + CPCAP_REG(SW6, CPCAP_REG_S6C, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW6_SEL, unknown_val_tbl, + 0, 0, 0, 0, 0, 0), + CPCAP_REG(VCAM, CPCAP_REG_VCAMC, CPCAP_REG_ASSIGN2, + CPCAP_BIT_VCAM_SEL, vcam_val_tbl, + 0x87, 0x30, 4, 0x3, 0, 420), + CPCAP_REG(VCSI, CPCAP_REG_VCSIC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VCSI_SEL, vcsi_val_tbl, + 0x47, 0x10, 4, 0x43, 0x41, 350), + CPCAP_REG(VDAC, CPCAP_REG_VDACC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VDAC_SEL, vdac_val_tbl, + 0x87, 0x30, 4, 0x3, 0, 420), + CPCAP_REG(VDIG, CPCAP_REG_VDIGC, CPCAP_REG_ASSIGN2, + CPCAP_BIT_VDIG_SEL, vdig_val_tbl, + 0x87, 0x30, 4, 0x82, 0, 420), + CPCAP_REG(VFUSE, CPCAP_REG_VFUSEC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VFUSE_SEL, vfuse_val_tbl, + 0x80, 0xf, 0, 0x80, 0, 420), + CPCAP_REG(VHVIO, CPCAP_REG_VHVIOC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VHVIO_SEL, vhvio_val_tbl, + 0x17, 0, 0, 0, 0x12, 0), + CPCAP_REG(VSDIO, CPCAP_REG_VSDIOC, CPCAP_REG_ASSIGN2, + CPCAP_BIT_VSDIO_SEL, vsdio_val_tbl, + 0x87, 0x38, 3, 0x82, 0, 420), + CPCAP_REG(VPLL, CPCAP_REG_VPLLC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VPLL_SEL, vpll_val_tbl, + 0x43, 0x18, 3, 0x2, 0, 420), + CPCAP_REG(VRF1, CPCAP_REG_VRF1C, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VRF1_SEL, vrf1_val_tbl, + 0xac, 0x2, 1, 0x4, 0, 10), + CPCAP_REG(VRF2, CPCAP_REG_VRF2C, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VRF2_SEL, vrf2_val_tbl, + 0x23, 0x8, 3, 0, 0, 10), + CPCAP_REG(VRFREF, CPCAP_REG_VRFREFC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VRFREF_SEL, vrfref_val_tbl, + 0x23, 0x8, 3, 0, 0, 420), + CPCAP_REG(VWLAN1, CPCAP_REG_VWLAN1C, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VWLAN1_SEL, vwlan1_val_tbl, + 0x47, 0x10, 4, 0, 0, 420), + CPCAP_REG(VWLAN2, CPCAP_REG_VWLAN2C, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VWLAN2_SEL, vwlan2_val_tbl, + 0x20c, 0xc0, 6, 0x20c, 0, 420), + CPCAP_REG(VSIM, CPCAP_REG_VSIMC, CPCAP_REG_ASSIGN3, + 0xffff, vsim_val_tbl, + 0x23, 0x8, 3, 0x3, 0, 420), + CPCAP_REG(VSIMCARD, CPCAP_REG_VSIMC, CPCAP_REG_ASSIGN3, + 0xffff, vsimcard_val_tbl, + 0x1e80, 0x8, 3, 0x1e00, 0, 420), + CPCAP_REG(VVIB, CPCAP_REG_VVIBC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VVIB_SEL, vvib_val_tbl, + 0x1, 0xc, 2, 0x1, 0, 500), + CPCAP_REG(VUSB, CPCAP_REG_VUSBC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VUSB_SEL, vusb_val_tbl, + 0x11c, 0x40, 6, 0xc, 0, 0), + CPCAP_REG(VAUDIO, CPCAP_REG_VAUDIOC, CPCAP_REG_ASSIGN4, + CPCAP_BIT_VAUDIO_SEL, vaudio_val_tbl, + 0x16, 0x1, 0, 0x4, 0, 0), + { /* sentinel */ }, +}; + +static struct cpcap_regulator xoom_regulators[] = { + CPCAP_REG(SW1, CPCAP_REG_S1C1, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW1_SEL, unknown_val_tbl, + 0, 0, 0, 0, 0, 0), + CPCAP_REG(SW2, CPCAP_REG_S2C1, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW2_SEL, sw2_sw4_val_tbl, + 0xf00, 0x7f, 0, 0x800, 0, 120), + CPCAP_REG(SW3, CPCAP_REG_S3C, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW3_SEL, unknown_val_tbl, + 0, 0, 0, 0, 0, 0), + CPCAP_REG(SW4, CPCAP_REG_S4C1, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW4_SEL, sw2_sw4_val_tbl, + 0xf00, 0x7f, 0, 0x900, 0, 100), + CPCAP_REG(SW5, CPCAP_REG_S5C, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW5_SEL, sw5_val_tbl, + 0x2a, 0, 0, 0x22, 0, 0), + CPCAP_REG(SW6, CPCAP_REG_S6C, CPCAP_REG_ASSIGN2, + CPCAP_BIT_SW6_SEL, unknown_val_tbl, + 0, 0, 0, 0, 0, 0), + CPCAP_REG(VCAM, CPCAP_REG_VCAMC, CPCAP_REG_ASSIGN2, + CPCAP_BIT_VCAM_SEL, vcam_val_tbl, + 0x87, 0x30, 4, 0x7, 0, 420), + CPCAP_REG(VCSI, CPCAP_REG_VCSIC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VCSI_SEL, vcsi_val_tbl, + 0x47, 0x10, 4, 0x7, 0, 350), + CPCAP_REG(VDAC, CPCAP_REG_VDACC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VDAC_SEL, vdac_val_tbl, + 0x87, 0x30, 4, 0x3, 0, 420), + CPCAP_REG(VDIG, CPCAP_REG_VDIGC, CPCAP_REG_ASSIGN2, + CPCAP_BIT_VDIG_SEL, vdig_val_tbl, + 0x87, 0x30, 4, 0x5, 0, 420), + CPCAP_REG(VFUSE, CPCAP_REG_VFUSEC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VFUSE_SEL, vfuse_val_tbl, + 0x80, 0xf, 0, 0x80, 0, 420), + CPCAP_REG(VHVIO, CPCAP_REG_VHVIOC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VHVIO_SEL, vhvio_val_tbl, + 0x17, 0, 0, 0x2, 0, 0), + CPCAP_REG(VSDIO, CPCAP_REG_VSDIOC, CPCAP_REG_ASSIGN2, + CPCAP_BIT_VSDIO_SEL, vsdio_val_tbl, + 0x87, 0x38, 3, 0x2, 0, 420), + CPCAP_REG(VPLL, CPCAP_REG_VPLLC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VPLL_SEL, vpll_val_tbl, + 0x43, 0x18, 3, 0x1, 0, 420), + CPCAP_REG(VRF1, CPCAP_REG_VRF1C, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VRF1_SEL, vrf1_val_tbl, + 0xac, 0x2, 1, 0xc, 0, 10), + CPCAP_REG(VRF2, CPCAP_REG_VRF2C, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VRF2_SEL, vrf2_val_tbl, + 0x23, 0x8, 3, 0x3, 0, 10), + CPCAP_REG(VRFREF, CPCAP_REG_VRFREFC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VRFREF_SEL, vrfref_val_tbl, + 0x23, 0x8, 3, 0x3, 0, 420), + CPCAP_REG(VWLAN1, CPCAP_REG_VWLAN1C, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VWLAN1_SEL, vwlan1_val_tbl, + 0x47, 0x10, 4, 0x5, 0, 420), + CPCAP_REG(VWLAN2, CPCAP_REG_VWLAN2C, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VWLAN2_SEL, vwlan2_val_tbl, + 0x20c, 0xc0, 6, 0x8, 0, 420), + CPCAP_REG(VSIM, CPCAP_REG_VSIMC, CPCAP_REG_ASSIGN3, + 0xffff, vsim_val_tbl, + 0x23, 0x8, 3, 0x3, 0, 420), + CPCAP_REG(VSIMCARD, CPCAP_REG_VSIMC, CPCAP_REG_ASSIGN3, + 0xffff, vsimcard_val_tbl, + 0x1e80, 0x8, 3, 0x1e00, 0, 420), + CPCAP_REG(VVIB, CPCAP_REG_VVIBC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VVIB_SEL, vvib_val_tbl, + 0x1, 0xc, 2, 0, 0x1, 500), + CPCAP_REG(VUSB, CPCAP_REG_VUSBC, CPCAP_REG_ASSIGN3, + CPCAP_BIT_VUSB_SEL, vusb_val_tbl, + 0x11c, 0x40, 6, 0xc, 0, 0), + CPCAP_REG(VAUDIO, CPCAP_REG_VAUDIOC, CPCAP_REG_ASSIGN4, + CPCAP_BIT_VAUDIO_SEL, vaudio_val_tbl, + 0x16, 0x1, 0, 0x4, 0, 0), + { /* sentinel */ }, +}; + +static const struct of_device_id cpcap_regulator_id_table[] = { + { + .compatible = "motorola,cpcap-regulator", + }, + { + .compatible = "motorola,mapphone-cpcap-regulator", + .data = omap4_regulators, + }, + { + .compatible = "motorola,xoom-cpcap-regulator", + .data = xoom_regulators, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cpcap_regulator_id_table); + +static int cpcap_regulator_probe(struct platform_device *pdev) +{ + struct cpcap_ddata *ddata; + const struct of_device_id *match; + struct regulator_config config; + struct regulator_init_data init_data; + int i; + + match = of_match_device(of_match_ptr(cpcap_regulator_id_table), + &pdev->dev); + if (!match) + return -EINVAL; + + if (!match->data) { + dev_err(&pdev->dev, "no configuration data found\n"); + + return -ENODEV; + } + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->reg = dev_get_regmap(pdev->dev.parent, NULL); + if (!ddata->reg) + return -ENODEV; + + ddata->dev = &pdev->dev; + ddata->soc = match->data; + platform_set_drvdata(pdev, ddata); + + memset(&config, 0, sizeof(config)); + memset(&init_data, 0, sizeof(init_data)); + config.dev = &pdev->dev; + config.regmap = ddata->reg; + config.init_data = &init_data; + + for (i = 0; i < CPCAP_NR_REGULATORS; i++) { + const struct cpcap_regulator *regulator = &ddata->soc[i]; + struct regulator_dev *rdev; + + if (!regulator->rdesc.name) + break; + + if (regulator->rdesc.volt_table == unknown_val_tbl) + continue; + + config.driver_data = (void *)regulator; + rdev = devm_regulator_register(&pdev->dev, + ®ulator->rdesc, + &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + regulator->rdesc.name); + + return PTR_ERR(rdev); + } + } + + return 0; +} + +static struct platform_driver cpcap_regulator_driver = { + .probe = cpcap_regulator_probe, + .driver = { + .name = "cpcap-regulator", + .of_match_table = of_match_ptr(cpcap_regulator_id_table), + }, +}; + +module_platform_driver(cpcap_regulator_driver); + +MODULE_ALIAS("platform:cpcap-regulator"); +MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); +MODULE_DESCRIPTION("CPCAP regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/da903x.c b/drivers/regulator/da903x.c new file mode 100644 index 000000000..33e8f3b8d --- /dev/null +++ b/drivers/regulator/da903x.c @@ -0,0 +1,498 @@ +/* + * Regulators driver for Dialog Semiconductor DA903x + * + * Copyright (C) 2006-2008 Marvell International Ltd. + * Copyright (C) 2008 Compulab Ltd. + * + * 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/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/da903x.h> + +/* DA9030 Registers */ +#define DA9030_INVAL (-1) +#define DA9030_LDO1011 (0x10) +#define DA9030_LDO15 (0x11) +#define DA9030_LDO1416 (0x12) +#define DA9030_LDO1819 (0x13) +#define DA9030_LDO17 (0x14) +#define DA9030_BUCK2DVM1 (0x15) +#define DA9030_BUCK2DVM2 (0x16) +#define DA9030_RCTL11 (0x17) +#define DA9030_RCTL21 (0x18) +#define DA9030_LDO1 (0x90) +#define DA9030_LDO23 (0x91) +#define DA9030_LDO45 (0x92) +#define DA9030_LDO6 (0x93) +#define DA9030_LDO78 (0x94) +#define DA9030_LDO912 (0x95) +#define DA9030_BUCK (0x96) +#define DA9030_RCTL12 (0x97) +#define DA9030_RCTL22 (0x98) +#define DA9030_LDO_UNLOCK (0xa0) +#define DA9030_LDO_UNLOCK_MASK (0xe0) +#define DA9034_OVER1 (0x10) + +/* DA9034 Registers */ +#define DA9034_INVAL (-1) +#define DA9034_OVER2 (0x11) +#define DA9034_OVER3 (0x12) +#define DA9034_LDO643 (0x13) +#define DA9034_LDO987 (0x14) +#define DA9034_LDO1110 (0x15) +#define DA9034_LDO1312 (0x16) +#define DA9034_LDO1514 (0x17) +#define DA9034_VCC1 (0x20) +#define DA9034_ADTV1 (0x23) +#define DA9034_ADTV2 (0x24) +#define DA9034_AVRC (0x25) +#define DA9034_CDTV1 (0x26) +#define DA9034_CDTV2 (0x27) +#define DA9034_CVRC (0x28) +#define DA9034_SDTV1 (0x29) +#define DA9034_SDTV2 (0x2a) +#define DA9034_SVRC (0x2b) +#define DA9034_MDTV1 (0x32) +#define DA9034_MDTV2 (0x33) +#define DA9034_MVRC (0x34) + +/* DA9035 Registers. DA9034 Registers are comptabile to DA9035. */ +#define DA9035_OVER3 (0x12) +#define DA9035_VCC2 (0x1f) +#define DA9035_3DTV1 (0x2c) +#define DA9035_3DTV2 (0x2d) +#define DA9035_3VRC (0x2e) +#define DA9035_AUTOSKIP (0x2f) + +struct da903x_regulator_info { + struct regulator_desc desc; + + int max_uV; + int vol_reg; + int vol_shift; + int vol_nbits; + int update_reg; + int update_bit; + int enable_reg; + int enable_bit; +}; + +static inline struct device *to_da903x_dev(struct regulator_dev *rdev) +{ + return rdev_get_dev(rdev)->parent->parent; +} + +static inline int check_range(struct da903x_regulator_info *info, + int min_uV, int max_uV) +{ + if (min_uV < info->desc.min_uV || min_uV > info->max_uV) + return -EINVAL; + + return 0; +} + +/* DA9030/DA9034 common operations */ +static int da903x_set_voltage_sel(struct regulator_dev *rdev, unsigned selector) +{ + struct da903x_regulator_info *info = rdev_get_drvdata(rdev); + struct device *da9034_dev = to_da903x_dev(rdev); + uint8_t val, mask; + + if (rdev->desc->n_voltages == 1) + return -EINVAL; + + val = selector << info->vol_shift; + mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; + + return da903x_update(da9034_dev, info->vol_reg, val, mask); +} + +static int da903x_get_voltage_sel(struct regulator_dev *rdev) +{ + struct da903x_regulator_info *info = rdev_get_drvdata(rdev); + struct device *da9034_dev = to_da903x_dev(rdev); + uint8_t val, mask; + int ret; + + if (rdev->desc->n_voltages == 1) + return 0; + + ret = da903x_read(da9034_dev, info->vol_reg, &val); + if (ret) + return ret; + + mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; + val = (val & mask) >> info->vol_shift; + + return val; +} + +static int da903x_enable(struct regulator_dev *rdev) +{ + struct da903x_regulator_info *info = rdev_get_drvdata(rdev); + struct device *da9034_dev = to_da903x_dev(rdev); + + return da903x_set_bits(da9034_dev, info->enable_reg, + 1 << info->enable_bit); +} + +static int da903x_disable(struct regulator_dev *rdev) +{ + struct da903x_regulator_info *info = rdev_get_drvdata(rdev); + struct device *da9034_dev = to_da903x_dev(rdev); + + return da903x_clr_bits(da9034_dev, info->enable_reg, + 1 << info->enable_bit); +} + +static int da903x_is_enabled(struct regulator_dev *rdev) +{ + struct da903x_regulator_info *info = rdev_get_drvdata(rdev); + struct device *da9034_dev = to_da903x_dev(rdev); + uint8_t reg_val; + int ret; + + ret = da903x_read(da9034_dev, info->enable_reg, ®_val); + if (ret) + return ret; + + return !!(reg_val & (1 << info->enable_bit)); +} + +/* DA9030 specific operations */ +static int da9030_set_ldo1_15_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct da903x_regulator_info *info = rdev_get_drvdata(rdev); + struct device *da903x_dev = to_da903x_dev(rdev); + uint8_t val, mask; + int ret; + + val = selector << info->vol_shift; + mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; + val |= DA9030_LDO_UNLOCK; /* have to set UNLOCK bits */ + mask |= DA9030_LDO_UNLOCK_MASK; + + /* write twice */ + ret = da903x_update(da903x_dev, info->vol_reg, val, mask); + if (ret) + return ret; + + return da903x_update(da903x_dev, info->vol_reg, val, mask); +} + +static int da9030_map_ldo14_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct da903x_regulator_info *info = rdev_get_drvdata(rdev); + int thresh, sel; + + if (check_range(info, min_uV, max_uV)) { + pr_err("invalid voltage range (%d, %d) uV\n", min_uV, max_uV); + return -EINVAL; + } + + thresh = (info->max_uV + info->desc.min_uV) / 2; + if (min_uV < thresh) { + sel = DIV_ROUND_UP(thresh - min_uV, info->desc.uV_step); + sel |= 0x4; + } else { + sel = DIV_ROUND_UP(min_uV - thresh, info->desc.uV_step); + } + + return sel; +} + +static int da9030_list_ldo14_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct da903x_regulator_info *info = rdev_get_drvdata(rdev); + int volt; + + if (selector & 0x4) + volt = rdev->desc->min_uV + + rdev->desc->uV_step * (3 - (selector & ~0x4)); + else + volt = (info->max_uV + rdev->desc->min_uV) / 2 + + rdev->desc->uV_step * (selector & ~0x4); + + if (volt > info->max_uV) + return -EINVAL; + + return volt; +} + +/* DA9034 specific operations */ +static int da9034_set_dvc_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct da903x_regulator_info *info = rdev_get_drvdata(rdev); + struct device *da9034_dev = to_da903x_dev(rdev); + uint8_t val, mask; + int ret; + + val = selector << info->vol_shift; + mask = ((1 << info->vol_nbits) - 1) << info->vol_shift; + + ret = da903x_update(da9034_dev, info->vol_reg, val, mask); + if (ret) + return ret; + + ret = da903x_set_bits(da9034_dev, info->update_reg, + 1 << info->update_bit); + return ret; +} + +static const struct regulator_linear_range da9034_ldo12_ranges[] = { + REGULATOR_LINEAR_RANGE(1700000, 0, 7, 50000), + REGULATOR_LINEAR_RANGE(2700000, 8, 15, 50000), +}; + +static const struct regulator_ops da903x_regulator_ldo_ops = { + .set_voltage_sel = da903x_set_voltage_sel, + .get_voltage_sel = da903x_get_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .enable = da903x_enable, + .disable = da903x_disable, + .is_enabled = da903x_is_enabled, +}; + +/* NOTE: this is dedicated for the insane DA9030 LDO14 */ +static const struct regulator_ops da9030_regulator_ldo14_ops = { + .set_voltage_sel = da903x_set_voltage_sel, + .get_voltage_sel = da903x_get_voltage_sel, + .list_voltage = da9030_list_ldo14_voltage, + .map_voltage = da9030_map_ldo14_voltage, + .enable = da903x_enable, + .disable = da903x_disable, + .is_enabled = da903x_is_enabled, +}; + +/* NOTE: this is dedicated for the DA9030 LDO1 and LDO15 that have locks */ +static const struct regulator_ops da9030_regulator_ldo1_15_ops = { + .set_voltage_sel = da9030_set_ldo1_15_voltage_sel, + .get_voltage_sel = da903x_get_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .enable = da903x_enable, + .disable = da903x_disable, + .is_enabled = da903x_is_enabled, +}; + +static const struct regulator_ops da9034_regulator_dvc_ops = { + .set_voltage_sel = da9034_set_dvc_voltage_sel, + .get_voltage_sel = da903x_get_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .enable = da903x_enable, + .disable = da903x_disable, + .is_enabled = da903x_is_enabled, +}; + +/* NOTE: this is dedicated for the insane LDO12 */ +static const struct regulator_ops da9034_regulator_ldo12_ops = { + .set_voltage_sel = da903x_set_voltage_sel, + .get_voltage_sel = da903x_get_voltage_sel, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .enable = da903x_enable, + .disable = da903x_disable, + .is_enabled = da903x_is_enabled, +}; + +#define DA903x_LDO(_pmic, _id, min, max, step, vreg, shift, nbits, ereg, ebit) \ +{ \ + .desc = { \ + .name = "LDO" #_id, \ + .ops = &da903x_regulator_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _pmic##_ID_LDO##_id, \ + .n_voltages = (step) ? ((max - min) / step + 1) : 1, \ + .owner = THIS_MODULE, \ + .min_uV = (min) * 1000, \ + .uV_step = (step) * 1000, \ + }, \ + .max_uV = (max) * 1000, \ + .vol_reg = _pmic##_##vreg, \ + .vol_shift = (shift), \ + .vol_nbits = (nbits), \ + .enable_reg = _pmic##_##ereg, \ + .enable_bit = (ebit), \ +} + +#define DA903x_DVC(_pmic, _id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ +{ \ + .desc = { \ + .name = #_id, \ + .ops = &da9034_regulator_dvc_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _pmic##_ID_##_id, \ + .n_voltages = (step) ? ((max - min) / step + 1) : 1, \ + .owner = THIS_MODULE, \ + .min_uV = (min) * 1000, \ + .uV_step = (step) * 1000, \ + }, \ + .max_uV = (max) * 1000, \ + .vol_reg = _pmic##_##vreg, \ + .vol_shift = (0), \ + .vol_nbits = (nbits), \ + .update_reg = _pmic##_##ureg, \ + .update_bit = (ubit), \ + .enable_reg = _pmic##_##ereg, \ + .enable_bit = (ebit), \ +} + +#define DA9034_LDO(_id, min, max, step, vreg, shift, nbits, ereg, ebit) \ + DA903x_LDO(DA9034, _id, min, max, step, vreg, shift, nbits, ereg, ebit) + +#define DA9030_LDO(_id, min, max, step, vreg, shift, nbits, ereg, ebit) \ + DA903x_LDO(DA9030, _id, min, max, step, vreg, shift, nbits, ereg, ebit) + +#define DA9030_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ + DA903x_DVC(DA9030, _id, min, max, step, vreg, nbits, ureg, ubit, \ + ereg, ebit) + +#define DA9034_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ + DA903x_DVC(DA9034, _id, min, max, step, vreg, nbits, ureg, ubit, \ + ereg, ebit) + +#define DA9035_DVC(_id, min, max, step, vreg, nbits, ureg, ubit, ereg, ebit) \ + DA903x_DVC(DA9035, _id, min, max, step, vreg, nbits, ureg, ubit, \ + ereg, ebit) + +static struct da903x_regulator_info da903x_regulator_info[] = { + /* DA9030 */ + DA9030_DVC(BUCK2, 850, 1625, 25, BUCK2DVM1, 5, BUCK2DVM1, 7, RCTL11, 0), + + DA9030_LDO( 1, 1200, 3200, 100, LDO1, 0, 5, RCTL12, 1), + DA9030_LDO( 2, 1800, 3200, 100, LDO23, 0, 4, RCTL12, 2), + DA9030_LDO( 3, 1800, 3200, 100, LDO23, 4, 4, RCTL12, 3), + DA9030_LDO( 4, 1800, 3200, 100, LDO45, 0, 4, RCTL12, 4), + DA9030_LDO( 5, 1800, 3200, 100, LDO45, 4, 4, RCTL12, 5), + DA9030_LDO( 6, 1800, 3200, 100, LDO6, 0, 4, RCTL12, 6), + DA9030_LDO( 7, 1800, 3200, 100, LDO78, 0, 4, RCTL12, 7), + DA9030_LDO( 8, 1800, 3200, 100, LDO78, 4, 4, RCTL22, 0), + DA9030_LDO( 9, 1800, 3200, 100, LDO912, 0, 4, RCTL22, 1), + DA9030_LDO(10, 1800, 3200, 100, LDO1011, 0, 4, RCTL22, 2), + DA9030_LDO(11, 1800, 3200, 100, LDO1011, 4, 4, RCTL22, 3), + DA9030_LDO(12, 1800, 3200, 100, LDO912, 4, 4, RCTL22, 4), + DA9030_LDO(14, 2760, 2940, 30, LDO1416, 0, 3, RCTL11, 4), + DA9030_LDO(15, 1100, 2650, 50, LDO15, 0, 5, RCTL11, 5), + DA9030_LDO(16, 1100, 2650, 50, LDO1416, 3, 5, RCTL11, 6), + DA9030_LDO(17, 1800, 3200, 100, LDO17, 0, 4, RCTL11, 7), + DA9030_LDO(18, 1800, 3200, 100, LDO1819, 0, 4, RCTL21, 2), + DA9030_LDO(19, 1800, 3200, 100, LDO1819, 4, 4, RCTL21, 1), + DA9030_LDO(13, 2100, 2100, 0, INVAL, 0, 0, RCTL11, 3), /* fixed @2.1V */ + + /* DA9034 */ + DA9034_DVC(BUCK1, 725, 1500, 25, ADTV2, 5, VCC1, 0, OVER1, 0), + DA9034_DVC(BUCK2, 725, 1500, 25, CDTV2, 5, VCC1, 2, OVER1, 1), + DA9034_DVC(LDO2, 725, 1500, 25, SDTV2, 5, VCC1, 4, OVER1, 2), + DA9034_DVC(LDO1, 1700, 2075, 25, MDTV1, 4, VCC1, 6, OVER3, 4), + + DA9034_LDO( 3, 1800, 3300, 100, LDO643, 0, 4, OVER3, 5), + DA9034_LDO( 4, 1800, 2900,1100, LDO643, 4, 1, OVER3, 6), + DA9034_LDO( 6, 2500, 2850, 50, LDO643, 5, 3, OVER2, 0), + DA9034_LDO( 7, 2700, 3050, 50, LDO987, 0, 3, OVER2, 1), + DA9034_LDO( 8, 2700, 2850, 50, LDO987, 3, 2, OVER2, 2), + DA9034_LDO( 9, 2700, 3050, 50, LDO987, 5, 3, OVER2, 3), + DA9034_LDO(10, 2700, 3050, 50, LDO1110, 0, 3, OVER2, 4), + DA9034_LDO(11, 1800, 3300, 100, LDO1110, 4, 4, OVER2, 5), + DA9034_LDO(12, 1700, 3050, 50, LDO1312, 0, 4, OVER3, 6), + DA9034_LDO(13, 1800, 3300, 100, LDO1312, 4, 4, OVER2, 7), + DA9034_LDO(14, 1800, 3300, 100, LDO1514, 0, 4, OVER3, 0), + DA9034_LDO(15, 1800, 3300, 100, LDO1514, 4, 4, OVER3, 1), + DA9034_LDO(5, 3100, 3100, 0, INVAL, 0, 0, OVER3, 7), /* fixed @3.1V */ + + /* DA9035 */ + DA9035_DVC(BUCK3, 1800, 2200, 100, 3DTV1, 3, VCC2, 0, OVER3, 3), +}; + +static inline struct da903x_regulator_info *find_regulator_info(int id) +{ + struct da903x_regulator_info *ri; + int i; + + for (i = 0; i < ARRAY_SIZE(da903x_regulator_info); i++) { + ri = &da903x_regulator_info[i]; + if (ri->desc.id == id) + return ri; + } + return NULL; +} + +static int da903x_regulator_probe(struct platform_device *pdev) +{ + struct da903x_regulator_info *ri = NULL; + struct regulator_dev *rdev; + struct regulator_config config = { }; + + ri = find_regulator_info(pdev->id); + if (ri == NULL) { + dev_err(&pdev->dev, "invalid regulator ID specified\n"); + return -EINVAL; + } + + /* Workaround for the weird LDO12 voltage setting */ + if (ri->desc.id == DA9034_ID_LDO12) { + ri->desc.ops = &da9034_regulator_ldo12_ops; + ri->desc.n_voltages = 16; + ri->desc.linear_ranges = da9034_ldo12_ranges; + ri->desc.n_linear_ranges = ARRAY_SIZE(da9034_ldo12_ranges); + } + + if (ri->desc.id == DA9030_ID_LDO14) + ri->desc.ops = &da9030_regulator_ldo14_ops; + + if (ri->desc.id == DA9030_ID_LDO1 || ri->desc.id == DA9030_ID_LDO15) + ri->desc.ops = &da9030_regulator_ldo1_15_ops; + + config.dev = &pdev->dev; + config.init_data = dev_get_platdata(&pdev->dev); + config.driver_data = ri; + + rdev = devm_regulator_register(&pdev->dev, &ri->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + ri->desc.name); + return PTR_ERR(rdev); + } + + platform_set_drvdata(pdev, rdev); + return 0; +} + +static struct platform_driver da903x_regulator_driver = { + .driver = { + .name = "da903x-regulator", + }, + .probe = da903x_regulator_probe, +}; + +static int __init da903x_regulator_init(void) +{ + return platform_driver_register(&da903x_regulator_driver); +} +subsys_initcall(da903x_regulator_init); + +static void __exit da903x_regulator_exit(void) +{ + platform_driver_unregister(&da903x_regulator_driver); +} +module_exit(da903x_regulator_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>" + "Mike Rapoport <mike@compulab.co.il>"); +MODULE_DESCRIPTION("Regulator Driver for Dialog Semiconductor DA903X PMIC"); +MODULE_ALIAS("platform:da903x-regulator"); diff --git a/drivers/regulator/da9052-regulator.c b/drivers/regulator/da9052-regulator.c new file mode 100644 index 000000000..bd91c95f7 --- /dev/null +++ b/drivers/regulator/da9052-regulator.c @@ -0,0 +1,488 @@ +/* +* da9052-regulator.c: Regulator driver for DA9052 +* +* Copyright(c) 2011 Dialog Semiconductor Ltd. +* +* Author: David Dajun Chen <dchen@diasemi.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; either version 2 of the License, or +* (at your option) any later version. +* +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#ifdef CONFIG_OF +#include <linux/of.h> +#include <linux/regulator/of_regulator.h> +#endif + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/pdata.h> + +/* Buck step size */ +#define DA9052_BUCK_PERI_3uV_STEP 100000 +#define DA9052_BUCK_PERI_REG_MAP_UPTO_3uV 24 +#define DA9052_CONST_3uV 3000000 + +#define DA9052_MIN_UA 0 +#define DA9052_MAX_UA 3 +#define DA9052_CURRENT_RANGE 4 + +/* Bit masks */ +#define DA9052_BUCK_ILIM_MASK_EVEN 0x0c +#define DA9052_BUCK_ILIM_MASK_ODD 0xc0 + +/* DA9052 REGULATOR IDs */ +#define DA9052_ID_BUCK1 0 +#define DA9052_ID_BUCK2 1 +#define DA9052_ID_BUCK3 2 +#define DA9052_ID_BUCK4 3 +#define DA9052_ID_LDO1 4 +#define DA9052_ID_LDO2 5 +#define DA9052_ID_LDO3 6 +#define DA9052_ID_LDO4 7 +#define DA9052_ID_LDO5 8 +#define DA9052_ID_LDO6 9 +#define DA9052_ID_LDO7 10 +#define DA9052_ID_LDO8 11 +#define DA9052_ID_LDO9 12 +#define DA9052_ID_LDO10 13 + +static const u32 da9052_current_limits[3][4] = { + {700000, 800000, 1000000, 1200000}, /* DA9052-BC BUCKs */ + {1600000, 2000000, 2400000, 3000000}, /* DA9053-AA/Bx BUCK-CORE */ + {800000, 1000000, 1200000, 1500000}, /* DA9053-AA/Bx BUCK-PRO, + * BUCK-MEM and BUCK-PERI + */ +}; + +struct da9052_regulator_info { + struct regulator_desc reg_desc; + int step_uV; + int min_uV; + int max_uV; + unsigned char activate_bit; +}; + +struct da9052_regulator { + struct da9052 *da9052; + struct da9052_regulator_info *info; + struct regulator_dev *rdev; +}; + +static int verify_range(struct da9052_regulator_info *info, + int min_uV, int max_uV) +{ + if (min_uV > info->max_uV || max_uV < info->min_uV) + return -EINVAL; + + return 0; +} + +static int da9052_dcdc_get_current_limit(struct regulator_dev *rdev) +{ + struct da9052_regulator *regulator = rdev_get_drvdata(rdev); + int offset = rdev_get_id(rdev); + int ret, row = 2; + + ret = da9052_reg_read(regulator->da9052, DA9052_BUCKA_REG + offset/2); + if (ret < 0) + return ret; + + /* Determine the even or odd position of the buck current limit + * register field + */ + if (offset % 2 == 0) + ret = (ret & DA9052_BUCK_ILIM_MASK_EVEN) >> 2; + else + ret = (ret & DA9052_BUCK_ILIM_MASK_ODD) >> 6; + + /* Select the appropriate current limit range */ + if (regulator->da9052->chip_id == DA9052) + row = 0; + else if (offset == 0) + row = 1; + + return da9052_current_limits[row][ret]; +} + +static int da9052_dcdc_set_current_limit(struct regulator_dev *rdev, int min_uA, + int max_uA) +{ + struct da9052_regulator *regulator = rdev_get_drvdata(rdev); + int offset = rdev_get_id(rdev); + int reg_val = 0; + int i, row = 2; + + /* Select the appropriate current limit range */ + if (regulator->da9052->chip_id == DA9052) + row = 0; + else if (offset == 0) + row = 1; + + for (i = DA9052_CURRENT_RANGE - 1; i >= 0; i--) { + if ((min_uA <= da9052_current_limits[row][i]) && + (da9052_current_limits[row][i] <= max_uA)) { + reg_val = i; + break; + } + } + + if (i < 0) + return -EINVAL; + + /* Determine the even or odd position of the buck current limit + * register field + */ + if (offset % 2 == 0) + return da9052_reg_update(regulator->da9052, + DA9052_BUCKA_REG + offset/2, + DA9052_BUCK_ILIM_MASK_EVEN, + reg_val << 2); + else + return da9052_reg_update(regulator->da9052, + DA9052_BUCKA_REG + offset/2, + DA9052_BUCK_ILIM_MASK_ODD, + reg_val << 6); +} + +static int da9052_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + struct da9052_regulator *regulator = rdev_get_drvdata(rdev); + struct da9052_regulator_info *info = regulator->info; + int id = rdev_get_id(rdev); + int volt_uV; + + if ((id == DA9052_ID_BUCK4) && (regulator->da9052->chip_id == DA9052) + && (selector >= DA9052_BUCK_PERI_REG_MAP_UPTO_3uV)) { + volt_uV = ((DA9052_BUCK_PERI_REG_MAP_UPTO_3uV * info->step_uV) + + info->min_uV); + volt_uV += (selector - DA9052_BUCK_PERI_REG_MAP_UPTO_3uV) + * (DA9052_BUCK_PERI_3uV_STEP); + } else { + volt_uV = (selector * info->step_uV) + info->min_uV; + } + + if (volt_uV > info->max_uV) + return -EINVAL; + + return volt_uV; +} + +static int da9052_map_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct da9052_regulator *regulator = rdev_get_drvdata(rdev); + struct da9052_regulator_info *info = regulator->info; + int id = rdev_get_id(rdev); + int ret, sel; + + ret = verify_range(info, min_uV, max_uV); + if (ret < 0) + return ret; + + if (min_uV < info->min_uV) + min_uV = info->min_uV; + + if ((id == DA9052_ID_BUCK4) && (regulator->da9052->chip_id == DA9052) + && (min_uV >= DA9052_CONST_3uV)) { + sel = DA9052_BUCK_PERI_REG_MAP_UPTO_3uV + + DIV_ROUND_UP(min_uV - DA9052_CONST_3uV, + DA9052_BUCK_PERI_3uV_STEP); + } else { + sel = DIV_ROUND_UP(min_uV - info->min_uV, info->step_uV); + } + + ret = da9052_list_voltage(rdev, sel); + if (ret < 0) + return ret; + + return sel; +} + +static int da9052_regulator_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct da9052_regulator *regulator = rdev_get_drvdata(rdev); + struct da9052_regulator_info *info = regulator->info; + int id = rdev_get_id(rdev); + int ret; + + ret = da9052_reg_update(regulator->da9052, rdev->desc->vsel_reg, + rdev->desc->vsel_mask, selector); + if (ret < 0) + return ret; + + /* Some LDOs and DCDCs are DVC controlled which requires enabling of + * the activate bit to implment the changes on the output. + */ + switch (id) { + case DA9052_ID_BUCK1: + case DA9052_ID_BUCK2: + case DA9052_ID_BUCK3: + case DA9052_ID_LDO2: + case DA9052_ID_LDO3: + ret = da9052_reg_update(regulator->da9052, DA9052_SUPPLY_REG, + info->activate_bit, info->activate_bit); + break; + } + + return ret; +} + +static int da9052_regulator_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_sel, + unsigned int new_sel) +{ + struct da9052_regulator *regulator = rdev_get_drvdata(rdev); + struct da9052_regulator_info *info = regulator->info; + int id = rdev_get_id(rdev); + int ret = 0; + + /* The DVC controlled LDOs and DCDCs ramp with 6.25mV/µs after enabling + * the activate bit. + */ + switch (id) { + case DA9052_ID_BUCK1: + case DA9052_ID_BUCK2: + case DA9052_ID_BUCK3: + case DA9052_ID_LDO2: + case DA9052_ID_LDO3: + ret = DIV_ROUND_UP(abs(new_sel - old_sel) * info->step_uV, + 6250); + break; + } + + return ret; +} + +static const struct regulator_ops da9052_dcdc_ops = { + .get_current_limit = da9052_dcdc_get_current_limit, + .set_current_limit = da9052_dcdc_set_current_limit, + + .list_voltage = da9052_list_voltage, + .map_voltage = da9052_map_voltage, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = da9052_regulator_set_voltage_sel, + .set_voltage_time_sel = da9052_regulator_set_voltage_time_sel, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static const struct regulator_ops da9052_ldo_ops = { + .list_voltage = da9052_list_voltage, + .map_voltage = da9052_map_voltage, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = da9052_regulator_set_voltage_sel, + .set_voltage_time_sel = da9052_regulator_set_voltage_time_sel, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +#define DA9052_LDO(_id, step, min, max, sbits, ebits, abits) \ +{\ + .reg_desc = {\ + .name = #_id,\ + .ops = &da9052_ldo_ops,\ + .type = REGULATOR_VOLTAGE,\ + .id = DA9052_ID_##_id,\ + .n_voltages = (max - min) / step + 1, \ + .owner = THIS_MODULE,\ + .vsel_reg = DA9052_BUCKCORE_REG + DA9052_ID_##_id, \ + .vsel_mask = (1 << (sbits)) - 1,\ + .enable_reg = DA9052_BUCKCORE_REG + DA9052_ID_##_id, \ + .enable_mask = 1 << (ebits),\ + },\ + .min_uV = (min) * 1000,\ + .max_uV = (max) * 1000,\ + .step_uV = (step) * 1000,\ + .activate_bit = (abits),\ +} + +#define DA9052_DCDC(_id, step, min, max, sbits, ebits, abits) \ +{\ + .reg_desc = {\ + .name = #_id,\ + .ops = &da9052_dcdc_ops,\ + .type = REGULATOR_VOLTAGE,\ + .id = DA9052_ID_##_id,\ + .n_voltages = (max - min) / step + 1, \ + .owner = THIS_MODULE,\ + .vsel_reg = DA9052_BUCKCORE_REG + DA9052_ID_##_id, \ + .vsel_mask = (1 << (sbits)) - 1,\ + .enable_reg = DA9052_BUCKCORE_REG + DA9052_ID_##_id, \ + .enable_mask = 1 << (ebits),\ + },\ + .min_uV = (min) * 1000,\ + .max_uV = (max) * 1000,\ + .step_uV = (step) * 1000,\ + .activate_bit = (abits),\ +} + +static struct da9052_regulator_info da9052_regulator_info[] = { + DA9052_DCDC(BUCK1, 25, 500, 2075, 6, 6, DA9052_SUPPLY_VBCOREGO), + DA9052_DCDC(BUCK2, 25, 500, 2075, 6, 6, DA9052_SUPPLY_VBPROGO), + DA9052_DCDC(BUCK3, 25, 950, 2525, 6, 6, DA9052_SUPPLY_VBMEMGO), + DA9052_DCDC(BUCK4, 50, 1800, 3600, 5, 6, 0), + DA9052_LDO(LDO1, 50, 600, 1800, 5, 6, 0), + DA9052_LDO(LDO2, 25, 600, 1800, 6, 6, DA9052_SUPPLY_VLDO2GO), + DA9052_LDO(LDO3, 25, 1725, 3300, 6, 6, DA9052_SUPPLY_VLDO3GO), + DA9052_LDO(LDO4, 25, 1725, 3300, 6, 6, 0), + DA9052_LDO(LDO5, 50, 1200, 3600, 6, 6, 0), + DA9052_LDO(LDO6, 50, 1200, 3600, 6, 6, 0), + DA9052_LDO(LDO7, 50, 1200, 3600, 6, 6, 0), + DA9052_LDO(LDO8, 50, 1200, 3600, 6, 6, 0), + DA9052_LDO(LDO9, 50, 1250, 3650, 6, 6, 0), + DA9052_LDO(LDO10, 50, 1200, 3600, 6, 6, 0), +}; + +static struct da9052_regulator_info da9053_regulator_info[] = { + DA9052_DCDC(BUCK1, 25, 500, 2075, 6, 6, DA9052_SUPPLY_VBCOREGO), + DA9052_DCDC(BUCK2, 25, 500, 2075, 6, 6, DA9052_SUPPLY_VBPROGO), + DA9052_DCDC(BUCK3, 25, 950, 2525, 6, 6, DA9052_SUPPLY_VBMEMGO), + DA9052_DCDC(BUCK4, 25, 950, 2525, 6, 6, 0), + DA9052_LDO(LDO1, 50, 600, 1800, 5, 6, 0), + DA9052_LDO(LDO2, 25, 600, 1800, 6, 6, DA9052_SUPPLY_VLDO2GO), + DA9052_LDO(LDO3, 25, 1725, 3300, 6, 6, DA9052_SUPPLY_VLDO3GO), + DA9052_LDO(LDO4, 25, 1725, 3300, 6, 6, 0), + DA9052_LDO(LDO5, 50, 1200, 3600, 6, 6, 0), + DA9052_LDO(LDO6, 50, 1200, 3600, 6, 6, 0), + DA9052_LDO(LDO7, 50, 1200, 3600, 6, 6, 0), + DA9052_LDO(LDO8, 50, 1200, 3600, 6, 6, 0), + DA9052_LDO(LDO9, 50, 1250, 3650, 6, 6, 0), + DA9052_LDO(LDO10, 50, 1200, 3600, 6, 6, 0), +}; + +static inline struct da9052_regulator_info *find_regulator_info(u8 chip_id, + int id) +{ + struct da9052_regulator_info *info; + int i; + + switch (chip_id) { + case DA9052: + for (i = 0; i < ARRAY_SIZE(da9052_regulator_info); i++) { + info = &da9052_regulator_info[i]; + if (info->reg_desc.id == id) + return info; + } + break; + case DA9053_AA: + case DA9053_BA: + case DA9053_BB: + case DA9053_BC: + for (i = 0; i < ARRAY_SIZE(da9053_regulator_info); i++) { + info = &da9053_regulator_info[i]; + if (info->reg_desc.id == id) + return info; + } + break; + } + + return NULL; +} + +static int da9052_regulator_probe(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct regulator_config config = { }; + struct da9052_regulator *regulator; + struct da9052 *da9052; + struct da9052_pdata *pdata; + + regulator = devm_kzalloc(&pdev->dev, sizeof(struct da9052_regulator), + GFP_KERNEL); + if (!regulator) + return -ENOMEM; + + da9052 = dev_get_drvdata(pdev->dev.parent); + pdata = dev_get_platdata(da9052->dev); + regulator->da9052 = da9052; + + regulator->info = find_regulator_info(regulator->da9052->chip_id, + cell->id); + if (regulator->info == NULL) { + dev_err(&pdev->dev, "invalid regulator ID specified\n"); + return -EINVAL; + } + + config.dev = &pdev->dev; + config.driver_data = regulator; + config.regmap = da9052->regmap; + if (pdata && pdata->regulators) { + config.init_data = pdata->regulators[cell->id]; + } else { +#ifdef CONFIG_OF + struct device_node *nproot = da9052->dev->of_node; + struct device_node *np; + + if (!nproot) + return -ENODEV; + + nproot = of_get_child_by_name(nproot, "regulators"); + if (!nproot) + return -ENODEV; + + for_each_child_of_node(nproot, np) { + if (!of_node_cmp(np->name, + regulator->info->reg_desc.name)) { + config.init_data = of_get_regulator_init_data( + &pdev->dev, np, + ®ulator->info->reg_desc); + config.of_node = np; + break; + } + } + of_node_put(nproot); +#endif + } + + regulator->rdev = devm_regulator_register(&pdev->dev, + ®ulator->info->reg_desc, + &config); + if (IS_ERR(regulator->rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + regulator->info->reg_desc.name); + return PTR_ERR(regulator->rdev); + } + + platform_set_drvdata(pdev, regulator); + + return 0; +} + +static struct platform_driver da9052_regulator_driver = { + .probe = da9052_regulator_probe, + .driver = { + .name = "da9052-regulator", + }, +}; + +static int __init da9052_regulator_init(void) +{ + return platform_driver_register(&da9052_regulator_driver); +} +subsys_initcall(da9052_regulator_init); + +static void __exit da9052_regulator_exit(void) +{ + platform_driver_unregister(&da9052_regulator_driver); +} +module_exit(da9052_regulator_exit); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("Power Regulator driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9052-regulator"); diff --git a/drivers/regulator/da9055-regulator.c b/drivers/regulator/da9055-regulator.c new file mode 100644 index 000000000..f40c3b864 --- /dev/null +++ b/drivers/regulator/da9055-regulator.c @@ -0,0 +1,686 @@ +/* +* Regulator driver for DA9055 PMIC +* +* Copyright(c) 2012 Dialog Semiconductor Ltd. +* +* Author: David Dajun Chen <dchen@diasemi.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; either version 2 of the License, or +* (at your option) any later version. +* +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/of.h> +#include <linux/regulator/of_regulator.h> + +#include <linux/mfd/da9055/core.h> +#include <linux/mfd/da9055/reg.h> +#include <linux/mfd/da9055/pdata.h> + +#define DA9055_MIN_UA 0 +#define DA9055_MAX_UA 3 + +#define DA9055_LDO_MODE_SYNC 0 +#define DA9055_LDO_MODE_SLEEP 1 + +#define DA9055_BUCK_MODE_SLEEP 1 +#define DA9055_BUCK_MODE_SYNC 2 +#define DA9055_BUCK_MODE_AUTO 3 + +/* DA9055 REGULATOR IDs */ +#define DA9055_ID_BUCK1 0 +#define DA9055_ID_BUCK2 1 +#define DA9055_ID_LDO1 2 +#define DA9055_ID_LDO2 3 +#define DA9055_ID_LDO3 4 +#define DA9055_ID_LDO4 5 +#define DA9055_ID_LDO5 6 +#define DA9055_ID_LDO6 7 + +/* DA9055 BUCK current limit */ +static const int da9055_current_limits[] = { 500000, 600000, 700000, 800000 }; + +struct da9055_conf_reg { + int reg; + int sel_mask; + int en_mask; +}; + +struct da9055_volt_reg { + int reg_a; + int reg_b; + int sl_shift; + int v_mask; +}; + +struct da9055_mode_reg { + int reg; + int mask; + int shift; +}; + +struct da9055_regulator_info { + struct regulator_desc reg_desc; + struct da9055_conf_reg conf; + struct da9055_volt_reg volt; + struct da9055_mode_reg mode; +}; + +struct da9055_regulator { + struct da9055 *da9055; + struct da9055_regulator_info *info; + struct regulator_dev *rdev; + enum gpio_select reg_rselect; +}; + +static unsigned int da9055_buck_get_mode(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int ret, mode = 0; + + ret = da9055_reg_read(regulator->da9055, info->mode.reg); + if (ret < 0) + return ret; + + switch ((ret & info->mode.mask) >> info->mode.shift) { + case DA9055_BUCK_MODE_SYNC: + mode = REGULATOR_MODE_FAST; + break; + case DA9055_BUCK_MODE_AUTO: + mode = REGULATOR_MODE_NORMAL; + break; + case DA9055_BUCK_MODE_SLEEP: + mode = REGULATOR_MODE_STANDBY; + break; + } + + return mode; +} + +static int da9055_buck_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int val = 0; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = DA9055_BUCK_MODE_SYNC << info->mode.shift; + break; + case REGULATOR_MODE_NORMAL: + val = DA9055_BUCK_MODE_AUTO << info->mode.shift; + break; + case REGULATOR_MODE_STANDBY: + val = DA9055_BUCK_MODE_SLEEP << info->mode.shift; + break; + } + + return da9055_reg_update(regulator->da9055, info->mode.reg, + info->mode.mask, val); +} + +static unsigned int da9055_ldo_get_mode(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int ret; + + ret = da9055_reg_read(regulator->da9055, info->volt.reg_b); + if (ret < 0) + return ret; + + if (ret >> info->volt.sl_shift) + return REGULATOR_MODE_STANDBY; + else + return REGULATOR_MODE_NORMAL; +} + +static int da9055_ldo_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + struct da9055_volt_reg volt = info->volt; + int val = 0; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + case REGULATOR_MODE_FAST: + val = DA9055_LDO_MODE_SYNC; + break; + case REGULATOR_MODE_STANDBY: + val = DA9055_LDO_MODE_SLEEP; + break; + } + + return da9055_reg_update(regulator->da9055, volt.reg_b, + 1 << volt.sl_shift, + val << volt.sl_shift); +} + +static int da9055_buck_get_current_limit(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int ret; + + ret = da9055_reg_read(regulator->da9055, DA9055_REG_BUCK_LIM); + if (ret < 0) + return ret; + + ret &= info->mode.mask; + return da9055_current_limits[ret >> info->mode.shift]; +} + +static int da9055_buck_set_current_limit(struct regulator_dev *rdev, int min_uA, + int max_uA) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int i; + + for (i = ARRAY_SIZE(da9055_current_limits) - 1; i >= 0; i--) { + if ((min_uA <= da9055_current_limits[i]) && + (da9055_current_limits[i] <= max_uA)) + return da9055_reg_update(regulator->da9055, + DA9055_REG_BUCK_LIM, + info->mode.mask, + i << info->mode.shift); + } + + return -EINVAL; +} + +static int da9055_regulator_get_voltage_sel(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + struct da9055_volt_reg volt = info->volt; + int ret, sel; + + /* + * There are two voltage register set A & B for voltage ramping but + * either one of then can be active therefore we first determine + * the active register set. + */ + ret = da9055_reg_read(regulator->da9055, info->conf.reg); + if (ret < 0) + return ret; + + ret &= info->conf.sel_mask; + + /* Get the voltage for the active register set A/B */ + if (ret == DA9055_REGUALTOR_SET_A) + ret = da9055_reg_read(regulator->da9055, volt.reg_a); + else + ret = da9055_reg_read(regulator->da9055, volt.reg_b); + + if (ret < 0) + return ret; + + sel = (ret & volt.v_mask); + return sel; +} + +static int da9055_regulator_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int ret; + + /* + * Regulator register set A/B is not selected through GPIO therefore + * we use default register set A for voltage ramping. + */ + if (regulator->reg_rselect == NO_GPIO) { + /* Select register set A */ + ret = da9055_reg_update(regulator->da9055, info->conf.reg, + info->conf.sel_mask, DA9055_SEL_REG_A); + if (ret < 0) + return ret; + + /* Set the voltage */ + return da9055_reg_update(regulator->da9055, info->volt.reg_a, + info->volt.v_mask, selector); + } + + /* + * Here regulator register set A/B is selected through GPIO. + * Therefore we first determine the selected register set A/B and + * then set the desired voltage for that register set A/B. + */ + ret = da9055_reg_read(regulator->da9055, info->conf.reg); + if (ret < 0) + return ret; + + ret &= info->conf.sel_mask; + + /* Set the voltage */ + if (ret == DA9055_REGUALTOR_SET_A) + return da9055_reg_update(regulator->da9055, info->volt.reg_a, + info->volt.v_mask, selector); + else + return da9055_reg_update(regulator->da9055, info->volt.reg_b, + info->volt.v_mask, selector); +} + +static int da9055_regulator_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + int ret; + + /* Select register set B for suspend voltage ramping. */ + if (regulator->reg_rselect == NO_GPIO) { + ret = da9055_reg_update(regulator->da9055, info->conf.reg, + info->conf.sel_mask, DA9055_SEL_REG_B); + if (ret < 0) + return ret; + } + + ret = regulator_map_voltage_linear(rdev, uV, uV); + if (ret < 0) + return ret; + + return da9055_reg_update(regulator->da9055, info->volt.reg_b, + info->volt.v_mask, ret); +} + +static int da9055_suspend_enable(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + + /* Select register set B for voltage ramping. */ + if (regulator->reg_rselect == NO_GPIO) + return da9055_reg_update(regulator->da9055, info->conf.reg, + info->conf.sel_mask, DA9055_SEL_REG_B); + else + return 0; +} + +static int da9055_suspend_disable(struct regulator_dev *rdev) +{ + struct da9055_regulator *regulator = rdev_get_drvdata(rdev); + struct da9055_regulator_info *info = regulator->info; + + /* Diselect register set B. */ + if (regulator->reg_rselect == NO_GPIO) + return da9055_reg_update(regulator->da9055, info->conf.reg, + info->conf.sel_mask, DA9055_SEL_REG_A); + else + return 0; +} + +static const struct regulator_ops da9055_buck_ops = { + .get_mode = da9055_buck_get_mode, + .set_mode = da9055_buck_set_mode, + + .get_current_limit = da9055_buck_get_current_limit, + .set_current_limit = da9055_buck_set_current_limit, + + .get_voltage_sel = da9055_regulator_get_voltage_sel, + .set_voltage_sel = da9055_regulator_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + + .set_suspend_voltage = da9055_regulator_set_suspend_voltage, + .set_suspend_enable = da9055_suspend_enable, + .set_suspend_disable = da9055_suspend_disable, + .set_suspend_mode = da9055_buck_set_mode, +}; + +static const struct regulator_ops da9055_ldo_ops = { + .get_mode = da9055_ldo_get_mode, + .set_mode = da9055_ldo_set_mode, + + .get_voltage_sel = da9055_regulator_get_voltage_sel, + .set_voltage_sel = da9055_regulator_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + + .set_suspend_voltage = da9055_regulator_set_suspend_voltage, + .set_suspend_enable = da9055_suspend_enable, + .set_suspend_disable = da9055_suspend_disable, + .set_suspend_mode = da9055_ldo_set_mode, + +}; + +#define DA9055_LDO(_id, step, min, max, vbits, voffset) \ +{\ + .reg_desc = {\ + .name = #_id,\ + .ops = &da9055_ldo_ops,\ + .type = REGULATOR_VOLTAGE,\ + .id = DA9055_ID_##_id,\ + .n_voltages = (max - min) / step + 1 + (voffset), \ + .enable_reg = DA9055_REG_BCORE_CONT + DA9055_ID_##_id, \ + .enable_mask = 1, \ + .min_uV = (min) * 1000,\ + .uV_step = (step) * 1000,\ + .linear_min_sel = (voffset),\ + .owner = THIS_MODULE,\ + },\ + .conf = {\ + .reg = DA9055_REG_BCORE_CONT + DA9055_ID_##_id, \ + .sel_mask = (1 << 4),\ + .en_mask = 1,\ + },\ + .volt = {\ + .reg_a = DA9055_REG_VBCORE_A + DA9055_ID_##_id, \ + .reg_b = DA9055_REG_VBCORE_B + DA9055_ID_##_id, \ + .sl_shift = 7,\ + .v_mask = (1 << (vbits)) - 1,\ + },\ +} + +#define DA9055_BUCK(_id, step, min, max, vbits, voffset, mbits, sbits) \ +{\ + .reg_desc = {\ + .name = #_id,\ + .ops = &da9055_buck_ops,\ + .type = REGULATOR_VOLTAGE,\ + .id = DA9055_ID_##_id,\ + .n_voltages = (max - min) / step + 1 + (voffset), \ + .enable_reg = DA9055_REG_BCORE_CONT + DA9055_ID_##_id, \ + .enable_mask = 1,\ + .min_uV = (min) * 1000,\ + .uV_step = (step) * 1000,\ + .linear_min_sel = (voffset),\ + .owner = THIS_MODULE,\ + },\ + .conf = {\ + .reg = DA9055_REG_BCORE_CONT + DA9055_ID_##_id, \ + .sel_mask = (1 << 4),\ + .en_mask = 1,\ + },\ + .volt = {\ + .reg_a = DA9055_REG_VBCORE_A + DA9055_ID_##_id, \ + .reg_b = DA9055_REG_VBCORE_B + DA9055_ID_##_id, \ + .sl_shift = 7,\ + .v_mask = (1 << (vbits)) - 1,\ + },\ + .mode = {\ + .reg = DA9055_REG_BCORE_MODE,\ + .mask = (mbits),\ + .shift = (sbits),\ + },\ +} + +static struct da9055_regulator_info da9055_regulator_info[] = { + DA9055_BUCK(BUCK1, 25, 725, 2075, 6, 9, 0xc, 2), + DA9055_BUCK(BUCK2, 25, 925, 2500, 6, 0, 3, 0), + DA9055_LDO(LDO1, 50, 900, 3300, 6, 2), + DA9055_LDO(LDO2, 50, 900, 3300, 6, 3), + DA9055_LDO(LDO3, 50, 900, 3300, 6, 2), + DA9055_LDO(LDO4, 50, 900, 3300, 6, 2), + DA9055_LDO(LDO5, 50, 900, 2750, 6, 2), + DA9055_LDO(LDO6, 20, 900, 3300, 7, 0), +}; + +/* + * Configures regulator to be controlled either through GPIO 1 or 2. + * GPIO can control regulator state and/or select the regulator register + * set A/B for voltage ramping. + */ +static int da9055_gpio_init(struct da9055_regulator *regulator, + struct regulator_config *config, + struct da9055_pdata *pdata, int id) +{ + struct da9055_regulator_info *info = regulator->info; + int ret = 0; + + if (!pdata) + return 0; + + if (pdata->gpio_ren && pdata->gpio_ren[id]) { + char name[18]; + int gpio_mux = pdata->gpio_ren[id]; + + config->ena_gpiod = pdata->ena_gpiods[id]; + config->ena_gpio_invert = 1; + + /* + * GPI pin is muxed with regulator to control the + * regulator state. + */ + sprintf(name, "DA9055 GPI %d", gpio_mux); + ret = devm_gpio_request_one(config->dev, gpio_mux, GPIOF_DIR_IN, + name); + if (ret < 0) + goto err; + + /* + * Let the regulator know that its state is controlled + * through GPI. + */ + ret = da9055_reg_update(regulator->da9055, info->conf.reg, + DA9055_E_GPI_MASK, + pdata->reg_ren[id] + << DA9055_E_GPI_SHIFT); + if (ret < 0) + goto err; + } + + if (pdata->gpio_rsel && pdata->gpio_rsel[id]) { + char name[18]; + int gpio_mux = pdata->gpio_rsel[id]; + + regulator->reg_rselect = pdata->reg_rsel[id]; + + /* + * GPI pin is muxed with regulator to select the + * regulator register set A/B for voltage ramping. + */ + sprintf(name, "DA9055 GPI %d", gpio_mux); + ret = devm_gpio_request_one(config->dev, gpio_mux, GPIOF_DIR_IN, + name); + if (ret < 0) + goto err; + + /* + * Let the regulator know that its register set A/B + * will be selected through GPI for voltage ramping. + */ + ret = da9055_reg_update(regulator->da9055, info->conf.reg, + DA9055_V_GPI_MASK, + pdata->reg_rsel[id] + << DA9055_V_GPI_SHIFT); + } + +err: + return ret; +} + +static irqreturn_t da9055_ldo5_6_oc_irq(int irq, void *data) +{ + struct da9055_regulator *regulator = data; + + regulator_notifier_call_chain(regulator->rdev, + REGULATOR_EVENT_OVER_CURRENT, NULL); + + return IRQ_HANDLED; +} + +static inline struct da9055_regulator_info *find_regulator_info(int id) +{ + struct da9055_regulator_info *info; + int i; + + for (i = 0; i < ARRAY_SIZE(da9055_regulator_info); i++) { + info = &da9055_regulator_info[i]; + if (info->reg_desc.id == id) + return info; + } + + return NULL; +} + +#ifdef CONFIG_OF +static struct of_regulator_match da9055_reg_matches[] = { + { .name = "BUCK1", }, + { .name = "BUCK2", }, + { .name = "LDO1", }, + { .name = "LDO2", }, + { .name = "LDO3", }, + { .name = "LDO4", }, + { .name = "LDO5", }, + { .name = "LDO6", }, +}; + +static int da9055_regulator_dt_init(struct platform_device *pdev, + struct da9055_regulator *regulator, + struct regulator_config *config, + int regid) +{ + struct device_node *nproot, *np; + int ret; + + nproot = of_node_get(pdev->dev.parent->of_node); + if (!nproot) + return -ENODEV; + + np = of_get_child_by_name(nproot, "regulators"); + if (!np) + return -ENODEV; + + ret = of_regulator_match(&pdev->dev, np, &da9055_reg_matches[regid], 1); + of_node_put(nproot); + if (ret < 0) { + dev_err(&pdev->dev, "Error matching regulator: %d\n", ret); + return ret; + } + + config->init_data = da9055_reg_matches[regid].init_data; + config->of_node = da9055_reg_matches[regid].of_node; + + if (!config->of_node) + return -ENODEV; + + return 0; +} +#else +static inline int da9055_regulator_dt_init(struct platform_device *pdev, + struct da9055_regulator *regulator, + struct regulator_config *config, + int regid) +{ + return -ENODEV; +} +#endif /* CONFIG_OF */ + +static int da9055_regulator_probe(struct platform_device *pdev) +{ + struct regulator_config config = { }; + struct da9055_regulator *regulator; + struct da9055 *da9055 = dev_get_drvdata(pdev->dev.parent); + struct da9055_pdata *pdata = dev_get_platdata(da9055->dev); + int ret, irq; + + regulator = devm_kzalloc(&pdev->dev, sizeof(struct da9055_regulator), + GFP_KERNEL); + if (!regulator) + return -ENOMEM; + + regulator->info = find_regulator_info(pdev->id); + if (regulator->info == NULL) { + dev_err(&pdev->dev, "invalid regulator ID specified\n"); + return -EINVAL; + } + + regulator->da9055 = da9055; + config.dev = &pdev->dev; + config.driver_data = regulator; + config.regmap = da9055->regmap; + + if (pdata && pdata->regulators) { + config.init_data = pdata->regulators[pdev->id]; + } else { + ret = da9055_regulator_dt_init(pdev, regulator, &config, + pdev->id); + if (ret < 0) + return ret; + } + + ret = da9055_gpio_init(regulator, &config, pdata, pdev->id); + if (ret < 0) + return ret; + + regulator->rdev = devm_regulator_register(&pdev->dev, + ®ulator->info->reg_desc, + &config); + if (IS_ERR(regulator->rdev)) { + dev_err(&pdev->dev, "Failed to register regulator %s\n", + regulator->info->reg_desc.name); + return PTR_ERR(regulator->rdev); + } + + /* Only LDO 5 and 6 has got the over current interrupt */ + if (pdev->id == DA9055_ID_LDO5 || pdev->id == DA9055_ID_LDO6) { + irq = platform_get_irq_byname(pdev, "REGULATOR"); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + da9055_ldo5_6_oc_irq, + IRQF_TRIGGER_HIGH | + IRQF_ONESHOT | + IRQF_PROBE_SHARED, + pdev->name, regulator); + if (ret != 0) { + if (ret != -EBUSY) { + dev_err(&pdev->dev, + "Failed to request Regulator IRQ %d: %d\n", + irq, ret); + return ret; + } + } + } + + platform_set_drvdata(pdev, regulator); + + return 0; +} + +static struct platform_driver da9055_regulator_driver = { + .probe = da9055_regulator_probe, + .driver = { + .name = "da9055-regulator", + }, +}; + +static int __init da9055_regulator_init(void) +{ + return platform_driver_register(&da9055_regulator_driver); +} +subsys_initcall(da9055_regulator_init); + +static void __exit da9055_regulator_exit(void) +{ + platform_driver_unregister(&da9055_regulator_driver); +} +module_exit(da9055_regulator_exit); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("Power Regulator driver for Dialog DA9055 PMIC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9055-regulator"); diff --git a/drivers/regulator/da9062-regulator.c b/drivers/regulator/da9062-regulator.c new file mode 100644 index 000000000..34a70d9dc --- /dev/null +++ b/drivers/regulator/da9062-regulator.c @@ -0,0 +1,1120 @@ +/* + * Regulator device driver for DA9061 and DA9062. + * Copyright (C) 2015-2017 Dialog Semiconductor + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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/slab.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/da9062/core.h> +#include <linux/mfd/da9062/registers.h> + +/* Regulator IDs */ +enum { + DA9061_ID_BUCK1, + DA9061_ID_BUCK2, + DA9061_ID_BUCK3, + DA9061_ID_LDO1, + DA9061_ID_LDO2, + DA9061_ID_LDO3, + DA9061_ID_LDO4, + DA9061_MAX_REGULATORS, +}; + +enum { + DA9062_ID_BUCK1, + DA9062_ID_BUCK2, + DA9062_ID_BUCK3, + DA9062_ID_BUCK4, + DA9062_ID_LDO1, + DA9062_ID_LDO2, + DA9062_ID_LDO3, + DA9062_ID_LDO4, + DA9062_MAX_REGULATORS, +}; + +/* Regulator capabilities and registers description */ +struct da9062_regulator_info { + struct regulator_desc desc; + /* Current limiting */ + unsigned int n_current_limits; + const int *current_limits; + /* Main register fields */ + struct reg_field mode; + struct reg_field suspend; + struct reg_field sleep; + struct reg_field suspend_sleep; + unsigned int suspend_vsel_reg; + struct reg_field ilimit; + /* Event detection bit */ + struct reg_field oc_event; +}; + +/* Single regulator settings */ +struct da9062_regulator { + struct regulator_desc desc; + struct regulator_dev *rdev; + struct da9062 *hw; + const struct da9062_regulator_info *info; + + struct regmap_field *mode; + struct regmap_field *suspend; + struct regmap_field *sleep; + struct regmap_field *suspend_sleep; + struct regmap_field *ilimit; +}; + +/* Encapsulates all information for the regulators driver */ +struct da9062_regulators { + int irq_ldo_lim; + unsigned n_regulators; + /* Array size to be defined during init. Keep at end. */ + struct da9062_regulator regulator[0]; +}; + +/* BUCK modes */ +enum { + BUCK_MODE_MANUAL, /* 0 */ + BUCK_MODE_SLEEP, /* 1 */ + BUCK_MODE_SYNC, /* 2 */ + BUCK_MODE_AUTO /* 3 */ +}; + +/* Regulator operations */ + +/* Current limits array (in uA) + * - DA9061_ID_[BUCK1|BUCK3] + * - DA9062_ID_[BUCK1|BUCK2|BUCK4] + * Entry indexes corresponds to register values. + */ +static const int da9062_buck_a_limits[] = { + 500000, 600000, 700000, 800000, 900000, 1000000, 1100000, 1200000, + 1300000, 1400000, 1500000, 1600000, 1700000, 1800000, 1900000, 2000000 +}; + +/* Current limits array (in uA) + * - DA9061_ID_BUCK2 + * - DA9062_ID_BUCK3 + * Entry indexes corresponds to register values. + */ +static const int da9062_buck_b_limits[] = { + 1500000, 1600000, 1700000, 1800000, 1900000, 2000000, 2100000, 2200000, + 2300000, 2400000, 2500000, 2600000, 2700000, 2800000, 2900000, 3000000 +}; + +static int da9062_set_current_limit(struct regulator_dev *rdev, + int min_ua, int max_ua) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + const struct da9062_regulator_info *rinfo = regl->info; + int n, tval; + + for (n = 0; n < rinfo->n_current_limits; n++) { + tval = rinfo->current_limits[n]; + if (tval >= min_ua && tval <= max_ua) + return regmap_field_write(regl->ilimit, n); + } + + return -EINVAL; +} + +static int da9062_get_current_limit(struct regulator_dev *rdev) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + const struct da9062_regulator_info *rinfo = regl->info; + unsigned int sel; + int ret; + + ret = regmap_field_read(regl->ilimit, &sel); + if (ret < 0) + return ret; + + if (sel >= rinfo->n_current_limits) + sel = rinfo->n_current_limits - 1; + + return rinfo->current_limits[sel]; +} + +static int da9062_buck_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + unsigned val; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = BUCK_MODE_SYNC; + break; + case REGULATOR_MODE_NORMAL: + val = BUCK_MODE_AUTO; + break; + case REGULATOR_MODE_STANDBY: + val = BUCK_MODE_SLEEP; + break; + default: + return -EINVAL; + } + + return regmap_field_write(regl->mode, val); +} + +/* + * Bucks use single mode register field for normal operation + * and suspend state. + * There are 3 modes to map to: FAST, NORMAL, and STANDBY. + */ + +static unsigned da9062_buck_get_mode(struct regulator_dev *rdev) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + struct regmap_field *field; + unsigned int val, mode = 0; + int ret; + + ret = regmap_field_read(regl->mode, &val); + if (ret < 0) + return ret; + + switch (val) { + default: + case BUCK_MODE_MANUAL: + mode = REGULATOR_MODE_FAST | REGULATOR_MODE_STANDBY; + /* Sleep flag bit decides the mode */ + break; + case BUCK_MODE_SLEEP: + return REGULATOR_MODE_STANDBY; + case BUCK_MODE_SYNC: + return REGULATOR_MODE_FAST; + case BUCK_MODE_AUTO: + return REGULATOR_MODE_NORMAL; + } + + /* Detect current regulator state */ + ret = regmap_field_read(regl->suspend, &val); + if (ret < 0) + return 0; + + /* Read regulator mode from proper register, depending on state */ + if (val) + field = regl->suspend_sleep; + else + field = regl->sleep; + + ret = regmap_field_read(field, &val); + if (ret < 0) + return 0; + + if (val) + mode &= REGULATOR_MODE_STANDBY; + else + mode &= REGULATOR_MODE_NORMAL | REGULATOR_MODE_FAST; + + return mode; +} + +/* + * LDOs use sleep flags - one for normal and one for suspend state. + * There are 2 modes to map to: NORMAL and STANDBY (sleep) for each state. + */ + +static int da9062_ldo_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + unsigned val; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + val = 0; + break; + case REGULATOR_MODE_STANDBY: + val = 1; + break; + default: + return -EINVAL; + } + + return regmap_field_write(regl->sleep, val); +} + +static unsigned da9062_ldo_get_mode(struct regulator_dev *rdev) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + struct regmap_field *field; + int ret, val; + + /* Detect current regulator state */ + ret = regmap_field_read(regl->suspend, &val); + if (ret < 0) + return 0; + + /* Read regulator mode from proper register, depending on state */ + if (val) + field = regl->suspend_sleep; + else + field = regl->sleep; + + ret = regmap_field_read(field, &val); + if (ret < 0) + return 0; + + if (val) + return REGULATOR_MODE_STANDBY; + else + return REGULATOR_MODE_NORMAL; +} + +static int da9062_buck_get_status(struct regulator_dev *rdev) +{ + int ret = regulator_is_enabled_regmap(rdev); + + if (ret == 0) { + ret = REGULATOR_STATUS_OFF; + } else if (ret > 0) { + ret = da9062_buck_get_mode(rdev); + if (ret > 0) + ret = regulator_mode_to_status(ret); + else if (ret == 0) + ret = -EIO; + } + + return ret; +} + +static int da9062_ldo_get_status(struct regulator_dev *rdev) +{ + int ret = regulator_is_enabled_regmap(rdev); + + if (ret == 0) { + ret = REGULATOR_STATUS_OFF; + } else if (ret > 0) { + ret = da9062_ldo_get_mode(rdev); + if (ret > 0) + ret = regulator_mode_to_status(ret); + else if (ret == 0) + ret = -EIO; + } + + return ret; +} + +static int da9062_set_suspend_voltage(struct regulator_dev *rdev, int uv) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + const struct da9062_regulator_info *rinfo = regl->info; + int ret, sel; + + sel = regulator_map_voltage_linear(rdev, uv, uv); + if (sel < 0) + return sel; + + sel <<= ffs(rdev->desc->vsel_mask) - 1; + + ret = regmap_update_bits(regl->hw->regmap, rinfo->suspend_vsel_reg, + rdev->desc->vsel_mask, sel); + + return ret; +} + +static int da9062_suspend_enable(struct regulator_dev *rdev) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + + return regmap_field_write(regl->suspend, 1); +} + +static int da9062_suspend_disable(struct regulator_dev *rdev) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + + return regmap_field_write(regl->suspend, 0); +} + +static int da9062_buck_set_suspend_mode(struct regulator_dev *rdev, + unsigned mode) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + int val; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = BUCK_MODE_SYNC; + break; + case REGULATOR_MODE_NORMAL: + val = BUCK_MODE_AUTO; + break; + case REGULATOR_MODE_STANDBY: + val = BUCK_MODE_SLEEP; + break; + default: + return -EINVAL; + } + + return regmap_field_write(regl->mode, val); +} + +static int da9062_ldo_set_suspend_mode(struct regulator_dev *rdev, + unsigned mode) +{ + struct da9062_regulator *regl = rdev_get_drvdata(rdev); + unsigned val; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + val = 0; + break; + case REGULATOR_MODE_STANDBY: + val = 1; + break; + default: + return -EINVAL; + } + + return regmap_field_write(regl->suspend_sleep, val); +} + +static const struct regulator_ops da9062_buck_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_current_limit = da9062_set_current_limit, + .get_current_limit = da9062_get_current_limit, + .set_mode = da9062_buck_set_mode, + .get_mode = da9062_buck_get_mode, + .get_status = da9062_buck_get_status, + .set_suspend_voltage = da9062_set_suspend_voltage, + .set_suspend_enable = da9062_suspend_enable, + .set_suspend_disable = da9062_suspend_disable, + .set_suspend_mode = da9062_buck_set_suspend_mode, +}; + +static const struct regulator_ops da9062_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_mode = da9062_ldo_set_mode, + .get_mode = da9062_ldo_get_mode, + .get_status = da9062_ldo_get_status, + .set_suspend_voltage = da9062_set_suspend_voltage, + .set_suspend_enable = da9062_suspend_enable, + .set_suspend_disable = da9062_suspend_disable, + .set_suspend_mode = da9062_ldo_set_suspend_mode, +}; + +/* DA9061 Regulator information */ +static const struct da9062_regulator_info local_da9061_regulator_info[] = { + { + .desc.id = DA9061_ID_BUCK1, + .desc.name = "DA9061 BUCK1", + .desc.of_match = of_match_ptr("buck1"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_buck_ops, + .desc.min_uV = (300) * 1000, + .desc.uV_step = (10) * 1000, + .desc.n_voltages = ((1570) - (300))/(10) + 1, + .current_limits = da9062_buck_a_limits, + .n_current_limits = ARRAY_SIZE(da9062_buck_a_limits), + .desc.enable_reg = DA9062AA_BUCK1_CONT, + .desc.enable_mask = DA9062AA_BUCK1_EN_MASK, + .desc.vsel_reg = DA9062AA_VBUCK1_A, + .desc.vsel_mask = DA9062AA_VBUCK1_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VBUCK1_A, + __builtin_ffs((int)DA9062AA_BUCK1_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK1_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VBUCK1_B, + __builtin_ffs((int)DA9062AA_BUCK1_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK1_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VBUCK1_B, + .mode = REG_FIELD(DA9062AA_BUCK1_CFG, + __builtin_ffs((int)DA9062AA_BUCK1_MODE_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK1_MODE_MASK)) - 1), + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VBUCK1_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VBUCK1_SEL_MASK)) - 1), + .ilimit = REG_FIELD(DA9062AA_BUCK_ILIM_C, + __builtin_ffs((int)DA9062AA_BUCK1_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK1_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9061_ID_BUCK2, + .desc.name = "DA9061 BUCK2", + .desc.of_match = of_match_ptr("buck2"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_buck_ops, + .desc.min_uV = (800) * 1000, + .desc.uV_step = (20) * 1000, + .desc.n_voltages = ((3340) - (800))/(20) + 1, + .current_limits = da9062_buck_b_limits, + .n_current_limits = ARRAY_SIZE(da9062_buck_b_limits), + .desc.enable_reg = DA9062AA_BUCK3_CONT, + .desc.enable_mask = DA9062AA_BUCK3_EN_MASK, + .desc.vsel_reg = DA9062AA_VBUCK3_A, + .desc.vsel_mask = DA9062AA_VBUCK3_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VBUCK3_A, + __builtin_ffs((int)DA9062AA_BUCK3_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK3_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VBUCK3_B, + __builtin_ffs((int)DA9062AA_BUCK3_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK3_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VBUCK3_B, + .mode = REG_FIELD(DA9062AA_BUCK3_CFG, + __builtin_ffs((int)DA9062AA_BUCK3_MODE_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK3_MODE_MASK)) - 1), + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VBUCK3_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VBUCK3_SEL_MASK)) - 1), + .ilimit = REG_FIELD(DA9062AA_BUCK_ILIM_A, + __builtin_ffs((int)DA9062AA_BUCK3_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK3_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9061_ID_BUCK3, + .desc.name = "DA9061 BUCK3", + .desc.of_match = of_match_ptr("buck3"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_buck_ops, + .desc.min_uV = (530) * 1000, + .desc.uV_step = (10) * 1000, + .desc.n_voltages = ((1800) - (530))/(10) + 1, + .current_limits = da9062_buck_a_limits, + .n_current_limits = ARRAY_SIZE(da9062_buck_a_limits), + .desc.enable_reg = DA9062AA_BUCK4_CONT, + .desc.enable_mask = DA9062AA_BUCK4_EN_MASK, + .desc.vsel_reg = DA9062AA_VBUCK4_A, + .desc.vsel_mask = DA9062AA_VBUCK4_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VBUCK4_A, + __builtin_ffs((int)DA9062AA_BUCK4_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK4_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VBUCK4_B, + __builtin_ffs((int)DA9062AA_BUCK4_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK4_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VBUCK4_B, + .mode = REG_FIELD(DA9062AA_BUCK4_CFG, + __builtin_ffs((int)DA9062AA_BUCK4_MODE_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK4_MODE_MASK)) - 1), + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VBUCK4_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VBUCK4_SEL_MASK)) - 1), + .ilimit = REG_FIELD(DA9062AA_BUCK_ILIM_B, + __builtin_ffs((int)DA9062AA_BUCK4_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK4_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9061_ID_LDO1, + .desc.name = "DA9061 LDO1", + .desc.of_match = of_match_ptr("ldo1"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_ldo_ops, + .desc.min_uV = (900) * 1000, + .desc.uV_step = (50) * 1000, + .desc.n_voltages = ((3600) - (900))/(50) + 1, + .desc.enable_reg = DA9062AA_LDO1_CONT, + .desc.enable_mask = DA9062AA_LDO1_EN_MASK, + .desc.vsel_reg = DA9062AA_VLDO1_A, + .desc.vsel_mask = DA9062AA_VLDO1_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VLDO1_A, + __builtin_ffs((int)DA9062AA_LDO1_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO1_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VLDO1_B, + __builtin_ffs((int)DA9062AA_LDO1_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO1_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VLDO1_B, + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VLDO1_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VLDO1_SEL_MASK)) - 1), + .oc_event = REG_FIELD(DA9062AA_STATUS_D, + __builtin_ffs((int)DA9062AA_LDO1_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO1_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9061_ID_LDO2, + .desc.name = "DA9061 LDO2", + .desc.of_match = of_match_ptr("ldo2"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_ldo_ops, + .desc.min_uV = (900) * 1000, + .desc.uV_step = (50) * 1000, + .desc.n_voltages = ((3600) - (600))/(50) + 1, + .desc.enable_reg = DA9062AA_LDO2_CONT, + .desc.enable_mask = DA9062AA_LDO2_EN_MASK, + .desc.vsel_reg = DA9062AA_VLDO2_A, + .desc.vsel_mask = DA9062AA_VLDO2_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VLDO2_A, + __builtin_ffs((int)DA9062AA_LDO2_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO2_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VLDO2_B, + __builtin_ffs((int)DA9062AA_LDO2_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO2_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VLDO2_B, + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VLDO2_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VLDO2_SEL_MASK)) - 1), + .oc_event = REG_FIELD(DA9062AA_STATUS_D, + __builtin_ffs((int)DA9062AA_LDO2_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO2_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9061_ID_LDO3, + .desc.name = "DA9061 LDO3", + .desc.of_match = of_match_ptr("ldo3"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_ldo_ops, + .desc.min_uV = (900) * 1000, + .desc.uV_step = (50) * 1000, + .desc.n_voltages = ((3600) - (900))/(50) + 1, + .desc.enable_reg = DA9062AA_LDO3_CONT, + .desc.enable_mask = DA9062AA_LDO3_EN_MASK, + .desc.vsel_reg = DA9062AA_VLDO3_A, + .desc.vsel_mask = DA9062AA_VLDO3_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VLDO3_A, + __builtin_ffs((int)DA9062AA_LDO3_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO3_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VLDO3_B, + __builtin_ffs((int)DA9062AA_LDO3_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO3_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VLDO3_B, + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VLDO3_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VLDO3_SEL_MASK)) - 1), + .oc_event = REG_FIELD(DA9062AA_STATUS_D, + __builtin_ffs((int)DA9062AA_LDO3_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO3_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9061_ID_LDO4, + .desc.name = "DA9061 LDO4", + .desc.of_match = of_match_ptr("ldo4"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_ldo_ops, + .desc.min_uV = (900) * 1000, + .desc.uV_step = (50) * 1000, + .desc.n_voltages = ((3600) - (900))/(50) + 1, + .desc.enable_reg = DA9062AA_LDO4_CONT, + .desc.enable_mask = DA9062AA_LDO4_EN_MASK, + .desc.vsel_reg = DA9062AA_VLDO4_A, + .desc.vsel_mask = DA9062AA_VLDO4_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VLDO4_A, + __builtin_ffs((int)DA9062AA_LDO4_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO4_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VLDO4_B, + __builtin_ffs((int)DA9062AA_LDO4_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO4_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VLDO4_B, + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VLDO4_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VLDO4_SEL_MASK)) - 1), + .oc_event = REG_FIELD(DA9062AA_STATUS_D, + __builtin_ffs((int)DA9062AA_LDO4_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO4_ILIM_MASK)) - 1), + }, +}; + +/* DA9062 Regulator information */ +static const struct da9062_regulator_info local_da9062_regulator_info[] = { + { + .desc.id = DA9062_ID_BUCK1, + .desc.name = "DA9062 BUCK1", + .desc.of_match = of_match_ptr("buck1"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_buck_ops, + .desc.min_uV = (300) * 1000, + .desc.uV_step = (10) * 1000, + .desc.n_voltages = ((1570) - (300))/(10) + 1, + .current_limits = da9062_buck_a_limits, + .n_current_limits = ARRAY_SIZE(da9062_buck_a_limits), + .desc.enable_reg = DA9062AA_BUCK1_CONT, + .desc.enable_mask = DA9062AA_BUCK1_EN_MASK, + .desc.vsel_reg = DA9062AA_VBUCK1_A, + .desc.vsel_mask = DA9062AA_VBUCK1_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VBUCK1_A, + __builtin_ffs((int)DA9062AA_BUCK1_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK1_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VBUCK1_B, + __builtin_ffs((int)DA9062AA_BUCK1_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK1_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VBUCK1_B, + .mode = REG_FIELD(DA9062AA_BUCK1_CFG, + __builtin_ffs((int)DA9062AA_BUCK1_MODE_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK1_MODE_MASK)) - 1), + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VBUCK1_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VBUCK1_SEL_MASK)) - 1), + .ilimit = REG_FIELD(DA9062AA_BUCK_ILIM_C, + __builtin_ffs((int)DA9062AA_BUCK1_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK1_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9062_ID_BUCK2, + .desc.name = "DA9062 BUCK2", + .desc.of_match = of_match_ptr("buck2"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_buck_ops, + .desc.min_uV = (300) * 1000, + .desc.uV_step = (10) * 1000, + .desc.n_voltages = ((1570) - (300))/(10) + 1, + .current_limits = da9062_buck_a_limits, + .n_current_limits = ARRAY_SIZE(da9062_buck_a_limits), + .desc.enable_reg = DA9062AA_BUCK2_CONT, + .desc.enable_mask = DA9062AA_BUCK2_EN_MASK, + .desc.vsel_reg = DA9062AA_VBUCK2_A, + .desc.vsel_mask = DA9062AA_VBUCK2_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VBUCK2_A, + __builtin_ffs((int)DA9062AA_BUCK2_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK2_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VBUCK2_B, + __builtin_ffs((int)DA9062AA_BUCK2_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK2_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VBUCK2_B, + .mode = REG_FIELD(DA9062AA_BUCK2_CFG, + __builtin_ffs((int)DA9062AA_BUCK2_MODE_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK2_MODE_MASK)) - 1), + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VBUCK2_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VBUCK2_SEL_MASK)) - 1), + .ilimit = REG_FIELD(DA9062AA_BUCK_ILIM_C, + __builtin_ffs((int)DA9062AA_BUCK2_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK2_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9062_ID_BUCK3, + .desc.name = "DA9062 BUCK3", + .desc.of_match = of_match_ptr("buck3"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_buck_ops, + .desc.min_uV = (800) * 1000, + .desc.uV_step = (20) * 1000, + .desc.n_voltages = ((3340) - (800))/(20) + 1, + .current_limits = da9062_buck_b_limits, + .n_current_limits = ARRAY_SIZE(da9062_buck_b_limits), + .desc.enable_reg = DA9062AA_BUCK3_CONT, + .desc.enable_mask = DA9062AA_BUCK3_EN_MASK, + .desc.vsel_reg = DA9062AA_VBUCK3_A, + .desc.vsel_mask = DA9062AA_VBUCK3_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VBUCK3_A, + __builtin_ffs((int)DA9062AA_BUCK3_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK3_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VBUCK3_B, + __builtin_ffs((int)DA9062AA_BUCK3_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK3_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VBUCK3_B, + .mode = REG_FIELD(DA9062AA_BUCK3_CFG, + __builtin_ffs((int)DA9062AA_BUCK3_MODE_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK3_MODE_MASK)) - 1), + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VBUCK3_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VBUCK3_SEL_MASK)) - 1), + .ilimit = REG_FIELD(DA9062AA_BUCK_ILIM_A, + __builtin_ffs((int)DA9062AA_BUCK3_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK3_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9062_ID_BUCK4, + .desc.name = "DA9062 BUCK4", + .desc.of_match = of_match_ptr("buck4"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_buck_ops, + .desc.min_uV = (530) * 1000, + .desc.uV_step = (10) * 1000, + .desc.n_voltages = ((1800) - (530))/(10) + 1, + .current_limits = da9062_buck_a_limits, + .n_current_limits = ARRAY_SIZE(da9062_buck_a_limits), + .desc.enable_reg = DA9062AA_BUCK4_CONT, + .desc.enable_mask = DA9062AA_BUCK4_EN_MASK, + .desc.vsel_reg = DA9062AA_VBUCK4_A, + .desc.vsel_mask = DA9062AA_VBUCK4_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VBUCK4_A, + __builtin_ffs((int)DA9062AA_BUCK4_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK4_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VBUCK4_B, + __builtin_ffs((int)DA9062AA_BUCK4_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK4_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VBUCK4_B, + .mode = REG_FIELD(DA9062AA_BUCK4_CFG, + __builtin_ffs((int)DA9062AA_BUCK4_MODE_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK4_MODE_MASK)) - 1), + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VBUCK4_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VBUCK4_SEL_MASK)) - 1), + .ilimit = REG_FIELD(DA9062AA_BUCK_ILIM_B, + __builtin_ffs((int)DA9062AA_BUCK4_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_BUCK4_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9062_ID_LDO1, + .desc.name = "DA9062 LDO1", + .desc.of_match = of_match_ptr("ldo1"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_ldo_ops, + .desc.min_uV = (900) * 1000, + .desc.uV_step = (50) * 1000, + .desc.n_voltages = ((3600) - (900))/(50) + 1, + .desc.enable_reg = DA9062AA_LDO1_CONT, + .desc.enable_mask = DA9062AA_LDO1_EN_MASK, + .desc.vsel_reg = DA9062AA_VLDO1_A, + .desc.vsel_mask = DA9062AA_VLDO1_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VLDO1_A, + __builtin_ffs((int)DA9062AA_LDO1_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO1_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VLDO1_B, + __builtin_ffs((int)DA9062AA_LDO1_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO1_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VLDO1_B, + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VLDO1_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VLDO1_SEL_MASK)) - 1), + .oc_event = REG_FIELD(DA9062AA_STATUS_D, + __builtin_ffs((int)DA9062AA_LDO1_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO1_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9062_ID_LDO2, + .desc.name = "DA9062 LDO2", + .desc.of_match = of_match_ptr("ldo2"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_ldo_ops, + .desc.min_uV = (900) * 1000, + .desc.uV_step = (50) * 1000, + .desc.n_voltages = ((3600) - (600))/(50) + 1, + .desc.enable_reg = DA9062AA_LDO2_CONT, + .desc.enable_mask = DA9062AA_LDO2_EN_MASK, + .desc.vsel_reg = DA9062AA_VLDO2_A, + .desc.vsel_mask = DA9062AA_VLDO2_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VLDO2_A, + __builtin_ffs((int)DA9062AA_LDO2_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO2_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VLDO2_B, + __builtin_ffs((int)DA9062AA_LDO2_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO2_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VLDO2_B, + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VLDO2_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VLDO2_SEL_MASK)) - 1), + .oc_event = REG_FIELD(DA9062AA_STATUS_D, + __builtin_ffs((int)DA9062AA_LDO2_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO2_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9062_ID_LDO3, + .desc.name = "DA9062 LDO3", + .desc.of_match = of_match_ptr("ldo3"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_ldo_ops, + .desc.min_uV = (900) * 1000, + .desc.uV_step = (50) * 1000, + .desc.n_voltages = ((3600) - (900))/(50) + 1, + .desc.enable_reg = DA9062AA_LDO3_CONT, + .desc.enable_mask = DA9062AA_LDO3_EN_MASK, + .desc.vsel_reg = DA9062AA_VLDO3_A, + .desc.vsel_mask = DA9062AA_VLDO3_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VLDO3_A, + __builtin_ffs((int)DA9062AA_LDO3_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO3_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VLDO3_B, + __builtin_ffs((int)DA9062AA_LDO3_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO3_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VLDO3_B, + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VLDO3_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VLDO3_SEL_MASK)) - 1), + .oc_event = REG_FIELD(DA9062AA_STATUS_D, + __builtin_ffs((int)DA9062AA_LDO3_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO3_ILIM_MASK)) - 1), + }, + { + .desc.id = DA9062_ID_LDO4, + .desc.name = "DA9062 LDO4", + .desc.of_match = of_match_ptr("ldo4"), + .desc.regulators_node = of_match_ptr("regulators"), + .desc.ops = &da9062_ldo_ops, + .desc.min_uV = (900) * 1000, + .desc.uV_step = (50) * 1000, + .desc.n_voltages = ((3600) - (900))/(50) + 1, + .desc.enable_reg = DA9062AA_LDO4_CONT, + .desc.enable_mask = DA9062AA_LDO4_EN_MASK, + .desc.vsel_reg = DA9062AA_VLDO4_A, + .desc.vsel_mask = DA9062AA_VLDO4_A_MASK, + .desc.linear_min_sel = 0, + .sleep = REG_FIELD(DA9062AA_VLDO4_A, + __builtin_ffs((int)DA9062AA_LDO4_SL_A_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO4_SL_A_MASK)) - 1), + .suspend_sleep = REG_FIELD(DA9062AA_VLDO4_B, + __builtin_ffs((int)DA9062AA_LDO4_SL_B_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO4_SL_B_MASK)) - 1), + .suspend_vsel_reg = DA9062AA_VLDO4_B, + .suspend = REG_FIELD(DA9062AA_DVC_1, + __builtin_ffs((int)DA9062AA_VLDO4_SEL_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_VLDO4_SEL_MASK)) - 1), + .oc_event = REG_FIELD(DA9062AA_STATUS_D, + __builtin_ffs((int)DA9062AA_LDO4_ILIM_MASK) - 1, + sizeof(unsigned int) * 8 - + __builtin_clz((DA9062AA_LDO4_ILIM_MASK)) - 1), + }, +}; + +/* Regulator interrupt handlers */ +static irqreturn_t da9062_ldo_lim_event(int irq, void *data) +{ + struct da9062_regulators *regulators = data; + struct da9062 *hw = regulators->regulator[0].hw; + struct da9062_regulator *regl; + int handled = IRQ_NONE; + int bits, i, ret; + + ret = regmap_read(hw->regmap, DA9062AA_STATUS_D, &bits); + if (ret < 0) { + dev_err(hw->dev, + "Failed to read LDO overcurrent indicator\n"); + goto ldo_lim_error; + } + + for (i = regulators->n_regulators - 1; i >= 0; i--) { + regl = ®ulators->regulator[i]; + if (regl->info->oc_event.reg != DA9062AA_STATUS_D) + continue; + + if (BIT(regl->info->oc_event.lsb) & bits) { + regulator_notifier_call_chain(regl->rdev, + REGULATOR_EVENT_OVER_CURRENT, NULL); + handled = IRQ_HANDLED; + } + } + +ldo_lim_error: + return handled; +} + +static int da9062_regulator_probe(struct platform_device *pdev) +{ + struct da9062 *chip = dev_get_drvdata(pdev->dev.parent); + struct da9062_regulators *regulators; + struct da9062_regulator *regl; + struct regulator_config config = { }; + const struct da9062_regulator_info *rinfo; + int irq, n, ret; + size_t size; + int max_regulators; + + switch (chip->chip_type) { + case COMPAT_TYPE_DA9061: + max_regulators = DA9061_MAX_REGULATORS; + rinfo = local_da9061_regulator_info; + break; + case COMPAT_TYPE_DA9062: + max_regulators = DA9062_MAX_REGULATORS; + rinfo = local_da9062_regulator_info; + break; + default: + dev_err(chip->dev, "Unrecognised chip type\n"); + return -ENODEV; + } + + /* Allocate memory required by usable regulators */ + size = sizeof(struct da9062_regulators) + + max_regulators * sizeof(struct da9062_regulator); + regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!regulators) + return -ENOMEM; + + regulators->n_regulators = max_regulators; + platform_set_drvdata(pdev, regulators); + + n = 0; + while (n < regulators->n_regulators) { + /* Initialise regulator structure */ + regl = ®ulators->regulator[n]; + regl->hw = chip; + regl->info = &rinfo[n]; + regl->desc = regl->info->desc; + regl->desc.type = REGULATOR_VOLTAGE; + regl->desc.owner = THIS_MODULE; + + if (regl->info->mode.reg) + regl->mode = devm_regmap_field_alloc( + &pdev->dev, + chip->regmap, + regl->info->mode); + if (regl->info->suspend.reg) + regl->suspend = devm_regmap_field_alloc( + &pdev->dev, + chip->regmap, + regl->info->suspend); + if (regl->info->sleep.reg) + regl->sleep = devm_regmap_field_alloc( + &pdev->dev, + chip->regmap, + regl->info->sleep); + if (regl->info->suspend_sleep.reg) + regl->suspend_sleep = devm_regmap_field_alloc( + &pdev->dev, + chip->regmap, + regl->info->suspend_sleep); + if (regl->info->ilimit.reg) + regl->ilimit = devm_regmap_field_alloc( + &pdev->dev, + chip->regmap, + regl->info->ilimit); + + /* Register regulator */ + memset(&config, 0, sizeof(config)); + config.dev = chip->dev; + config.driver_data = regl; + config.regmap = chip->regmap; + + regl->rdev = devm_regulator_register(&pdev->dev, ®l->desc, + &config); + if (IS_ERR(regl->rdev)) { + dev_err(&pdev->dev, + "Failed to register %s regulator\n", + regl->desc.name); + return PTR_ERR(regl->rdev); + } + + n++; + } + + /* LDOs overcurrent event support */ + irq = platform_get_irq_byname(pdev, "LDO_LIM"); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ.\n"); + return irq; + } + regulators->irq_ldo_lim = irq; + + ret = devm_request_threaded_irq(&pdev->dev, irq, + NULL, da9062_ldo_lim_event, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "LDO_LIM", regulators); + if (ret) { + dev_warn(&pdev->dev, + "Failed to request LDO_LIM IRQ.\n"); + regulators->irq_ldo_lim = -ENXIO; + } + + return 0; +} + +static struct platform_driver da9062_regulator_driver = { + .driver = { + .name = "da9062-regulators", + }, + .probe = da9062_regulator_probe, +}; + +static int __init da9062_regulator_init(void) +{ + return platform_driver_register(&da9062_regulator_driver); +} +subsys_initcall(da9062_regulator_init); + +static void __exit da9062_regulator_cleanup(void) +{ + platform_driver_unregister(&da9062_regulator_driver); +} +module_exit(da9062_regulator_cleanup); + +/* Module information */ +MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>"); +MODULE_DESCRIPTION("REGULATOR device driver for Dialog DA9062 and DA9061"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9062-regulators"); diff --git a/drivers/regulator/da9063-regulator.c b/drivers/regulator/da9063-regulator.c new file mode 100644 index 000000000..8cbcd2a3e --- /dev/null +++ b/drivers/regulator/da9063-regulator.c @@ -0,0 +1,917 @@ + +/* + * Regulator driver for DA9063 PMIC series + * + * Copyright 2012 Dialog Semiconductors Ltd. + * Copyright 2013 Philipp Zabel, Pengutronix + * + * Author: Krystian Garbaciak <krystian.garbaciak@diasemi.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; 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/slab.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/da9063/core.h> +#include <linux/mfd/da9063/pdata.h> +#include <linux/mfd/da9063/registers.h> + + +/* Definition for registering regmap bit fields using a mask */ +#define BFIELD(_reg, _mask) \ + REG_FIELD(_reg, __builtin_ffs((int)_mask) - 1, \ + sizeof(unsigned int) * 8 - __builtin_clz((_mask)) - 1) + +/* Regulator capabilities and registers description */ +struct da9063_regulator_info { + struct regulator_desc desc; + + /* Current limiting */ + unsigned n_current_limits; + const int *current_limits; + + /* DA9063 main register fields */ + struct reg_field mode; /* buck mode of operation */ + struct reg_field suspend; + struct reg_field sleep; + struct reg_field suspend_sleep; + unsigned int suspend_vsel_reg; + struct reg_field ilimit; + + /* DA9063 event detection bit */ + struct reg_field oc_event; +}; + +/* Macros for LDO */ +#define DA9063_LDO(chip, regl_name, min_mV, step_mV, max_mV) \ + .desc.id = chip##_ID_##regl_name, \ + .desc.name = __stringify(chip##_##regl_name), \ + .desc.ops = &da9063_ldo_ops, \ + .desc.min_uV = (min_mV) * 1000, \ + .desc.uV_step = (step_mV) * 1000, \ + .desc.n_voltages = (((max_mV) - (min_mV))/(step_mV) + 1 \ + + (DA9063_V##regl_name##_BIAS)), \ + .desc.enable_reg = DA9063_REG_##regl_name##_CONT, \ + .desc.enable_mask = DA9063_LDO_EN, \ + .desc.vsel_reg = DA9063_REG_V##regl_name##_A, \ + .desc.vsel_mask = DA9063_V##regl_name##_MASK, \ + .desc.linear_min_sel = DA9063_V##regl_name##_BIAS, \ + .sleep = BFIELD(DA9063_REG_V##regl_name##_A, DA9063_LDO_SL), \ + .suspend_sleep = BFIELD(DA9063_REG_V##regl_name##_B, DA9063_LDO_SL), \ + .suspend_vsel_reg = DA9063_REG_V##regl_name##_B + +/* Macros for voltage DC/DC converters (BUCKs) */ +#define DA9063_BUCK(chip, regl_name, min_mV, step_mV, max_mV, limits_array) \ + .desc.id = chip##_ID_##regl_name, \ + .desc.name = __stringify(chip##_##regl_name), \ + .desc.ops = &da9063_buck_ops, \ + .desc.min_uV = (min_mV) * 1000, \ + .desc.uV_step = (step_mV) * 1000, \ + .desc.n_voltages = ((max_mV) - (min_mV))/(step_mV) + 1, \ + .current_limits = limits_array, \ + .n_current_limits = ARRAY_SIZE(limits_array) + +#define DA9063_BUCK_COMMON_FIELDS(regl_name) \ + .desc.enable_reg = DA9063_REG_##regl_name##_CONT, \ + .desc.enable_mask = DA9063_BUCK_EN, \ + .desc.vsel_reg = DA9063_REG_V##regl_name##_A, \ + .desc.vsel_mask = DA9063_VBUCK_MASK, \ + .desc.linear_min_sel = DA9063_VBUCK_BIAS, \ + .sleep = BFIELD(DA9063_REG_V##regl_name##_A, DA9063_BUCK_SL), \ + .suspend_sleep = BFIELD(DA9063_REG_V##regl_name##_B, DA9063_BUCK_SL), \ + .suspend_vsel_reg = DA9063_REG_V##regl_name##_B, \ + .mode = BFIELD(DA9063_REG_##regl_name##_CFG, DA9063_BUCK_MODE_MASK) + +/* Defines asignment of regulators info table to chip model */ +struct da9063_dev_model { + const struct da9063_regulator_info *regulator_info; + unsigned n_regulators; + enum da9063_type type; +}; + +/* Single regulator settings */ +struct da9063_regulator { + struct regulator_desc desc; + struct regulator_dev *rdev; + struct da9063 *hw; + const struct da9063_regulator_info *info; + + struct regmap_field *mode; + struct regmap_field *suspend; + struct regmap_field *sleep; + struct regmap_field *suspend_sleep; + struct regmap_field *ilimit; +}; + +/* Encapsulates all information for the regulators driver */ +struct da9063_regulators { + unsigned n_regulators; + /* Array size to be defined during init. Keep at end. */ + struct da9063_regulator regulator[0]; +}; + +/* BUCK modes for DA9063 */ +enum { + BUCK_MODE_MANUAL, /* 0 */ + BUCK_MODE_SLEEP, /* 1 */ + BUCK_MODE_SYNC, /* 2 */ + BUCK_MODE_AUTO /* 3 */ +}; + +/* Regulator operations */ + +/* Current limits array (in uA) for BCORE1, BCORE2, BPRO. + Entry indexes corresponds to register values. */ +static const int da9063_buck_a_limits[] = { + 500000, 600000, 700000, 800000, 900000, 1000000, 1100000, 1200000, + 1300000, 1400000, 1500000, 1600000, 1700000, 1800000, 1900000, 2000000 +}; + +/* Current limits array (in uA) for BMEM, BIO, BPERI. + Entry indexes corresponds to register values. */ +static const int da9063_buck_b_limits[] = { + 1500000, 1600000, 1700000, 1800000, 1900000, 2000000, 2100000, 2200000, + 2300000, 2400000, 2500000, 2600000, 2700000, 2800000, 2900000, 3000000 +}; + +/* Current limits array (in uA) for merged BCORE1 and BCORE2. + Entry indexes corresponds to register values. */ +static const int da9063_bcores_merged_limits[] = { + 1000000, 1200000, 1400000, 1600000, 1800000, 2000000, 2200000, 2400000, + 2600000, 2800000, 3000000, 3200000, 3400000, 3600000, 3800000, 4000000 +}; + +/* Current limits array (in uA) for merged BMEM and BIO. + Entry indexes corresponds to register values. */ +static const int da9063_bmem_bio_merged_limits[] = { + 3000000, 3200000, 3400000, 3600000, 3800000, 4000000, 4200000, 4400000, + 4600000, 4800000, 5000000, 5200000, 5400000, 5600000, 5800000, 6000000 +}; + +static int da9063_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + const struct da9063_regulator_info *rinfo = regl->info; + int n, tval; + + for (n = 0; n < rinfo->n_current_limits; n++) { + tval = rinfo->current_limits[n]; + if (tval >= min_uA && tval <= max_uA) + return regmap_field_write(regl->ilimit, n); + } + + return -EINVAL; +} + +static int da9063_get_current_limit(struct regulator_dev *rdev) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + const struct da9063_regulator_info *rinfo = regl->info; + unsigned int sel; + int ret; + + ret = regmap_field_read(regl->ilimit, &sel); + if (ret < 0) + return ret; + + if (sel >= rinfo->n_current_limits) + sel = rinfo->n_current_limits - 1; + + return rinfo->current_limits[sel]; +} + +static int da9063_buck_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + unsigned val; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = BUCK_MODE_SYNC; + break; + case REGULATOR_MODE_NORMAL: + val = BUCK_MODE_AUTO; + break; + case REGULATOR_MODE_STANDBY: + val = BUCK_MODE_SLEEP; + break; + default: + return -EINVAL; + } + + return regmap_field_write(regl->mode, val); +} + +/* + * Bucks use single mode register field for normal operation + * and suspend state. + * There are 3 modes to map to: FAST, NORMAL, and STANDBY. + */ + +static unsigned da9063_buck_get_mode(struct regulator_dev *rdev) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + struct regmap_field *field; + unsigned int val, mode = 0; + int ret; + + ret = regmap_field_read(regl->mode, &val); + if (ret < 0) + return ret; + + switch (val) { + default: + case BUCK_MODE_MANUAL: + mode = REGULATOR_MODE_FAST | REGULATOR_MODE_STANDBY; + /* Sleep flag bit decides the mode */ + break; + case BUCK_MODE_SLEEP: + return REGULATOR_MODE_STANDBY; + case BUCK_MODE_SYNC: + return REGULATOR_MODE_FAST; + case BUCK_MODE_AUTO: + return REGULATOR_MODE_NORMAL; + } + + /* Detect current regulator state */ + ret = regmap_field_read(regl->suspend, &val); + if (ret < 0) + return 0; + + /* Read regulator mode from proper register, depending on state */ + if (val) + field = regl->suspend_sleep; + else + field = regl->sleep; + + ret = regmap_field_read(field, &val); + if (ret < 0) + return 0; + + if (val) + mode &= REGULATOR_MODE_STANDBY; + else + mode &= REGULATOR_MODE_NORMAL | REGULATOR_MODE_FAST; + + return mode; +} + +/* + * LDOs use sleep flags - one for normal and one for suspend state. + * There are 2 modes to map to: NORMAL and STANDBY (sleep) for each state. + */ + +static int da9063_ldo_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + unsigned val; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + val = 0; + break; + case REGULATOR_MODE_STANDBY: + val = 1; + break; + default: + return -EINVAL; + } + + return regmap_field_write(regl->sleep, val); +} + +static unsigned da9063_ldo_get_mode(struct regulator_dev *rdev) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + struct regmap_field *field; + int ret, val; + + /* Detect current regulator state */ + ret = regmap_field_read(regl->suspend, &val); + if (ret < 0) + return 0; + + /* Read regulator mode from proper register, depending on state */ + if (val) + field = regl->suspend_sleep; + else + field = regl->sleep; + + ret = regmap_field_read(field, &val); + if (ret < 0) + return 0; + + if (val) + return REGULATOR_MODE_STANDBY; + else + return REGULATOR_MODE_NORMAL; +} + +static int da9063_buck_get_status(struct regulator_dev *rdev) +{ + int ret = regulator_is_enabled_regmap(rdev); + + if (ret == 0) { + ret = REGULATOR_STATUS_OFF; + } else if (ret > 0) { + ret = da9063_buck_get_mode(rdev); + if (ret > 0) + ret = regulator_mode_to_status(ret); + else if (ret == 0) + ret = -EIO; + } + + return ret; +} + +static int da9063_ldo_get_status(struct regulator_dev *rdev) +{ + int ret = regulator_is_enabled_regmap(rdev); + + if (ret == 0) { + ret = REGULATOR_STATUS_OFF; + } else if (ret > 0) { + ret = da9063_ldo_get_mode(rdev); + if (ret > 0) + ret = regulator_mode_to_status(ret); + else if (ret == 0) + ret = -EIO; + } + + return ret; +} + +static int da9063_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + const struct da9063_regulator_info *rinfo = regl->info; + int ret, sel; + + sel = regulator_map_voltage_linear(rdev, uV, uV); + if (sel < 0) + return sel; + + sel <<= ffs(rdev->desc->vsel_mask) - 1; + + ret = regmap_update_bits(regl->hw->regmap, rinfo->suspend_vsel_reg, + rdev->desc->vsel_mask, sel); + + return ret; +} + +static int da9063_suspend_enable(struct regulator_dev *rdev) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + + return regmap_field_write(regl->suspend, 1); +} + +static int da9063_suspend_disable(struct regulator_dev *rdev) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + + return regmap_field_write(regl->suspend, 0); +} + +static int da9063_buck_set_suspend_mode(struct regulator_dev *rdev, unsigned mode) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + int val; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = BUCK_MODE_SYNC; + break; + case REGULATOR_MODE_NORMAL: + val = BUCK_MODE_AUTO; + break; + case REGULATOR_MODE_STANDBY: + val = BUCK_MODE_SLEEP; + break; + default: + return -EINVAL; + } + + return regmap_field_write(regl->mode, val); +} + +static int da9063_ldo_set_suspend_mode(struct regulator_dev *rdev, unsigned mode) +{ + struct da9063_regulator *regl = rdev_get_drvdata(rdev); + unsigned val; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + val = 0; + break; + case REGULATOR_MODE_STANDBY: + val = 1; + break; + default: + return -EINVAL; + } + + return regmap_field_write(regl->suspend_sleep, val); +} + +static const struct regulator_ops da9063_buck_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_current_limit = da9063_set_current_limit, + .get_current_limit = da9063_get_current_limit, + .set_mode = da9063_buck_set_mode, + .get_mode = da9063_buck_get_mode, + .get_status = da9063_buck_get_status, + .set_suspend_voltage = da9063_set_suspend_voltage, + .set_suspend_enable = da9063_suspend_enable, + .set_suspend_disable = da9063_suspend_disable, + .set_suspend_mode = da9063_buck_set_suspend_mode, +}; + +static const struct regulator_ops da9063_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_mode = da9063_ldo_set_mode, + .get_mode = da9063_ldo_get_mode, + .get_status = da9063_ldo_get_status, + .set_suspend_voltage = da9063_set_suspend_voltage, + .set_suspend_enable = da9063_suspend_enable, + .set_suspend_disable = da9063_suspend_disable, + .set_suspend_mode = da9063_ldo_set_suspend_mode, +}; + +/* Info of regulators for DA9063 */ +static const struct da9063_regulator_info da9063_regulator_info[] = { + { + DA9063_BUCK(DA9063, BCORE1, 300, 10, 1570, + da9063_buck_a_limits), + DA9063_BUCK_COMMON_FIELDS(BCORE1), + .suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBCORE1_SEL), + .ilimit = BFIELD(DA9063_REG_BUCK_ILIM_C, + DA9063_BCORE1_ILIM_MASK), + }, + { + DA9063_BUCK(DA9063, BCORE2, 300, 10, 1570, + da9063_buck_a_limits), + DA9063_BUCK_COMMON_FIELDS(BCORE2), + .suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBCORE2_SEL), + .ilimit = BFIELD(DA9063_REG_BUCK_ILIM_C, + DA9063_BCORE2_ILIM_MASK), + }, + { + DA9063_BUCK(DA9063, BPRO, 530, 10, 1800, + da9063_buck_a_limits), + DA9063_BUCK_COMMON_FIELDS(BPRO), + .suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBPRO_SEL), + .ilimit = BFIELD(DA9063_REG_BUCK_ILIM_B, + DA9063_BPRO_ILIM_MASK), + }, + { + DA9063_BUCK(DA9063, BMEM, 800, 20, 3340, + da9063_buck_b_limits), + DA9063_BUCK_COMMON_FIELDS(BMEM), + .suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBMEM_SEL), + .ilimit = BFIELD(DA9063_REG_BUCK_ILIM_A, + DA9063_BMEM_ILIM_MASK), + }, + { + DA9063_BUCK(DA9063, BIO, 800, 20, 3340, + da9063_buck_b_limits), + DA9063_BUCK_COMMON_FIELDS(BIO), + .suspend = BFIELD(DA9063_REG_DVC_2, DA9063_VBIO_SEL), + .ilimit = BFIELD(DA9063_REG_BUCK_ILIM_A, + DA9063_BIO_ILIM_MASK), + }, + { + DA9063_BUCK(DA9063, BPERI, 800, 20, 3340, + da9063_buck_b_limits), + DA9063_BUCK_COMMON_FIELDS(BPERI), + .suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBPERI_SEL), + .ilimit = BFIELD(DA9063_REG_BUCK_ILIM_B, + DA9063_BPERI_ILIM_MASK), + }, + { + DA9063_BUCK(DA9063, BCORES_MERGED, 300, 10, 1570, + da9063_bcores_merged_limits), + /* BCORES_MERGED uses the same register fields as BCORE1 */ + DA9063_BUCK_COMMON_FIELDS(BCORE1), + .suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBCORE1_SEL), + .ilimit = BFIELD(DA9063_REG_BUCK_ILIM_C, + DA9063_BCORE1_ILIM_MASK), + }, + { + DA9063_BUCK(DA9063, BMEM_BIO_MERGED, 800, 20, 3340, + da9063_bmem_bio_merged_limits), + /* BMEM_BIO_MERGED uses the same register fields as BMEM */ + DA9063_BUCK_COMMON_FIELDS(BMEM), + .suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBMEM_SEL), + .ilimit = BFIELD(DA9063_REG_BUCK_ILIM_A, + DA9063_BMEM_ILIM_MASK), + }, + { + DA9063_LDO(DA9063, LDO3, 900, 20, 3440), + .suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VLDO3_SEL), + .oc_event = BFIELD(DA9063_REG_STATUS_D, DA9063_LDO3_LIM), + }, + { + DA9063_LDO(DA9063, LDO7, 900, 50, 3600), + .suspend = BFIELD(DA9063_REG_LDO7_CONT, DA9063_VLDO7_SEL), + .oc_event = BFIELD(DA9063_REG_STATUS_D, DA9063_LDO7_LIM), + }, + { + DA9063_LDO(DA9063, LDO8, 900, 50, 3600), + .suspend = BFIELD(DA9063_REG_LDO8_CONT, DA9063_VLDO8_SEL), + .oc_event = BFIELD(DA9063_REG_STATUS_D, DA9063_LDO8_LIM), + }, + { + DA9063_LDO(DA9063, LDO9, 950, 50, 3600), + .suspend = BFIELD(DA9063_REG_LDO9_CONT, DA9063_VLDO9_SEL), + }, + { + DA9063_LDO(DA9063, LDO11, 900, 50, 3600), + .suspend = BFIELD(DA9063_REG_LDO11_CONT, DA9063_VLDO11_SEL), + .oc_event = BFIELD(DA9063_REG_STATUS_D, DA9063_LDO11_LIM), + }, + + /* The following LDOs are present only on DA9063, not on DA9063L */ + { + DA9063_LDO(DA9063, LDO1, 600, 20, 1860), + .suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VLDO1_SEL), + }, + { + DA9063_LDO(DA9063, LDO2, 600, 20, 1860), + .suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VLDO2_SEL), + }, + { + DA9063_LDO(DA9063, LDO4, 900, 20, 3440), + .suspend = BFIELD(DA9063_REG_DVC_2, DA9063_VLDO4_SEL), + .oc_event = BFIELD(DA9063_REG_STATUS_D, DA9063_LDO4_LIM), + }, + { + DA9063_LDO(DA9063, LDO5, 900, 50, 3600), + .suspend = BFIELD(DA9063_REG_LDO5_CONT, DA9063_VLDO5_SEL), + }, + { + DA9063_LDO(DA9063, LDO6, 900, 50, 3600), + .suspend = BFIELD(DA9063_REG_LDO6_CONT, DA9063_VLDO6_SEL), + }, + + { + DA9063_LDO(DA9063, LDO10, 900, 50, 3600), + .suspend = BFIELD(DA9063_REG_LDO10_CONT, DA9063_VLDO10_SEL), + }, +}; + +/* Link chip model with regulators info table */ +static struct da9063_dev_model regulators_models[] = { + { + .regulator_info = da9063_regulator_info, + .n_regulators = ARRAY_SIZE(da9063_regulator_info), + .type = PMIC_TYPE_DA9063, + }, + { + .regulator_info = da9063_regulator_info, + .n_regulators = ARRAY_SIZE(da9063_regulator_info) - 6, + .type = PMIC_TYPE_DA9063L, + }, + { } +}; + +/* Regulator interrupt handlers */ +static irqreturn_t da9063_ldo_lim_event(int irq, void *data) +{ + struct da9063_regulators *regulators = data; + struct da9063 *hw = regulators->regulator[0].hw; + struct da9063_regulator *regl; + int bits, i , ret; + + ret = regmap_read(hw->regmap, DA9063_REG_STATUS_D, &bits); + if (ret < 0) + return IRQ_NONE; + + for (i = regulators->n_regulators - 1; i >= 0; i--) { + regl = ®ulators->regulator[i]; + if (regl->info->oc_event.reg != DA9063_REG_STATUS_D) + continue; + + if (BIT(regl->info->oc_event.lsb) & bits) + regulator_notifier_call_chain(regl->rdev, + REGULATOR_EVENT_OVER_CURRENT, NULL); + } + + return IRQ_HANDLED; +} + +/* + * Probing and Initialisation functions + */ +static const struct regulator_init_data *da9063_get_regulator_initdata( + const struct da9063_regulators_pdata *regl_pdata, int id) +{ + int i; + + for (i = 0; i < regl_pdata->n_regulators; i++) { + if (id == regl_pdata->regulator_data[i].id) + return regl_pdata->regulator_data[i].initdata; + } + + return NULL; +} + +#ifdef CONFIG_OF +static struct of_regulator_match da9063_matches[] = { + [DA9063_ID_BCORE1] = { .name = "bcore1" }, + [DA9063_ID_BCORE2] = { .name = "bcore2" }, + [DA9063_ID_BPRO] = { .name = "bpro", }, + [DA9063_ID_BMEM] = { .name = "bmem", }, + [DA9063_ID_BIO] = { .name = "bio", }, + [DA9063_ID_BPERI] = { .name = "bperi", }, + [DA9063_ID_BCORES_MERGED] = { .name = "bcores-merged" }, + [DA9063_ID_BMEM_BIO_MERGED] = { .name = "bmem-bio-merged", }, + [DA9063_ID_LDO3] = { .name = "ldo3", }, + [DA9063_ID_LDO7] = { .name = "ldo7", }, + [DA9063_ID_LDO8] = { .name = "ldo8", }, + [DA9063_ID_LDO9] = { .name = "ldo9", }, + [DA9063_ID_LDO11] = { .name = "ldo11", }, + /* The following LDOs are present only on DA9063, not on DA9063L */ + [DA9063_ID_LDO1] = { .name = "ldo1", }, + [DA9063_ID_LDO2] = { .name = "ldo2", }, + [DA9063_ID_LDO4] = { .name = "ldo4", }, + [DA9063_ID_LDO5] = { .name = "ldo5", }, + [DA9063_ID_LDO6] = { .name = "ldo6", }, + [DA9063_ID_LDO10] = { .name = "ldo10", }, +}; + +static struct da9063_regulators_pdata *da9063_parse_regulators_dt( + struct platform_device *pdev, + struct of_regulator_match **da9063_reg_matches) +{ + struct da9063 *da9063 = dev_get_drvdata(pdev->dev.parent); + struct da9063_regulators_pdata *pdata; + struct da9063_regulator_data *rdata; + struct device_node *node; + int da9063_matches_len = ARRAY_SIZE(da9063_matches); + int i, n, num; + + if (da9063->type == PMIC_TYPE_DA9063L) + da9063_matches_len -= 6; + + node = of_get_child_by_name(pdev->dev.parent->of_node, "regulators"); + if (!node) { + dev_err(&pdev->dev, "Regulators device node not found\n"); + return ERR_PTR(-ENODEV); + } + + num = of_regulator_match(&pdev->dev, node, da9063_matches, + da9063_matches_len); + of_node_put(node); + if (num < 0) { + dev_err(&pdev->dev, "Failed to match regulators\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->regulator_data = devm_kcalloc(&pdev->dev, + num, sizeof(*pdata->regulator_data), + GFP_KERNEL); + if (!pdata->regulator_data) + return ERR_PTR(-ENOMEM); + pdata->n_regulators = num; + + n = 0; + for (i = 0; i < da9063_matches_len; i++) { + if (!da9063_matches[i].init_data) + continue; + + rdata = &pdata->regulator_data[n]; + rdata->id = i; + rdata->initdata = da9063_matches[i].init_data; + + n++; + } + + *da9063_reg_matches = da9063_matches; + return pdata; +} +#else +static struct da9063_regulators_pdata *da9063_parse_regulators_dt( + struct platform_device *pdev, + struct of_regulator_match **da9063_reg_matches) +{ + *da9063_reg_matches = NULL; + return ERR_PTR(-ENODEV); +} +#endif + +static int da9063_regulator_probe(struct platform_device *pdev) +{ + struct da9063 *da9063 = dev_get_drvdata(pdev->dev.parent); + struct da9063_pdata *da9063_pdata = dev_get_platdata(da9063->dev); + struct of_regulator_match *da9063_reg_matches = NULL; + struct da9063_regulators_pdata *regl_pdata; + const struct da9063_dev_model *model; + struct da9063_regulators *regulators; + struct da9063_regulator *regl; + struct regulator_config config; + bool bcores_merged, bmem_bio_merged; + int id, irq, n, n_regulators, ret, val; + size_t size; + + regl_pdata = da9063_pdata ? da9063_pdata->regulators_pdata : NULL; + + if (!regl_pdata) + regl_pdata = da9063_parse_regulators_dt(pdev, + &da9063_reg_matches); + + if (IS_ERR(regl_pdata) || regl_pdata->n_regulators == 0) { + dev_err(&pdev->dev, + "No regulators defined for the platform\n"); + return -ENODEV; + } + + /* Find regulators set for particular device model */ + for (model = regulators_models; model->regulator_info; model++) { + if (model->type == da9063->type) + break; + } + if (!model->regulator_info) { + dev_err(&pdev->dev, "Chip model not recognised (%u)\n", + da9063->type); + return -ENODEV; + } + + ret = regmap_read(da9063->regmap, DA9063_REG_CONFIG_H, &val); + if (ret < 0) { + dev_err(&pdev->dev, + "Error while reading BUCKs configuration\n"); + return ret; + } + bcores_merged = val & DA9063_BCORE_MERGE; + bmem_bio_merged = val & DA9063_BUCK_MERGE; + + n_regulators = model->n_regulators; + if (bcores_merged) + n_regulators -= 2; /* remove BCORE1, BCORE2 */ + else + n_regulators--; /* remove BCORES_MERGED */ + if (bmem_bio_merged) + n_regulators -= 2; /* remove BMEM, BIO */ + else + n_regulators--; /* remove BMEM_BIO_MERGED */ + + /* Allocate memory required by usable regulators */ + size = sizeof(struct da9063_regulators) + + n_regulators * sizeof(struct da9063_regulator); + regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!regulators) + return -ENOMEM; + + regulators->n_regulators = n_regulators; + platform_set_drvdata(pdev, regulators); + + /* Register all regulators declared in platform information */ + n = 0; + id = 0; + while (n < regulators->n_regulators) { + /* Skip regulator IDs depending on merge mode configuration */ + switch (id) { + case DA9063_ID_BCORE1: + case DA9063_ID_BCORE2: + if (bcores_merged) { + id++; + continue; + } + break; + case DA9063_ID_BMEM: + case DA9063_ID_BIO: + if (bmem_bio_merged) { + id++; + continue; + } + break; + case DA9063_ID_BCORES_MERGED: + if (!bcores_merged) { + id++; + continue; + } + break; + case DA9063_ID_BMEM_BIO_MERGED: + if (!bmem_bio_merged) { + id++; + continue; + } + break; + } + + /* Initialise regulator structure */ + regl = ®ulators->regulator[n]; + regl->hw = da9063; + regl->info = &model->regulator_info[id]; + regl->desc = regl->info->desc; + regl->desc.type = REGULATOR_VOLTAGE; + regl->desc.owner = THIS_MODULE; + + if (regl->info->mode.reg) + regl->mode = devm_regmap_field_alloc(&pdev->dev, + da9063->regmap, regl->info->mode); + if (regl->info->suspend.reg) + regl->suspend = devm_regmap_field_alloc(&pdev->dev, + da9063->regmap, regl->info->suspend); + if (regl->info->sleep.reg) + regl->sleep = devm_regmap_field_alloc(&pdev->dev, + da9063->regmap, regl->info->sleep); + if (regl->info->suspend_sleep.reg) + regl->suspend_sleep = devm_regmap_field_alloc(&pdev->dev, + da9063->regmap, regl->info->suspend_sleep); + if (regl->info->ilimit.reg) + regl->ilimit = devm_regmap_field_alloc(&pdev->dev, + da9063->regmap, regl->info->ilimit); + + /* Register regulator */ + memset(&config, 0, sizeof(config)); + config.dev = &pdev->dev; + config.init_data = da9063_get_regulator_initdata(regl_pdata, id); + config.driver_data = regl; + if (da9063_reg_matches) + config.of_node = da9063_reg_matches[id].of_node; + config.regmap = da9063->regmap; + regl->rdev = devm_regulator_register(&pdev->dev, ®l->desc, + &config); + if (IS_ERR(regl->rdev)) { + dev_err(&pdev->dev, + "Failed to register %s regulator\n", + regl->desc.name); + return PTR_ERR(regl->rdev); + } + id++; + n++; + } + + /* LDOs overcurrent event support */ + irq = platform_get_irq_byname(pdev, "LDO_LIM"); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ.\n"); + return irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, + NULL, da9063_ldo_lim_event, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "LDO_LIM", regulators); + if (ret) { + dev_err(&pdev->dev, "Failed to request LDO_LIM IRQ.\n"); + return ret; + } + + return 0; +} + +static struct platform_driver da9063_regulator_driver = { + .driver = { + .name = DA9063_DRVNAME_REGULATORS, + }, + .probe = da9063_regulator_probe, +}; + +static int __init da9063_regulator_init(void) +{ + return platform_driver_register(&da9063_regulator_driver); +} +subsys_initcall(da9063_regulator_init); + +static void __exit da9063_regulator_cleanup(void) +{ + platform_driver_unregister(&da9063_regulator_driver); +} +module_exit(da9063_regulator_cleanup); + + +/* Module information */ +MODULE_AUTHOR("Krystian Garbaciak <krystian.garbaciak@diasemi.com>"); +MODULE_DESCRIPTION("DA9063 regulators driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DA9063_DRVNAME_REGULATORS); diff --git a/drivers/regulator/da9210-regulator.c b/drivers/regulator/da9210-regulator.c new file mode 100644 index 000000000..d0496d6b0 --- /dev/null +++ b/drivers/regulator/da9210-regulator.c @@ -0,0 +1,293 @@ +/* + * da9210-regulator.c - Regulator device driver for DA9210 + * Copyright (C) 2013 Dialog Semiconductor Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/of_device.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regmap.h> + +#include "da9210-regulator.h" + +struct da9210 { + struct regulator_dev *rdev; + struct regmap *regmap; +}; + +static const struct regmap_config da9210_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int da9210_set_current_limit(struct regulator_dev *rdev, int min_uA, + int max_uA); +static int da9210_get_current_limit(struct regulator_dev *rdev); + +static const struct regulator_ops da9210_buck_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_current_limit = da9210_set_current_limit, + .get_current_limit = da9210_get_current_limit, +}; + +/* Default limits measured in millivolts and milliamps */ +#define DA9210_MIN_MV 300 +#define DA9210_MAX_MV 1570 +#define DA9210_STEP_MV 10 + +/* Current limits for buck (uA) indices corresponds with register values */ +static const int da9210_buck_limits[] = { + 1600000, 1800000, 2000000, 2200000, 2400000, 2600000, 2800000, 3000000, + 3200000, 3400000, 3600000, 3800000, 4000000, 4200000, 4400000, 4600000 +}; + +static const struct regulator_desc da9210_reg = { + .name = "DA9210", + .id = 0, + .ops = &da9210_buck_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = ((DA9210_MAX_MV - DA9210_MIN_MV) / DA9210_STEP_MV) + 1, + .min_uV = (DA9210_MIN_MV * 1000), + .uV_step = (DA9210_STEP_MV * 1000), + .vsel_reg = DA9210_REG_VBUCK_A, + .vsel_mask = DA9210_VBUCK_MASK, + .enable_reg = DA9210_REG_BUCK_CONT, + .enable_mask = DA9210_BUCK_EN, + .owner = THIS_MODULE, +}; + +static int da9210_set_current_limit(struct regulator_dev *rdev, int min_uA, + int max_uA) +{ + struct da9210 *chip = rdev_get_drvdata(rdev); + unsigned int sel; + int i; + + /* search for closest to maximum */ + for (i = ARRAY_SIZE(da9210_buck_limits)-1; i >= 0; i--) { + if (min_uA <= da9210_buck_limits[i] && + max_uA >= da9210_buck_limits[i]) { + sel = i; + sel = sel << DA9210_BUCK_ILIM_SHIFT; + return regmap_update_bits(chip->regmap, + DA9210_REG_BUCK_ILIM, + DA9210_BUCK_ILIM_MASK, sel); + } + } + + return -EINVAL; +} + +static int da9210_get_current_limit(struct regulator_dev *rdev) +{ + struct da9210 *chip = rdev_get_drvdata(rdev); + unsigned int data; + unsigned int sel; + int ret; + + ret = regmap_read(chip->regmap, DA9210_REG_BUCK_ILIM, &data); + if (ret < 0) + return ret; + + /* select one of 16 values: 0000 (1600mA) to 1111 (4600mA) */ + sel = (data & DA9210_BUCK_ILIM_MASK) >> DA9210_BUCK_ILIM_SHIFT; + + return da9210_buck_limits[sel]; +} + +static irqreturn_t da9210_irq_handler(int irq, void *data) +{ + struct da9210 *chip = data; + unsigned int val, handled = 0; + int error, ret = IRQ_NONE; + + error = regmap_read(chip->regmap, DA9210_REG_EVENT_B, &val); + if (error < 0) + goto error_i2c; + + mutex_lock(&chip->rdev->mutex); + + if (val & DA9210_E_OVCURR) { + regulator_notifier_call_chain(chip->rdev, + REGULATOR_EVENT_OVER_CURRENT, + NULL); + handled |= DA9210_E_OVCURR; + } + if (val & DA9210_E_NPWRGOOD) { + regulator_notifier_call_chain(chip->rdev, + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + handled |= DA9210_E_NPWRGOOD; + } + if (val & (DA9210_E_TEMP_WARN | DA9210_E_TEMP_CRIT)) { + regulator_notifier_call_chain(chip->rdev, + REGULATOR_EVENT_OVER_TEMP, NULL); + handled |= val & (DA9210_E_TEMP_WARN | DA9210_E_TEMP_CRIT); + } + if (val & DA9210_E_VMAX) { + regulator_notifier_call_chain(chip->rdev, + REGULATOR_EVENT_REGULATION_OUT, + NULL); + handled |= DA9210_E_VMAX; + } + + mutex_unlock(&chip->rdev->mutex); + + if (handled) { + /* Clear handled events */ + error = regmap_write(chip->regmap, DA9210_REG_EVENT_B, handled); + if (error < 0) + goto error_i2c; + + ret = IRQ_HANDLED; + } + + return ret; + +error_i2c: + dev_err(regmap_get_device(chip->regmap), "I2C error : %d\n", error); + return ret; +} + +/* + * I2C driver interface functions + */ + +static const struct of_device_id da9210_dt_ids[] = { + { .compatible = "dlg,da9210", }, + { } +}; +MODULE_DEVICE_TABLE(of, da9210_dt_ids); + +static int da9210_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da9210 *chip; + struct device *dev = &i2c->dev; + struct da9210_pdata *pdata = dev_get_platdata(dev); + struct regulator_dev *rdev = NULL; + struct regulator_config config = { }; + int error; + const struct of_device_id *match; + + if (i2c->dev.of_node && !pdata) { + match = of_match_device(of_match_ptr(da9210_dt_ids), + &i2c->dev); + if (!match) { + dev_err(&i2c->dev, "Error: No device match found\n"); + return -ENODEV; + } + } + + chip = devm_kzalloc(&i2c->dev, sizeof(struct da9210), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->regmap = devm_regmap_init_i2c(i2c, &da9210_regmap_config); + if (IS_ERR(chip->regmap)) { + error = PTR_ERR(chip->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + error); + return error; + } + + config.dev = &i2c->dev; + config.init_data = pdata ? &pdata->da9210_constraints : + of_get_regulator_init_data(dev, dev->of_node, &da9210_reg); + config.driver_data = chip; + config.regmap = chip->regmap; + config.of_node = dev->of_node; + + /* Mask all interrupt sources to deassert interrupt line */ + error = regmap_write(chip->regmap, DA9210_REG_MASK_A, ~0); + if (!error) + error = regmap_write(chip->regmap, DA9210_REG_MASK_B, ~0); + if (error) { + dev_err(&i2c->dev, "Failed to write to mask reg: %d\n", error); + return error; + } + + rdev = devm_regulator_register(&i2c->dev, &da9210_reg, &config); + if (IS_ERR(rdev)) { + dev_err(&i2c->dev, "Failed to register DA9210 regulator\n"); + return PTR_ERR(rdev); + } + + chip->rdev = rdev; + if (i2c->irq) { + error = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + da9210_irq_handler, + IRQF_TRIGGER_LOW | + IRQF_ONESHOT | IRQF_SHARED, + "da9210", chip); + if (error) { + dev_err(&i2c->dev, "Failed to request IRQ%u: %d\n", + i2c->irq, error); + return error; + } + + error = regmap_update_bits(chip->regmap, DA9210_REG_MASK_B, + DA9210_M_OVCURR | DA9210_M_NPWRGOOD | + DA9210_M_TEMP_WARN | + DA9210_M_TEMP_CRIT | DA9210_M_VMAX, 0); + if (error < 0) { + dev_err(&i2c->dev, "Failed to update mask reg: %d\n", + error); + return error; + } + } else { + dev_warn(&i2c->dev, "No IRQ configured\n"); + } + + i2c_set_clientdata(i2c, chip); + + return 0; +} + +static const struct i2c_device_id da9210_i2c_id[] = { + {"da9210", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, da9210_i2c_id); + +static struct i2c_driver da9210_regulator_driver = { + .driver = { + .name = "da9210", + .of_match_table = of_match_ptr(da9210_dt_ids), + }, + .probe = da9210_i2c_probe, + .id_table = da9210_i2c_id, +}; + +module_i2c_driver(da9210_regulator_driver); + +MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>"); +MODULE_DESCRIPTION("Regulator device driver for Dialog DA9210"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/da9210-regulator.h b/drivers/regulator/da9210-regulator.h new file mode 100644 index 000000000..749c55080 --- /dev/null +++ b/drivers/regulator/da9210-regulator.h @@ -0,0 +1,288 @@ + +/* + * da9210-regulator.h - Regulator definitions for DA9210 + * Copyright (C) 2013 Dialog Semiconductor Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __DA9210_REGISTERS_H__ +#define __DA9210_REGISTERS_H__ + +struct da9210_pdata { + struct regulator_init_data da9210_constraints; +}; + +/* Page selection */ +#define DA9210_REG_PAGE_CON 0x00 + +/* System Control and Event Registers */ +#define DA9210_REG_STATUS_A 0x50 +#define DA9210_REG_STATUS_B 0x51 +#define DA9210_REG_EVENT_A 0x52 +#define DA9210_REG_EVENT_B 0x53 +#define DA9210_REG_MASK_A 0x54 +#define DA9210_REG_MASK_B 0x55 +#define DA9210_REG_CONTROL_A 0x56 + +/* GPIO Control Registers */ +#define DA9210_REG_GPIO_0_1 0x58 +#define DA9210_REG_GPIO_2_3 0x59 +#define DA9210_REG_GPIO_4_5 0x5A +#define DA9210_REG_GPIO_6 0x5B + +/* Regulator Registers */ +#define DA9210_REG_BUCK_CONT 0x5D +#define DA9210_REG_BUCK_ILIM 0xD0 +#define DA9210_REG_BUCK_CONF1 0xD1 +#define DA9210_REG_BUCK_CONF2 0xD2 +#define DA9210_REG_VBACK_AUTO 0xD4 +#define DA9210_REG_VBACK_BASE 0xD5 +#define DA9210_REG_VBACK_MAX_DVC_IF 0xD6 +#define DA9210_REG_VBACK_DVC 0xD7 +#define DA9210_REG_VBUCK_A 0xD8 +#define DA9210_REG_VBUCK_B 0xD9 + +/* I2C Interface Settings */ +#define DA9210_REG_INTERFACE 0x105 + +/* OTP */ +#define DA9210_REG_OPT_COUNT 0x140 +#define DA9210_REG_OPT_ADDR 0x141 +#define DA9210_REG_OPT_DATA 0x142 + +/* Customer Trim and Configuration */ +#define DA9210_REG_CONFIG_A 0x143 +#define DA9210_REG_CONFIG_B 0x144 +#define DA9210_REG_CONFIG_C 0x145 +#define DA9210_REG_CONFIG_D 0x146 +#define DA9210_REG_CONFIG_E 0x147 + + +/* + * Registers bits + */ +/* DA9210_REG_PAGE_CON (addr=0x00) */ +#define DA9210_PEG_PAGE_SHIFT 0 +#define DA9210_REG_PAGE_MASK 0x0F +/* On I2C registers 0x00 - 0xFF */ +#define DA9210_REG_PAGE0 0 +/* On I2C registers 0x100 - 0x1FF */ +#define DA9210_REG_PAGE2 2 +#define DA9210_PAGE_WRITE_MODE 0x00 +#define DA9210_REPEAT_WRITE_MODE 0x40 +#define DA9210_PAGE_REVERT 0x80 + +/* DA9210_REG_STATUS_A (addr=0x50) */ +#define DA9210_GPI0 0x01 +#define DA9210_GPI1 0x02 +#define DA9210_GPI2 0x04 +#define DA9210_GPI3 0x08 +#define DA9210_GPI4 0x10 +#define DA9210_GPI5 0x20 +#define DA9210_GPI6 0x40 + +/* DA9210_REG_EVENT_A (addr=0x52) */ +#define DA9210_E_GPI0 0x01 +#define DA9210_E_GPI1 0x02 +#define DA9210_E_GPI2 0x04 +#define DA9210_E_GPI3 0x08 +#define DA9210_E_GPI4 0x10 +#define DA9210_E_GPI5 0x20 +#define DA9210_E_GPI6 0x40 + +/* DA9210_REG_EVENT_B (addr=0x53) */ +#define DA9210_E_OVCURR 0x01 +#define DA9210_E_NPWRGOOD 0x02 +#define DA9210_E_TEMP_WARN 0x04 +#define DA9210_E_TEMP_CRIT 0x08 +#define DA9210_E_VMAX 0x10 + +/* DA9210_REG_MASK_A (addr=0x54) */ +#define DA9210_M_GPI0 0x01 +#define DA9210_M_GPI1 0x02 +#define DA9210_M_GPI2 0x04 +#define DA9210_M_GPI3 0x08 +#define DA9210_M_GPI4 0x10 +#define DA9210_M_GPI5 0x20 +#define DA9210_M_GPI6 0x40 + +/* DA9210_REG_MASK_B (addr=0x55) */ +#define DA9210_M_OVCURR 0x01 +#define DA9210_M_NPWRGOOD 0x02 +#define DA9210_M_TEMP_WARN 0x04 +#define DA9210_M_TEMP_CRIT 0x08 +#define DA9210_M_VMAX 0x10 + +/* DA9210_REG_CONTROL_A (addr=0x56) */ +#define DA9210_DEBOUNCING_SHIFT 0 +#define DA9210_DEBOUNCING_MASK 0x07 +#define DA9210_SLEW_RATE_SHIFT 3 +#define DA9210_SLEW_RATE_MASK 0x18 +#define DA9210_V_LOCK 0x20 + +/* DA9210_REG_GPIO_0_1 (addr=0x58) */ +#define DA9210_GPIO0_PIN_SHIFT 0 +#define DA9210_GPIO0_PIN_MASK 0x03 +#define DA9210_GPIO0_PIN_GPI 0x00 +#define DA9210_GPIO0_PIN_GPO_OD 0x02 +#define DA9210_GPIO0_PIN_GPO 0x03 +#define DA9210_GPIO0_TYPE 0x04 +#define DA9210_GPIO0_TYPE_GPI 0x00 +#define DA9210_GPIO0_TYPE_GPO 0x04 +#define DA9210_GPIO0_MODE 0x08 +#define DA9210_GPIO1_PIN_SHIFT 4 +#define DA9210_GPIO1_PIN_MASK 0x30 +#define DA9210_GPIO1_PIN_GPI 0x00 +#define DA9210_GPIO1_PIN_VERROR 0x10 +#define DA9210_GPIO1_PIN_GPO_OD 0x20 +#define DA9210_GPIO1_PIN_GPO 0x30 +#define DA9210_GPIO1_TYPE_SHIFT 0x40 +#define DA9210_GPIO1_TYPE_GPI 0x00 +#define DA9210_GPIO1_TYPE_GPO 0x40 +#define DA9210_GPIO1_MODE 0x80 + +/* DA9210_REG_GPIO_2_3 (addr=0x59) */ +#define DA9210_GPIO2_PIN_SHIFT 0 +#define DA9210_GPIO2_PIN_MASK 0x03 +#define DA9210_GPIO2_PIN_GPI 0x00 +#define DA9210_GPIO5_PIN_BUCK_CLK 0x10 +#define DA9210_GPIO2_PIN_GPO_OD 0x02 +#define DA9210_GPIO2_PIN_GPO 0x03 +#define DA9210_GPIO2_TYPE 0x04 +#define DA9210_GPIO2_TYPE_GPI 0x00 +#define DA9210_GPIO2_TYPE_GPO 0x04 +#define DA9210_GPIO2_MODE 0x08 +#define DA9210_GPIO3_PIN_SHIFT 4 +#define DA9210_GPIO3_PIN_MASK 0x30 +#define DA9210_GPIO3_PIN_GPI 0x00 +#define DA9210_GPIO3_PIN_IERROR 0x10 +#define DA9210_GPIO3_PIN_GPO_OD 0x20 +#define DA9210_GPIO3_PIN_GPO 0x30 +#define DA9210_GPIO3_TYPE_SHIFT 0x40 +#define DA9210_GPIO3_TYPE_GPI 0x00 +#define DA9210_GPIO3_TYPE_GPO 0x40 +#define DA9210_GPIO3_MODE 0x80 + +/* DA9210_REG_GPIO_4_5 (addr=0x5A) */ +#define DA9210_GPIO4_PIN_SHIFT 0 +#define DA9210_GPIO4_PIN_MASK 0x03 +#define DA9210_GPIO4_PIN_GPI 0x00 +#define DA9210_GPIO4_PIN_GPO_OD 0x02 +#define DA9210_GPIO4_PIN_GPO 0x03 +#define DA9210_GPIO4_TYPE 0x04 +#define DA9210_GPIO4_TYPE_GPI 0x00 +#define DA9210_GPIO4_TYPE_GPO 0x04 +#define DA9210_GPIO4_MODE 0x08 +#define DA9210_GPIO5_PIN_SHIFT 4 +#define DA9210_GPIO5_PIN_MASK 0x30 +#define DA9210_GPIO5_PIN_GPI 0x00 +#define DA9210_GPIO5_PIN_INTERFACE 0x01 +#define DA9210_GPIO5_PIN_GPO_OD 0x20 +#define DA9210_GPIO5_PIN_GPO 0x30 +#define DA9210_GPIO5_TYPE_SHIFT 0x40 +#define DA9210_GPIO5_TYPE_GPI 0x00 +#define DA9210_GPIO5_TYPE_GPO 0x40 +#define DA9210_GPIO5_MODE 0x80 + +/* DA9210_REG_GPIO_6 (addr=0x5B) */ +#define DA9210_GPIO6_PIN_SHIFT 0 +#define DA9210_GPIO6_PIN_MASK 0x03 +#define DA9210_GPIO6_PIN_GPI 0x00 +#define DA9210_GPIO6_PIN_INTERFACE 0x01 +#define DA9210_GPIO6_PIN_GPO_OD 0x02 +#define DA9210_GPIO6_PIN_GPO 0x03 +#define DA9210_GPIO6_TYPE 0x04 +#define DA9210_GPIO6_TYPE_GPI 0x00 +#define DA9210_GPIO6_TYPE_GPO 0x04 +#define DA9210_GPIO6_MODE 0x08 + +/* DA9210_REG_BUCK_CONT (addr=0x5D) */ +#define DA9210_BUCK_EN 0x01 +#define DA9210_BUCK_GPI_SHIFT 1 +#define DA9210_BUCK_GPI_MASK 0x06 +#define DA9210_BUCK_GPI_OFF 0x00 +#define DA9210_BUCK_GPI_GPIO0 0x02 +#define DA9210_BUCK_GPI_GPIO3 0x04 +#define DA9210_BUCK_GPI_GPIO4 0x06 +#define DA9210_BUCK_PD_DIS 0x08 +#define DA9210_VBUCK_SEL 0x10 +#define DA9210_VBUCK_SEL_A 0x00 +#define DA9210_VBUCK_SEL_B 0x10 +#define DA9210_VBUCK_GPI_SHIFT 5 +#define DA9210_VBUCK_GPI_MASK 0x60 +#define DA9210_VBUCK_GPI_OFF 0x00 +#define DA9210_VBUCK_GPI_GPIO0 0x20 +#define DA9210_VBUCK_GPI_GPIO3 0x40 +#define DA9210_VBUCK_GPI_GPIO4 0x60 +#define DA9210_DVC_CTRL_EN 0x80 + +/* DA9210_REG_BUCK_ILIM (addr=0xD0) */ +#define DA9210_BUCK_ILIM_SHIFT 0 +#define DA9210_BUCK_ILIM_MASK 0x0F +#define DA9210_BUCK_IALARM 0x10 + +/* DA9210_REG_BUCK_CONF1 (addr=0xD1) */ +#define DA9210_BUCK_MODE_SHIFT 0 +#define DA9210_BUCK_MODE_MASK 0x03 +#define DA9210_BUCK_MODE_MANUAL 0x00 +#define DA9210_BUCK_MODE_SLEEP 0x01 +#define DA9210_BUCK_MODE_SYNC 0x02 +#define DA9210_BUCK_MODE_AUTO 0x03 +#define DA9210_STARTUP_CTRL_SHIFT 2 +#define DA9210_STARTUP_CTRL_MASK 0x1C +#define DA9210_PWR_DOWN_CTRL_SHIFT 5 +#define DA9210_PWR_DOWN_CTRL_MASK 0xE0 + +/* DA9210_REG_BUCK_CONF2 (addr=0xD2) */ +#define DA9210_PHASE_SEL_SHIFT 0 +#define DA9210_PHASE_SEL_MASK 0x03 +#define DA9210_FREQ_SEL 0x40 + +/* DA9210_REG_BUCK_AUTO (addr=0xD4) */ +#define DA9210_VBUCK_AUTO_SHIFT 0 +#define DA9210_VBUCK_AUTO_MASK 0x7F + +/* DA9210_REG_BUCK_BASE (addr=0xD5) */ +#define DA9210_VBUCK_BASE_SHIFT 0 +#define DA9210_VBUCK_BASE_MASK 0x7F + +/* DA9210_REG_VBUCK_MAX_DVC_IF (addr=0xD6) */ +#define DA9210_VBUCK_MAX_SHIFT 0 +#define DA9210_VBUCK_MAX_MASK 0x7F +#define DA9210_DVC_STEP_SIZE 0x80 +#define DA9210_DVC_STEP_SIZE_10MV 0x00 +#define DA9210_DVC_STEP_SIZE_20MV 0x80 + +/* DA9210_REG_VBUCK_DVC (addr=0xD7) */ +#define DA9210_VBUCK_DVC_SHIFT 0 +#define DA9210_VBUCK_DVC_MASK 0x7F + +/* DA9210_REG_VBUCK_A/B (addr=0xD8/0xD9) */ +#define DA9210_VBUCK_SHIFT 0 +#define DA9210_VBUCK_MASK 0x7F +#define DA9210_VBUCK_BIAS 0 +#define DA9210_BUCK_SL 0x80 + +/* DA9210_REG_INTERFACE (addr=0x105) */ +#define DA9210_IF_BASE_ADDR_SHIFT 4 +#define DA9210_IF_BASE_ADDR_MASK 0xF0 + +/* DA9210_REG_CONFIG_E (addr=0x147) */ +#define DA9210_STAND_ALONE 0x01 + +#endif /* __DA9210_REGISTERS_H__ */ + diff --git a/drivers/regulator/da9211-regulator.c b/drivers/regulator/da9211-regulator.c new file mode 100644 index 000000000..6c122b3df --- /dev/null +++ b/drivers/regulator/da9211-regulator.c @@ -0,0 +1,535 @@ +/* + * da9211-regulator.c - Regulator device driver for DA9211/DA9212 + * /DA9213/DA9223/DA9214/DA9224/DA9215/DA9225 + * Copyright (C) 2015 Dialog Semiconductor Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regmap.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/da9211.h> +#include "da9211-regulator.h" + +/* DEVICE IDs */ +#define DA9211_DEVICE_ID 0x22 +#define DA9213_DEVICE_ID 0x23 +#define DA9215_DEVICE_ID 0x24 + +#define DA9211_BUCK_MODE_SLEEP 1 +#define DA9211_BUCK_MODE_SYNC 2 +#define DA9211_BUCK_MODE_AUTO 3 + +/* DA9211 REGULATOR IDs */ +#define DA9211_ID_BUCKA 0 +#define DA9211_ID_BUCKB 1 + +struct da9211 { + struct device *dev; + struct regmap *regmap; + struct da9211_pdata *pdata; + struct regulator_dev *rdev[DA9211_MAX_REGULATORS]; + int num_regulator; + int chip_irq; + int chip_id; +}; + +static const struct regmap_range_cfg da9211_regmap_range[] = { + { + .selector_reg = DA9211_REG_PAGE_CON, + .selector_mask = DA9211_REG_PAGE_MASK, + .selector_shift = DA9211_REG_PAGE_SHIFT, + .window_start = 0, + .window_len = 256, + .range_min = 0, + .range_max = 5*128, + }, +}; + +static const struct regmap_config da9211_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 5 * 128, + .ranges = da9211_regmap_range, + .num_ranges = ARRAY_SIZE(da9211_regmap_range), +}; + +/* Default limits measured in millivolts and milliamps */ +#define DA9211_MIN_MV 300 +#define DA9211_MAX_MV 1570 +#define DA9211_STEP_MV 10 + +/* Current limits for DA9211 buck (uA) indices + * corresponds with register values + */ +static const int da9211_current_limits[] = { + 2000000, 2200000, 2400000, 2600000, 2800000, 3000000, 3200000, 3400000, + 3600000, 3800000, 4000000, 4200000, 4400000, 4600000, 4800000, 5000000 +}; +/* Current limits for DA9213 buck (uA) indices + * corresponds with register values + */ +static const int da9213_current_limits[] = { + 3000000, 3200000, 3400000, 3600000, 3800000, 4000000, 4200000, 4400000, + 4600000, 4800000, 5000000, 5200000, 5400000, 5600000, 5800000, 6000000 +}; +/* Current limits for DA9215 buck (uA) indices + * corresponds with register values + */ +static const int da9215_current_limits[] = { + 4000000, 4200000, 4400000, 4600000, 4800000, 5000000, 5200000, 5400000, + 5600000, 5800000, 6000000, 6200000, 6400000, 6600000, 6800000, 7000000 +}; + +static unsigned int da9211_buck_get_mode(struct regulator_dev *rdev) +{ + int id = rdev_get_id(rdev); + struct da9211 *chip = rdev_get_drvdata(rdev); + unsigned int data; + int ret, mode = 0; + + ret = regmap_read(chip->regmap, DA9211_REG_BUCKA_CONF+id, &data); + if (ret < 0) + return ret; + + switch (data & 0x03) { + case DA9211_BUCK_MODE_SYNC: + mode = REGULATOR_MODE_FAST; + break; + case DA9211_BUCK_MODE_AUTO: + mode = REGULATOR_MODE_NORMAL; + break; + case DA9211_BUCK_MODE_SLEEP: + mode = REGULATOR_MODE_STANDBY; + break; + } + + return mode; +} + +static int da9211_buck_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + int id = rdev_get_id(rdev); + struct da9211 *chip = rdev_get_drvdata(rdev); + int val = 0; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = DA9211_BUCK_MODE_SYNC; + break; + case REGULATOR_MODE_NORMAL: + val = DA9211_BUCK_MODE_AUTO; + break; + case REGULATOR_MODE_STANDBY: + val = DA9211_BUCK_MODE_SLEEP; + break; + } + + return regmap_update_bits(chip->regmap, DA9211_REG_BUCKA_CONF+id, + 0x03, val); +} + +static int da9211_set_current_limit(struct regulator_dev *rdev, int min, + int max) +{ + int id = rdev_get_id(rdev); + struct da9211 *chip = rdev_get_drvdata(rdev); + int i, max_size; + const int *current_limits; + + switch (chip->chip_id) { + case DA9211: + current_limits = da9211_current_limits; + max_size = ARRAY_SIZE(da9211_current_limits)-1; + break; + case DA9213: + current_limits = da9213_current_limits; + max_size = ARRAY_SIZE(da9213_current_limits)-1; + break; + case DA9215: + current_limits = da9215_current_limits; + max_size = ARRAY_SIZE(da9215_current_limits)-1; + break; + default: + return -EINVAL; + } + + /* search for closest to maximum */ + for (i = max_size; i >= 0; i--) { + if (min <= current_limits[i] && + max >= current_limits[i]) { + return regmap_update_bits(chip->regmap, + DA9211_REG_BUCK_ILIM, + (0x0F << id*4), (i << id*4)); + } + } + + return -EINVAL; +} + +static int da9211_get_current_limit(struct regulator_dev *rdev) +{ + int id = rdev_get_id(rdev); + struct da9211 *chip = rdev_get_drvdata(rdev); + unsigned int data; + int ret; + const int *current_limits; + + switch (chip->chip_id) { + case DA9211: + current_limits = da9211_current_limits; + break; + case DA9213: + current_limits = da9213_current_limits; + break; + case DA9215: + current_limits = da9215_current_limits; + break; + default: + return -EINVAL; + } + + ret = regmap_read(chip->regmap, DA9211_REG_BUCK_ILIM, &data); + if (ret < 0) + return ret; + + /* select one of 16 values: 0000 (2000mA or 3000mA) + * to 1111 (5000mA or 6000mA). + */ + data = (data >> id*4) & 0x0F; + return current_limits[data]; +} + +static const struct regulator_ops da9211_buck_ops = { + .get_mode = da9211_buck_get_mode, + .set_mode = da9211_buck_set_mode, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_current_limit = da9211_set_current_limit, + .get_current_limit = da9211_get_current_limit, +}; + +#define DA9211_BUCK(_id) \ +{\ + .name = #_id,\ + .ops = &da9211_buck_ops,\ + .type = REGULATOR_VOLTAGE,\ + .id = DA9211_ID_##_id,\ + .n_voltages = (DA9211_MAX_MV - DA9211_MIN_MV) / DA9211_STEP_MV + 1,\ + .min_uV = (DA9211_MIN_MV * 1000),\ + .uV_step = (DA9211_STEP_MV * 1000),\ + .enable_reg = DA9211_REG_BUCKA_CONT + DA9211_ID_##_id,\ + .enable_mask = DA9211_BUCKA_EN,\ + .vsel_reg = DA9211_REG_VBUCKA_A + DA9211_ID_##_id * 2,\ + .vsel_mask = DA9211_VBUCK_MASK,\ + .owner = THIS_MODULE,\ +} + +static struct regulator_desc da9211_regulators[] = { + DA9211_BUCK(BUCKA), + DA9211_BUCK(BUCKB), +}; + +#ifdef CONFIG_OF +static struct of_regulator_match da9211_matches[] = { + [DA9211_ID_BUCKA] = { .name = "BUCKA" }, + [DA9211_ID_BUCKB] = { .name = "BUCKB" }, + }; + +static struct da9211_pdata *da9211_parse_regulators_dt( + struct device *dev) +{ + struct da9211_pdata *pdata; + struct device_node *node; + int i, num, n; + + node = of_get_child_by_name(dev->of_node, "regulators"); + if (!node) { + dev_err(dev, "regulators node not found\n"); + return ERR_PTR(-ENODEV); + } + + num = of_regulator_match(dev, node, da9211_matches, + ARRAY_SIZE(da9211_matches)); + of_node_put(node); + if (num < 0) { + dev_err(dev, "Failed to match regulators\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->num_buck = num; + + n = 0; + for (i = 0; i < ARRAY_SIZE(da9211_matches); i++) { + if (!da9211_matches[i].init_data) + continue; + + pdata->init_data[n] = da9211_matches[i].init_data; + pdata->reg_node[n] = da9211_matches[i].of_node; + pdata->gpiod_ren[n] = devm_gpiod_get_from_of_node(dev, + da9211_matches[i].of_node, + "enable", + 0, + GPIOD_OUT_HIGH, + "da9211-enable"); + n++; + } + + return pdata; +} +#else +static struct da9211_pdata *da9211_parse_regulators_dt( + struct device *dev) +{ + return ERR_PTR(-ENODEV); +} +#endif + +static irqreturn_t da9211_irq_handler(int irq, void *data) +{ + struct da9211 *chip = data; + int reg_val, err, ret = IRQ_NONE; + + err = regmap_read(chip->regmap, DA9211_REG_EVENT_B, ®_val); + if (err < 0) + goto error_i2c; + + if (reg_val & DA9211_E_OV_CURR_A) { + regulator_notifier_call_chain(chip->rdev[0], + REGULATOR_EVENT_OVER_CURRENT, NULL); + + err = regmap_write(chip->regmap, DA9211_REG_EVENT_B, + DA9211_E_OV_CURR_A); + if (err < 0) + goto error_i2c; + + ret = IRQ_HANDLED; + } + + if (reg_val & DA9211_E_OV_CURR_B) { + regulator_notifier_call_chain(chip->rdev[1], + REGULATOR_EVENT_OVER_CURRENT, NULL); + + err = regmap_write(chip->regmap, DA9211_REG_EVENT_B, + DA9211_E_OV_CURR_B); + if (err < 0) + goto error_i2c; + + ret = IRQ_HANDLED; + } + + return ret; + +error_i2c: + dev_err(chip->dev, "I2C error : %d\n", err); + return IRQ_NONE; +} + +static int da9211_regulator_init(struct da9211 *chip) +{ + struct regulator_config config = { }; + int i, ret; + unsigned int data; + + ret = regmap_read(chip->regmap, DA9211_REG_CONFIG_E, &data); + if (ret < 0) { + dev_err(chip->dev, "Failed to read CONFIG_E reg: %d\n", ret); + return ret; + } + + data &= DA9211_SLAVE_SEL; + /* If configuration for 1/2 bucks is different between platform data + * and the register, driver should exit. + */ + if (chip->pdata->num_buck == 1 && data == 0x00) + chip->num_regulator = 1; + else if (chip->pdata->num_buck == 2 && data != 0x00) + chip->num_regulator = 2; + else { + dev_err(chip->dev, "Configuration is mismatched\n"); + return -EINVAL; + } + + for (i = 0; i < chip->num_regulator; i++) { + config.init_data = chip->pdata->init_data[i]; + config.dev = chip->dev; + config.driver_data = chip; + config.regmap = chip->regmap; + config.of_node = chip->pdata->reg_node[i]; + + if (chip->pdata->gpiod_ren[i]) + config.ena_gpiod = chip->pdata->gpiod_ren[i]; + else + config.ena_gpiod = NULL; + + chip->rdev[i] = devm_regulator_register(chip->dev, + &da9211_regulators[i], &config); + if (IS_ERR(chip->rdev[i])) { + dev_err(chip->dev, + "Failed to register DA9211 regulator\n"); + return PTR_ERR(chip->rdev[i]); + } + + if (chip->chip_irq != 0) { + ret = regmap_update_bits(chip->regmap, + DA9211_REG_MASK_B, DA9211_M_OV_CURR_A << i, 0); + if (ret < 0) { + dev_err(chip->dev, + "Failed to update mask reg: %d\n", ret); + return ret; + } + } + } + + return 0; +} + +/* + * I2C driver interface functions + */ +static int da9211_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da9211 *chip; + int error, ret; + unsigned int data; + + chip = devm_kzalloc(&i2c->dev, sizeof(struct da9211), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &i2c->dev; + chip->regmap = devm_regmap_init_i2c(i2c, &da9211_regmap_config); + if (IS_ERR(chip->regmap)) { + error = PTR_ERR(chip->regmap); + dev_err(chip->dev, "Failed to allocate register map: %d\n", + error); + return error; + } + + i2c_set_clientdata(i2c, chip); + + chip->pdata = i2c->dev.platform_data; + + ret = regmap_read(chip->regmap, DA9211_REG_DEVICE_ID, &data); + if (ret < 0) { + dev_err(chip->dev, "Failed to read DEVICE_ID reg: %d\n", ret); + return ret; + } + + switch (data) { + case DA9211_DEVICE_ID: + chip->chip_id = DA9211; + break; + case DA9213_DEVICE_ID: + chip->chip_id = DA9213; + break; + case DA9215_DEVICE_ID: + chip->chip_id = DA9215; + break; + default: + dev_err(chip->dev, "Unsupported device id = 0x%x.\n", data); + return -ENODEV; + } + + if (!chip->pdata) + chip->pdata = da9211_parse_regulators_dt(chip->dev); + + if (IS_ERR(chip->pdata)) { + dev_err(chip->dev, "No regulators defined for the platform\n"); + return PTR_ERR(chip->pdata); + } + + chip->chip_irq = i2c->irq; + + if (chip->chip_irq != 0) { + ret = devm_request_threaded_irq(chip->dev, chip->chip_irq, NULL, + da9211_irq_handler, + IRQF_TRIGGER_LOW|IRQF_ONESHOT, + "da9211", chip); + if (ret != 0) { + dev_err(chip->dev, "Failed to request IRQ: %d\n", + chip->chip_irq); + return ret; + } + } else { + dev_warn(chip->dev, "No IRQ configured\n"); + } + + ret = da9211_regulator_init(chip); + + if (ret < 0) + dev_err(chip->dev, "Failed to initialize regulator: %d\n", ret); + + return ret; +} + +static const struct i2c_device_id da9211_i2c_id[] = { + {"da9211", DA9211}, + {"da9212", DA9212}, + {"da9213", DA9213}, + {"da9223", DA9223}, + {"da9214", DA9214}, + {"da9224", DA9224}, + {"da9215", DA9215}, + {"da9225", DA9225}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, da9211_i2c_id); + +#ifdef CONFIG_OF +static const struct of_device_id da9211_dt_ids[] = { + { .compatible = "dlg,da9211", .data = &da9211_i2c_id[0] }, + { .compatible = "dlg,da9212", .data = &da9211_i2c_id[1] }, + { .compatible = "dlg,da9213", .data = &da9211_i2c_id[2] }, + { .compatible = "dlg,da9223", .data = &da9211_i2c_id[3] }, + { .compatible = "dlg,da9214", .data = &da9211_i2c_id[4] }, + { .compatible = "dlg,da9224", .data = &da9211_i2c_id[5] }, + { .compatible = "dlg,da9215", .data = &da9211_i2c_id[6] }, + { .compatible = "dlg,da9225", .data = &da9211_i2c_id[7] }, + {}, +}; +MODULE_DEVICE_TABLE(of, da9211_dt_ids); +#endif + +static struct i2c_driver da9211_regulator_driver = { + .driver = { + .name = "da9211", + .of_match_table = of_match_ptr(da9211_dt_ids), + }, + .probe = da9211_i2c_probe, + .id_table = da9211_i2c_id, +}; + +module_i2c_driver(da9211_regulator_driver); + +MODULE_AUTHOR("James Ban <James.Ban.opensource@diasemi.com>"); +MODULE_DESCRIPTION("DA9211/DA9212/DA9213/DA9223/DA9214/DA9224/DA9215/DA9225 regulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/da9211-regulator.h b/drivers/regulator/da9211-regulator.h new file mode 100644 index 000000000..2cb32aab4 --- /dev/null +++ b/drivers/regulator/da9211-regulator.h @@ -0,0 +1,275 @@ +/* + * da9211-regulator.h - Regulator definitions for DA9211/DA9212 + * /DA9213/DA9223/DA9214/DA9224/DA9215/DA9225 + * Copyright (C) 2015 Dialog Semiconductor Ltd. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __DA9211_REGISTERS_H__ +#define __DA9211_REGISTERS_H__ + +/* Page selection */ +#define DA9211_REG_PAGE_CON 0x00 + +/* System Control and Event Registers */ +#define DA9211_REG_STATUS_A 0x50 +#define DA9211_REG_STATUS_B 0x51 +#define DA9211_REG_EVENT_A 0x52 +#define DA9211_REG_EVENT_B 0x53 +#define DA9211_REG_MASK_A 0x54 +#define DA9211_REG_MASK_B 0x55 +#define DA9211_REG_CONTROL_A 0x56 + +/* GPIO Control Registers */ +#define DA9211_REG_GPIO_0_1 0x58 +#define DA9211_REG_GPIO_2_3 0x59 +#define DA9211_REG_GPIO_4 0x5A + +/* Regulator Registers */ +#define DA9211_REG_BUCKA_CONT 0x5D +#define DA9211_REG_BUCKB_CONT 0x5E +#define DA9211_REG_BUCK_ILIM 0xD0 +#define DA9211_REG_BUCKA_CONF 0xD1 +#define DA9211_REG_BUCKB_CONF 0xD2 +#define DA9211_REG_BUCK_CONF 0xD3 +#define DA9211_REG_VBACKA_MAX 0xD5 +#define DA9211_REG_VBACKB_MAX 0xD6 +#define DA9211_REG_VBUCKA_A 0xD7 +#define DA9211_REG_VBUCKA_B 0xD8 +#define DA9211_REG_VBUCKB_A 0xD9 +#define DA9211_REG_VBUCKB_B 0xDA + +/* I2C Interface Settings */ +#define DA9211_REG_INTERFACE 0x105 + +/* BUCK Phase Selection*/ +#define DA9211_REG_CONFIG_E 0x147 + +/* Device ID */ +#define DA9211_REG_DEVICE_ID 0x201 + +/* + * Registers bits + */ +/* DA9211_REG_PAGE_CON (addr=0x00) */ +#define DA9211_REG_PAGE_SHIFT 1 +#define DA9211_REG_PAGE_MASK 0x06 +/* On I2C registers 0x00 - 0xFF */ +#define DA9211_REG_PAGE0 0 +/* On I2C registers 0x100 - 0x1FF */ +#define DA9211_REG_PAGE2 2 +#define DA9211_PAGE_WRITE_MODE 0x00 +#define DA9211_REPEAT_WRITE_MODE 0x40 +#define DA9211_PAGE_REVERT 0x80 + +/* DA9211_REG_STATUS_A (addr=0x50) */ +#define DA9211_GPI0 0x01 +#define DA9211_GPI1 0x02 +#define DA9211_GPI2 0x04 +#define DA9211_GPI3 0x08 +#define DA9211_GPI4 0x10 + +/* DA9211_REG_EVENT_A (addr=0x52) */ +#define DA9211_E_GPI0 0x01 +#define DA9211_E_GPI1 0x02 +#define DA9211_E_GPI2 0x04 +#define DA9211_E_GPI3 0x08 +#define DA9211_E_GPI4 0x10 +#define DA9211_E_UVLO_IO 0x40 + +/* DA9211_REG_EVENT_B (addr=0x53) */ +#define DA9211_E_PWRGOOD_A 0x01 +#define DA9211_E_PWRGOOD_B 0x02 +#define DA9211_E_TEMP_WARN 0x04 +#define DA9211_E_TEMP_CRIT 0x08 +#define DA9211_E_OV_CURR_A 0x10 +#define DA9211_E_OV_CURR_B 0x20 + +/* DA9211_REG_MASK_A (addr=0x54) */ +#define DA9211_M_GPI0 0x01 +#define DA9211_M_GPI1 0x02 +#define DA9211_M_GPI2 0x04 +#define DA9211_M_GPI3 0x08 +#define DA9211_M_GPI4 0x10 +#define DA9211_M_UVLO_IO 0x40 + +/* DA9211_REG_MASK_B (addr=0x55) */ +#define DA9211_M_PWRGOOD_A 0x01 +#define DA9211_M_PWRGOOD_B 0x02 +#define DA9211_M_TEMP_WARN 0x04 +#define DA9211_M_TEMP_CRIT 0x08 +#define DA9211_M_OV_CURR_A 0x10 +#define DA9211_M_OV_CURR_B 0x20 + +/* DA9211_REG_CONTROL_A (addr=0x56) */ +#define DA9211_DEBOUNCING_SHIFT 0 +#define DA9211_DEBOUNCING_MASK 0x07 +#define DA9211_SLEW_RATE_SHIFT 3 +#define DA9211_SLEW_RATE_A_MASK 0x18 +#define DA9211_SLEW_RATE_B_SHIFT 5 +#define DA9211_SLEW_RATE_B_MASK 0x60 +#define DA9211_V_LOCK 0x80 + +/* DA9211_REG_GPIO_0_1 (addr=0x58) */ +#define DA9211_GPIO0_PIN_SHIFT 0 +#define DA9211_GPIO0_PIN_MASK 0x03 +#define DA9211_GPIO0_PIN_GPI 0x00 +#define DA9211_GPIO0_PIN_GPO_OD 0x02 +#define DA9211_GPIO0_PIN_GPO 0x03 +#define DA9211_GPIO0_TYPE 0x04 +#define DA9211_GPIO0_TYPE_GPI 0x00 +#define DA9211_GPIO0_TYPE_GPO 0x04 +#define DA9211_GPIO0_MODE 0x08 +#define DA9211_GPIO1_PIN_SHIFT 4 +#define DA9211_GPIO1_PIN_MASK 0x30 +#define DA9211_GPIO1_PIN_GPI 0x00 +#define DA9211_GPIO1_PIN_VERROR 0x10 +#define DA9211_GPIO1_PIN_GPO_OD 0x20 +#define DA9211_GPIO1_PIN_GPO 0x30 +#define DA9211_GPIO1_TYPE_SHIFT 0x40 +#define DA9211_GPIO1_TYPE_GPI 0x00 +#define DA9211_GPIO1_TYPE_GPO 0x40 +#define DA9211_GPIO1_MODE 0x80 + +/* DA9211_REG_GPIO_2_3 (addr=0x59) */ +#define DA9211_GPIO2_PIN_SHIFT 0 +#define DA9211_GPIO2_PIN_MASK 0x03 +#define DA9211_GPIO2_PIN_GPI 0x00 +#define DA9211_GPIO5_PIN_BUCK_CLK 0x10 +#define DA9211_GPIO2_PIN_GPO_OD 0x02 +#define DA9211_GPIO2_PIN_GPO 0x03 +#define DA9211_GPIO2_TYPE 0x04 +#define DA9211_GPIO2_TYPE_GPI 0x00 +#define DA9211_GPIO2_TYPE_GPO 0x04 +#define DA9211_GPIO2_MODE 0x08 +#define DA9211_GPIO3_PIN_SHIFT 4 +#define DA9211_GPIO3_PIN_MASK 0x30 +#define DA9211_GPIO3_PIN_GPI 0x00 +#define DA9211_GPIO3_PIN_IERROR 0x10 +#define DA9211_GPIO3_PIN_GPO_OD 0x20 +#define DA9211_GPIO3_PIN_GPO 0x30 +#define DA9211_GPIO3_TYPE_SHIFT 0x40 +#define DA9211_GPIO3_TYPE_GPI 0x00 +#define DA9211_GPIO3_TYPE_GPO 0x40 +#define DA9211_GPIO3_MODE 0x80 + +/* DA9211_REG_GPIO_4 (addr=0x5A) */ +#define DA9211_GPIO4_PIN_SHIFT 0 +#define DA9211_GPIO4_PIN_MASK 0x03 +#define DA9211_GPIO4_PIN_GPI 0x00 +#define DA9211_GPIO4_PIN_GPO_OD 0x02 +#define DA9211_GPIO4_PIN_GPO 0x03 +#define DA9211_GPIO4_TYPE 0x04 +#define DA9211_GPIO4_TYPE_GPI 0x00 +#define DA9211_GPIO4_TYPE_GPO 0x04 +#define DA9211_GPIO4_MODE 0x08 + +/* DA9211_REG_BUCKA_CONT (addr=0x5D) */ +#define DA9211_BUCKA_EN 0x01 +#define DA9211_BUCKA_GPI_SHIFT 1 +#define DA9211_BUCKA_GPI_MASK 0x06 +#define DA9211_BUCKA_GPI_OFF 0x00 +#define DA9211_BUCKA_GPI_GPIO0 0x02 +#define DA9211_BUCKA_GPI_GPIO1 0x04 +#define DA9211_BUCKA_GPI_GPIO3 0x06 +#define DA9211_BUCKA_PD_DIS 0x08 +#define DA9211_VBUCKA_SEL 0x10 +#define DA9211_VBUCKA_SEL_A 0x00 +#define DA9211_VBUCKA_SEL_B 0x10 +#define DA9211_VBUCKA_GPI_SHIFT 5 +#define DA9211_VBUCKA_GPI_MASK 0x60 +#define DA9211_VBUCKA_GPI_OFF 0x00 +#define DA9211_VBUCKA_GPI_GPIO1 0x20 +#define DA9211_VBUCKA_GPI_GPIO2 0x40 +#define DA9211_VBUCKA_GPI_GPIO4 0x60 + +/* DA9211_REG_BUCKB_CONT (addr=0x5E) */ +#define DA9211_BUCKB_EN 0x01 +#define DA9211_BUCKB_GPI_SHIFT 1 +#define DA9211_BUCKB_GPI_MASK 0x06 +#define DA9211_BUCKB_GPI_OFF 0x00 +#define DA9211_BUCKB_GPI_GPIO0 0x02 +#define DA9211_BUCKB_GPI_GPIO1 0x04 +#define DA9211_BUCKB_GPI_GPIO3 0x06 +#define DA9211_BUCKB_PD_DIS 0x08 +#define DA9211_VBUCKB_SEL 0x10 +#define DA9211_VBUCKB_SEL_A 0x00 +#define DA9211_VBUCKB_SEL_B 0x10 +#define DA9211_VBUCKB_GPI_SHIFT 5 +#define DA9211_VBUCKB_GPI_MASK 0x60 +#define DA9211_VBUCKB_GPI_OFF 0x00 +#define DA9211_VBUCKB_GPI_GPIO1 0x20 +#define DA9211_VBUCKB_GPI_GPIO2 0x40 +#define DA9211_VBUCKB_GPI_GPIO4 0x60 + +/* DA9211_REG_BUCK_ILIM (addr=0xD0) */ +#define DA9211_BUCKA_ILIM_SHIFT 0 +#define DA9211_BUCKA_ILIM_MASK 0x0F +#define DA9211_BUCKB_ILIM_SHIFT 4 +#define DA9211_BUCKB_ILIM_MASK 0xF0 + +/* DA9211_REG_BUCKA_CONF (addr=0xD1) */ +#define DA9211_BUCKA_MODE_SHIFT 0 +#define DA9211_BUCKA_MODE_MASK 0x03 +#define DA9211_BUCKA_MODE_MANUAL 0x00 +#define DA9211_BUCKA_MODE_SLEEP 0x01 +#define DA9211_BUCKA_MODE_SYNC 0x02 +#define DA9211_BUCKA_MODE_AUTO 0x03 +#define DA9211_BUCKA_UP_CTRL_SHIFT 2 +#define DA9211_BUCKA_UP_CTRL_MASK 0x1C +#define DA9211_BUCKA_DOWN_CTRL_SHIFT 5 +#define DA9211_BUCKA_DOWN_CTRL_MASK 0xE0 + +/* DA9211_REG_BUCKB_CONF (addr=0xD2) */ +#define DA9211_BUCKB_MODE_SHIFT 0 +#define DA9211_BUCKB_MODE_MASK 0x03 +#define DA9211_BUCKB_MODE_MANUAL 0x00 +#define DA9211_BUCKB_MODE_SLEEP 0x01 +#define DA9211_BUCKB_MODE_SYNC 0x02 +#define DA9211_BUCKB_MODE_AUTO 0x03 +#define DA9211_BUCKB_UP_CTRL_SHIFT 2 +#define DA9211_BUCKB_UP_CTRL_MASK 0x1C +#define DA9211_BUCKB_DOWN_CTRL_SHIFT 5 +#define DA9211_BUCKB_DOWN_CTRL_MASK 0xE0 + +/* DA9211_REG_BUCK_CONF (addr=0xD3) */ +#define DA9211_PHASE_SEL_A_SHIFT 0 +#define DA9211_PHASE_SEL_A_MASK 0x03 +#define DA9211_PHASE_SEL_B_SHIFT 2 +#define DA9211_PHASE_SEL_B_MASK 0x04 +#define DA9211_PH_SH_EN_A_SHIFT 3 +#define DA9211_PH_SH_EN_A_MASK 0x08 +#define DA9211_PH_SH_EN_B_SHIFT 4 +#define DA9211_PH_SH_EN_B_MASK 0x10 + +/* DA9211_REG_VBUCKA_MAX (addr=0xD5) */ +#define DA9211_VBUCKA_BASE_SHIFT 0 +#define DA9211_VBUCKA_BASE_MASK 0x7F + +/* DA9211_REG_VBUCKB_MAX (addr=0xD6) */ +#define DA9211_VBUCKB_BASE_SHIFT 0 +#define DA9211_VBUCKB_BASE_MASK 0x7F + +/* DA9211_REG_VBUCKA/B_A/B (addr=0xD7/0xD8/0xD9/0xDA) */ +#define DA9211_VBUCK_SHIFT 0 +#define DA9211_VBUCK_MASK 0x7F +#define DA9211_VBUCK_BIAS 0 +#define DA9211_BUCK_SL 0x80 + +/* DA9211_REG_INTERFACE (addr=0x105) */ +#define DA9211_IF_BASE_ADDR_SHIFT 4 +#define DA9211_IF_BASE_ADDR_MASK 0xF0 + +/* DA9211_REG_CONFIG_E (addr=0x147) */ +#define DA9211_SLAVE_SEL 0x40 + +#endif /* __DA9211_REGISTERS_H__ */ diff --git a/drivers/regulator/db8500-prcmu.c b/drivers/regulator/db8500-prcmu.c new file mode 100644 index 000000000..7cec535cf --- /dev/null +++ b/drivers/regulator/db8500-prcmu.c @@ -0,0 +1,557 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Authors: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + * Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + * + * Power domain regulators on DB8500 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/db8500-prcmu.h> +#include <linux/regulator/of_regulator.h> +#include <linux/of.h> +#include <linux/module.h> +#include "dbx500-prcmu.h" + +static int db8500_regulator_enable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-enable\n", + info->desc.name); + + if (!info->is_enabled) { + info->is_enabled = true; + if (!info->exclude_from_power_state) + power_state_active_enable(); + } + + return 0; +} + +static int db8500_regulator_disable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + int ret = 0; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-disable\n", + info->desc.name); + + if (info->is_enabled) { + info->is_enabled = false; + if (!info->exclude_from_power_state) + ret = power_state_active_disable(); + } + + return ret; +} + +static int db8500_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-is_enabled (is_enabled):" + " %i\n", info->desc.name, info->is_enabled); + + return info->is_enabled; +} + +/* db8500 regulator operations */ +static struct regulator_ops db8500_regulator_ops = { + .enable = db8500_regulator_enable, + .disable = db8500_regulator_disable, + .is_enabled = db8500_regulator_is_enabled, +}; + +/* + * EPOD control + */ +static bool epod_on[NUM_EPOD_ID]; +static bool epod_ramret[NUM_EPOD_ID]; + +static int enable_epod(u16 epod_id, bool ramret) +{ + int ret; + + if (ramret) { + if (!epod_on[epod_id]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); + if (ret < 0) + return ret; + } + epod_ramret[epod_id] = true; + } else { + ret = prcmu_set_epod(epod_id, EPOD_STATE_ON); + if (ret < 0) + return ret; + epod_on[epod_id] = true; + } + + return 0; +} + +static int disable_epod(u16 epod_id, bool ramret) +{ + int ret; + + if (ramret) { + if (!epod_on[epod_id]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_OFF); + if (ret < 0) + return ret; + } + epod_ramret[epod_id] = false; + } else { + if (epod_ramret[epod_id]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); + if (ret < 0) + return ret; + } else { + ret = prcmu_set_epod(epod_id, EPOD_STATE_OFF); + if (ret < 0) + return ret; + } + epod_on[epod_id] = false; + } + + return 0; +} + +/* + * Regulator switch + */ +static int db8500_regulator_switch_enable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-switch-%s-enable\n", + info->desc.name); + + ret = enable_epod(info->epod_id, info->is_ramret); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "regulator-switch-%s-enable: prcmu call failed\n", + info->desc.name); + goto out; + } + + info->is_enabled = true; +out: + return ret; +} + +static int db8500_regulator_switch_disable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-switch-%s-disable\n", + info->desc.name); + + ret = disable_epod(info->epod_id, info->is_ramret); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "regulator_switch-%s-disable: prcmu call failed\n", + info->desc.name); + goto out; + } + + info->is_enabled = 0; +out: + return ret; +} + +static int db8500_regulator_switch_is_enabled(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), + "regulator-switch-%s-is_enabled (is_enabled): %i\n", + info->desc.name, info->is_enabled); + + return info->is_enabled; +} + +static struct regulator_ops db8500_regulator_switch_ops = { + .enable = db8500_regulator_switch_enable, + .disable = db8500_regulator_switch_disable, + .is_enabled = db8500_regulator_switch_is_enabled, +}; + +/* + * Regulator information + */ +static struct dbx500_regulator_info +dbx500_regulator_info[DB8500_NUM_REGULATORS] = { + [DB8500_REGULATOR_VAPE] = { + .desc = { + .name = "db8500-vape", + .id = DB8500_REGULATOR_VAPE, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VARM] = { + .desc = { + .name = "db8500-varm", + .id = DB8500_REGULATOR_VARM, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VMODEM] = { + .desc = { + .name = "db8500-vmodem", + .id = DB8500_REGULATOR_VMODEM, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VPLL] = { + .desc = { + .name = "db8500-vpll", + .id = DB8500_REGULATOR_VPLL, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VSMPS1] = { + .desc = { + .name = "db8500-vsmps1", + .id = DB8500_REGULATOR_VSMPS1, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VSMPS2] = { + .desc = { + .name = "db8500-vsmps2", + .id = DB8500_REGULATOR_VSMPS2, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .fixed_uV = 1800000, + .n_voltages = 1, + }, + .exclude_from_power_state = true, + }, + [DB8500_REGULATOR_VSMPS3] = { + .desc = { + .name = "db8500-vsmps3", + .id = DB8500_REGULATOR_VSMPS3, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VRF1] = { + .desc = { + .name = "db8500-vrf1", + .id = DB8500_REGULATOR_VRF1, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_SWITCH_SVAMMDSP] = { + .desc = { + .name = "db8500-sva-mmdsp", + .id = DB8500_REGULATOR_SWITCH_SVAMMDSP, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SVAMMDSP, + }, + [DB8500_REGULATOR_SWITCH_SVAMMDSPRET] = { + .desc = { + .name = "db8500-sva-mmdsp-ret", + .id = DB8500_REGULATOR_SWITCH_SVAMMDSPRET, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SVAMMDSP, + .is_ramret = true, + }, + [DB8500_REGULATOR_SWITCH_SVAPIPE] = { + .desc = { + .name = "db8500-sva-pipe", + .id = DB8500_REGULATOR_SWITCH_SVAPIPE, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SVAPIPE, + }, + [DB8500_REGULATOR_SWITCH_SIAMMDSP] = { + .desc = { + .name = "db8500-sia-mmdsp", + .id = DB8500_REGULATOR_SWITCH_SIAMMDSP, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SIAMMDSP, + }, + [DB8500_REGULATOR_SWITCH_SIAMMDSPRET] = { + .desc = { + .name = "db8500-sia-mmdsp-ret", + .id = DB8500_REGULATOR_SWITCH_SIAMMDSPRET, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SIAMMDSP, + .is_ramret = true, + }, + [DB8500_REGULATOR_SWITCH_SIAPIPE] = { + .desc = { + .name = "db8500-sia-pipe", + .id = DB8500_REGULATOR_SWITCH_SIAPIPE, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SIAPIPE, + }, + [DB8500_REGULATOR_SWITCH_SGA] = { + .desc = { + .name = "db8500-sga", + .id = DB8500_REGULATOR_SWITCH_SGA, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SGA, + }, + [DB8500_REGULATOR_SWITCH_B2R2_MCDE] = { + .desc = { + .name = "db8500-b2r2-mcde", + .id = DB8500_REGULATOR_SWITCH_B2R2_MCDE, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_B2R2_MCDE, + }, + [DB8500_REGULATOR_SWITCH_ESRAM12] = { + .desc = { + .name = "db8500-esram12", + .id = DB8500_REGULATOR_SWITCH_ESRAM12, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_ESRAM12, + .is_enabled = true, + }, + [DB8500_REGULATOR_SWITCH_ESRAM12RET] = { + .desc = { + .name = "db8500-esram12-ret", + .id = DB8500_REGULATOR_SWITCH_ESRAM12RET, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_ESRAM12, + .is_ramret = true, + }, + [DB8500_REGULATOR_SWITCH_ESRAM34] = { + .desc = { + .name = "db8500-esram34", + .id = DB8500_REGULATOR_SWITCH_ESRAM34, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_ESRAM34, + .is_enabled = true, + }, + [DB8500_REGULATOR_SWITCH_ESRAM34RET] = { + .desc = { + .name = "db8500-esram34-ret", + .id = DB8500_REGULATOR_SWITCH_ESRAM34RET, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_ESRAM34, + .is_ramret = true, + }, +}; + +static int db8500_regulator_register(struct platform_device *pdev, + struct regulator_init_data *init_data, + int id, + struct device_node *np) +{ + struct dbx500_regulator_info *info; + struct regulator_config config = { }; + int err; + + /* assign per-regulator data */ + info = &dbx500_regulator_info[id]; + info->dev = &pdev->dev; + + config.dev = &pdev->dev; + config.init_data = init_data; + config.driver_data = info; + config.of_node = np; + + /* register with the regulator framework */ + info->rdev = devm_regulator_register(&pdev->dev, &info->desc, &config); + if (IS_ERR(info->rdev)) { + err = PTR_ERR(info->rdev); + dev_err(&pdev->dev, "failed to register %s: err %i\n", + info->desc.name, err); + return err; + } + + dev_dbg(rdev_get_dev(info->rdev), + "regulator-%s-probed\n", info->desc.name); + + return 0; +} + +static struct of_regulator_match db8500_regulator_matches[] = { + { .name = "db8500_vape", .driver_data = (void *) DB8500_REGULATOR_VAPE, }, + { .name = "db8500_varm", .driver_data = (void *) DB8500_REGULATOR_VARM, }, + { .name = "db8500_vmodem", .driver_data = (void *) DB8500_REGULATOR_VMODEM, }, + { .name = "db8500_vpll", .driver_data = (void *) DB8500_REGULATOR_VPLL, }, + { .name = "db8500_vsmps1", .driver_data = (void *) DB8500_REGULATOR_VSMPS1, }, + { .name = "db8500_vsmps2", .driver_data = (void *) DB8500_REGULATOR_VSMPS2, }, + { .name = "db8500_vsmps3", .driver_data = (void *) DB8500_REGULATOR_VSMPS3, }, + { .name = "db8500_vrf1", .driver_data = (void *) DB8500_REGULATOR_VRF1, }, + { .name = "db8500_sva_mmdsp", .driver_data = (void *) DB8500_REGULATOR_SWITCH_SVAMMDSP, }, + { .name = "db8500_sva_mmdsp_ret", .driver_data = (void *) DB8500_REGULATOR_SWITCH_SVAMMDSPRET, }, + { .name = "db8500_sva_pipe", .driver_data = (void *) DB8500_REGULATOR_SWITCH_SVAPIPE, }, + { .name = "db8500_sia_mmdsp", .driver_data = (void *) DB8500_REGULATOR_SWITCH_SIAMMDSP, }, + { .name = "db8500_sia_mmdsp_ret", .driver_data = (void *) DB8500_REGULATOR_SWITCH_SIAMMDSPRET, }, + { .name = "db8500_sia_pipe", .driver_data = (void *) DB8500_REGULATOR_SWITCH_SIAPIPE, }, + { .name = "db8500_sga", .driver_data = (void *) DB8500_REGULATOR_SWITCH_SGA, }, + { .name = "db8500_b2r2_mcde", .driver_data = (void *) DB8500_REGULATOR_SWITCH_B2R2_MCDE, }, + { .name = "db8500_esram12", .driver_data = (void *) DB8500_REGULATOR_SWITCH_ESRAM12, }, + { .name = "db8500_esram12_ret", .driver_data = (void *) DB8500_REGULATOR_SWITCH_ESRAM12RET, }, + { .name = "db8500_esram34", .driver_data = (void *) DB8500_REGULATOR_SWITCH_ESRAM34, }, + { .name = "db8500_esram34_ret", .driver_data = (void *) DB8500_REGULATOR_SWITCH_ESRAM34RET, }, +}; + +static int +db8500_regulator_of_probe(struct platform_device *pdev, + struct device_node *np) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(dbx500_regulator_info); i++) { + err = db8500_regulator_register( + pdev, db8500_regulator_matches[i].init_data, + i, db8500_regulator_matches[i].of_node); + if (err) + return err; + } + + return 0; +} + +static int db8500_regulator_probe(struct platform_device *pdev) +{ + struct regulator_init_data *db8500_init_data = + dev_get_platdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; + int i, err; + + /* register all regulators */ + if (np) { + err = of_regulator_match(&pdev->dev, np, + db8500_regulator_matches, + ARRAY_SIZE(db8500_regulator_matches)); + if (err < 0) { + dev_err(&pdev->dev, + "Error parsing regulator init data: %d\n", err); + return err; + } + + err = db8500_regulator_of_probe(pdev, np); + if (err) + return err; + } else { + for (i = 0; i < ARRAY_SIZE(dbx500_regulator_info); i++) { + err = db8500_regulator_register(pdev, + &db8500_init_data[i], + i, NULL); + if (err) + return err; + } + } + + err = ux500_regulator_debug_init(pdev, + dbx500_regulator_info, + ARRAY_SIZE(dbx500_regulator_info)); + return 0; +} + +static int db8500_regulator_remove(struct platform_device *pdev) +{ + ux500_regulator_debug_exit(); + + return 0; +} + +static struct platform_driver db8500_regulator_driver = { + .driver = { + .name = "db8500-prcmu-regulators", + }, + .probe = db8500_regulator_probe, + .remove = db8500_regulator_remove, +}; + +static int __init db8500_regulator_init(void) +{ + return platform_driver_register(&db8500_regulator_driver); +} + +static void __exit db8500_regulator_exit(void) +{ + platform_driver_unregister(&db8500_regulator_driver); +} + +arch_initcall(db8500_regulator_init); +module_exit(db8500_regulator_exit); + +MODULE_AUTHOR("STMicroelectronics/ST-Ericsson"); +MODULE_DESCRIPTION("DB8500 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/dbx500-prcmu.c b/drivers/regulator/dbx500-prcmu.c new file mode 100644 index 000000000..8976141c1 --- /dev/null +++ b/drivers/regulator/dbx500-prcmu.c @@ -0,0 +1,206 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Authors: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + * Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + * + * UX500 common part of Power domain regulators + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/regulator/driver.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include "dbx500-prcmu.h" + +/* + * power state reference count + */ +static int power_state_active_cnt; /* will initialize to zero */ +static DEFINE_SPINLOCK(power_state_active_lock); + +void power_state_active_enable(void) +{ + unsigned long flags; + + spin_lock_irqsave(&power_state_active_lock, flags); + power_state_active_cnt++; + spin_unlock_irqrestore(&power_state_active_lock, flags); +} + +int power_state_active_disable(void) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&power_state_active_lock, flags); + if (power_state_active_cnt <= 0) { + pr_err("power state: unbalanced enable/disable calls\n"); + ret = -EINVAL; + goto out; + } + + power_state_active_cnt--; +out: + spin_unlock_irqrestore(&power_state_active_lock, flags); + return ret; +} + +#ifdef CONFIG_REGULATOR_DEBUG + +static int power_state_active_get(void) +{ + unsigned long flags; + int cnt; + + spin_lock_irqsave(&power_state_active_lock, flags); + cnt = power_state_active_cnt; + spin_unlock_irqrestore(&power_state_active_lock, flags); + + return cnt; +} + +static struct ux500_regulator_debug { + struct dentry *dir; + struct dentry *status_file; + struct dentry *power_state_cnt_file; + struct dbx500_regulator_info *regulator_array; + int num_regulators; + u8 *state_before_suspend; + u8 *state_after_suspend; +} rdebug; + +static int ux500_regulator_power_state_cnt_print(struct seq_file *s, void *p) +{ + /* print power state count */ + seq_printf(s, "ux500-regulator power state count: %i\n", + power_state_active_get()); + + return 0; +} + +static int ux500_regulator_power_state_cnt_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ux500_regulator_power_state_cnt_print, + inode->i_private); +} + +static const struct file_operations ux500_regulator_power_state_cnt_fops = { + .open = ux500_regulator_power_state_cnt_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ux500_regulator_status_print(struct seq_file *s, void *p) +{ + int i; + + /* print dump header */ + seq_puts(s, "ux500-regulator status:\n"); + seq_printf(s, "%31s : %8s : %8s\n", "current", "before", "after"); + + for (i = 0; i < rdebug.num_regulators; i++) { + struct dbx500_regulator_info *info; + /* Access per-regulator data */ + info = &rdebug.regulator_array[i]; + + /* print status */ + seq_printf(s, "%20s : %8s : %8s : %8s\n", + info->desc.name, + info->is_enabled ? "enabled" : "disabled", + rdebug.state_before_suspend[i] ? "enabled" : "disabled", + rdebug.state_after_suspend[i] ? "enabled" : "disabled"); + } + + return 0; +} + +static int ux500_regulator_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, ux500_regulator_status_print, + inode->i_private); +} + +static const struct file_operations ux500_regulator_status_fops = { + .open = ux500_regulator_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +int __attribute__((weak)) dbx500_regulator_testcase( + struct dbx500_regulator_info *regulator_info, + int num_regulators) +{ + return 0; +} + +int +ux500_regulator_debug_init(struct platform_device *pdev, + struct dbx500_regulator_info *regulator_info, + int num_regulators) +{ + /* create directory */ + rdebug.dir = debugfs_create_dir("ux500-regulator", NULL); + if (!rdebug.dir) + goto exit_no_debugfs; + + /* create "status" file */ + rdebug.status_file = debugfs_create_file("status", + S_IRUGO, rdebug.dir, &pdev->dev, + &ux500_regulator_status_fops); + if (!rdebug.status_file) + goto exit_destroy_dir; + + /* create "power-state-count" file */ + rdebug.power_state_cnt_file = debugfs_create_file("power-state-count", + S_IRUGO, rdebug.dir, &pdev->dev, + &ux500_regulator_power_state_cnt_fops); + if (!rdebug.power_state_cnt_file) + goto exit_destroy_status; + + rdebug.regulator_array = regulator_info; + rdebug.num_regulators = num_regulators; + + rdebug.state_before_suspend = kzalloc(num_regulators, GFP_KERNEL); + if (!rdebug.state_before_suspend) + goto exit_destroy_power_state; + + rdebug.state_after_suspend = kzalloc(num_regulators, GFP_KERNEL); + if (!rdebug.state_after_suspend) + goto exit_free; + + dbx500_regulator_testcase(regulator_info, num_regulators); + return 0; + +exit_free: + kfree(rdebug.state_before_suspend); +exit_destroy_power_state: + debugfs_remove(rdebug.power_state_cnt_file); +exit_destroy_status: + debugfs_remove(rdebug.status_file); +exit_destroy_dir: + debugfs_remove(rdebug.dir); +exit_no_debugfs: + dev_err(&pdev->dev, "failed to create debugfs entries.\n"); + return -ENOMEM; +} + +int ux500_regulator_debug_exit(void) +{ + debugfs_remove_recursive(rdebug.dir); + kfree(rdebug.state_after_suspend); + kfree(rdebug.state_before_suspend); + + return 0; +} +#endif diff --git a/drivers/regulator/dbx500-prcmu.h b/drivers/regulator/dbx500-prcmu.h new file mode 100644 index 000000000..c8e51ace9 --- /dev/null +++ b/drivers/regulator/dbx500-prcmu.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Bengt Jonsson <bengt.jonsson@stericsson.com> for ST-Ericsson, + * Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + * License Terms: GNU General Public License v2 + * + */ + +#ifndef DBX500_REGULATOR_H +#define DBX500_REGULATOR_H + +#include <linux/platform_device.h> + +/** + * struct dbx500_regulator_info - dbx500 regulator information + * @dev: device pointer + * @desc: regulator description + * @rdev: regulator device pointer + * @is_enabled: status of the regulator + * @epod_id: id for EPOD (power domain) + * @is_ramret: RAM retention switch for EPOD (power domain) + * + */ +struct dbx500_regulator_info { + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *rdev; + bool is_enabled; + u16 epod_id; + bool is_ramret; + bool exclude_from_power_state; +}; + +void power_state_active_enable(void); +int power_state_active_disable(void); + + +#ifdef CONFIG_REGULATOR_DEBUG +int ux500_regulator_debug_init(struct platform_device *pdev, + struct dbx500_regulator_info *regulator_info, + int num_regulators); + +int ux500_regulator_debug_exit(void); +#else + +static inline int ux500_regulator_debug_init(struct platform_device *pdev, + struct dbx500_regulator_info *regulator_info, + int num_regulators) +{ + return 0; +} + +static inline int ux500_regulator_debug_exit(void) +{ + return 0; +} + +#endif +#endif diff --git a/drivers/regulator/devres.c b/drivers/regulator/devres.c new file mode 100644 index 000000000..784e3bf32 --- /dev/null +++ b/drivers/regulator/devres.c @@ -0,0 +1,486 @@ +/* + * devres.c -- Voltage/Current Regulator framework devres implementation. + * + * Copyright 2013 Linaro Ltd + * + * 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/err.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/module.h> + +#include "internal.h" + +static void devm_regulator_release(struct device *dev, void *res) +{ + regulator_put(*(struct regulator **)res); +} + +static struct regulator *_devm_regulator_get(struct device *dev, const char *id, + int get_type) +{ + struct regulator **ptr, *regulator; + + ptr = devres_alloc(devm_regulator_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + regulator = _regulator_get(dev, id, get_type); + if (!IS_ERR(regulator)) { + *ptr = regulator; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return regulator; +} + +/** + * devm_regulator_get - Resource managed regulator_get() + * @dev: device for regulator "consumer" + * @id: Supply name or regulator ID. + * + * Managed regulator_get(). Regulators returned from this function are + * automatically regulator_put() on driver detach. See regulator_get() for more + * information. + */ +struct regulator *devm_regulator_get(struct device *dev, const char *id) +{ + return _devm_regulator_get(dev, id, NORMAL_GET); +} +EXPORT_SYMBOL_GPL(devm_regulator_get); + +/** + * devm_regulator_get_exclusive - Resource managed regulator_get_exclusive() + * @dev: device for regulator "consumer" + * @id: Supply name or regulator ID. + * + * Managed regulator_get_exclusive(). Regulators returned from this function + * are automatically regulator_put() on driver detach. See regulator_get() for + * more information. + */ +struct regulator *devm_regulator_get_exclusive(struct device *dev, + const char *id) +{ + return _devm_regulator_get(dev, id, EXCLUSIVE_GET); +} +EXPORT_SYMBOL_GPL(devm_regulator_get_exclusive); + +/** + * devm_regulator_get_optional - Resource managed regulator_get_optional() + * @dev: device for regulator "consumer" + * @id: Supply name or regulator ID. + * + * Managed regulator_get_optional(). Regulators returned from this + * function are automatically regulator_put() on driver detach. See + * regulator_get_optional() for more information. + */ +struct regulator *devm_regulator_get_optional(struct device *dev, + const char *id) +{ + return _devm_regulator_get(dev, id, OPTIONAL_GET); +} +EXPORT_SYMBOL_GPL(devm_regulator_get_optional); + +static int devm_regulator_match(struct device *dev, void *res, void *data) +{ + struct regulator **r = res; + if (!r || !*r) { + WARN_ON(!r || !*r); + return 0; + } + return *r == data; +} + +/** + * devm_regulator_put - Resource managed regulator_put() + * @regulator: regulator to free + * + * Deallocate a regulator allocated with devm_regulator_get(). Normally + * this function will not need to be called and the resource management + * code will ensure that the resource is freed. + */ +void devm_regulator_put(struct regulator *regulator) +{ + int rc; + + rc = devres_release(regulator->dev, devm_regulator_release, + devm_regulator_match, regulator); + if (rc != 0) + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_regulator_put); + +struct regulator_bulk_devres { + struct regulator_bulk_data *consumers; + int num_consumers; +}; + +static void devm_regulator_bulk_release(struct device *dev, void *res) +{ + struct regulator_bulk_devres *devres = res; + + regulator_bulk_free(devres->num_consumers, devres->consumers); +} + +/** + * devm_regulator_bulk_get - managed get multiple regulator consumers + * + * @dev: Device to supply + * @num_consumers: Number of consumers to register + * @consumers: Configuration of consumers; clients are stored here. + * + * @return 0 on success, an errno on failure. + * + * This helper function allows drivers to get several regulator + * consumers in one operation with management, the regulators will + * automatically be freed when the device is unbound. If any of the + * regulators cannot be acquired then any regulators that were + * allocated will be freed before returning to the caller. + */ +int devm_regulator_bulk_get(struct device *dev, int num_consumers, + struct regulator_bulk_data *consumers) +{ + struct regulator_bulk_devres *devres; + int ret; + + devres = devres_alloc(devm_regulator_bulk_release, + sizeof(*devres), GFP_KERNEL); + if (!devres) + return -ENOMEM; + + ret = regulator_bulk_get(dev, num_consumers, consumers); + if (!ret) { + devres->consumers = consumers; + devres->num_consumers = num_consumers; + devres_add(dev, devres); + } else { + devres_free(devres); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_regulator_bulk_get); + +static void devm_rdev_release(struct device *dev, void *res) +{ + regulator_unregister(*(struct regulator_dev **)res); +} + +/** + * devm_regulator_register - Resource managed regulator_register() + * @regulator_desc: regulator to register + * @config: runtime configuration for regulator + * + * Called by regulator drivers to register a regulator. Returns a + * valid pointer to struct regulator_dev on success or an ERR_PTR() on + * error. The regulator will automatically be released when the device + * is unbound. + */ +struct regulator_dev *devm_regulator_register(struct device *dev, + const struct regulator_desc *regulator_desc, + const struct regulator_config *config) +{ + struct regulator_dev **ptr, *rdev; + + ptr = devres_alloc(devm_rdev_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + rdev = regulator_register(regulator_desc, config); + if (!IS_ERR(rdev)) { + *ptr = rdev; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return rdev; +} +EXPORT_SYMBOL_GPL(devm_regulator_register); + +static int devm_rdev_match(struct device *dev, void *res, void *data) +{ + struct regulator_dev **r = res; + if (!r || !*r) { + WARN_ON(!r || !*r); + return 0; + } + return *r == data; +} + +/** + * devm_regulator_unregister - Resource managed regulator_unregister() + * @regulator: regulator to free + * + * Unregister a regulator registered with devm_regulator_register(). + * Normally this function will not need to be called and the resource + * management code will ensure that the resource is freed. + */ +void devm_regulator_unregister(struct device *dev, struct regulator_dev *rdev) +{ + int rc; + + rc = devres_release(dev, devm_rdev_release, devm_rdev_match, rdev); + if (rc != 0) + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_regulator_unregister); + +struct regulator_supply_alias_match { + struct device *dev; + const char *id; +}; + +static int devm_regulator_match_supply_alias(struct device *dev, void *res, + void *data) +{ + struct regulator_supply_alias_match *match = res; + struct regulator_supply_alias_match *target = data; + + return match->dev == target->dev && strcmp(match->id, target->id) == 0; +} + +static void devm_regulator_destroy_supply_alias(struct device *dev, void *res) +{ + struct regulator_supply_alias_match *match = res; + + regulator_unregister_supply_alias(match->dev, match->id); +} + +/** + * devm_regulator_register_supply_alias - Resource managed + * regulator_register_supply_alias() + * + * @dev: device that will be given as the regulator "consumer" + * @id: Supply name or regulator ID + * @alias_dev: device that should be used to lookup the supply + * @alias_id: Supply name or regulator ID that should be used to lookup the + * supply + * + * The supply alias will automatically be unregistered when the source + * device is unbound. + */ +int devm_regulator_register_supply_alias(struct device *dev, const char *id, + struct device *alias_dev, + const char *alias_id) +{ + struct regulator_supply_alias_match *match; + int ret; + + match = devres_alloc(devm_regulator_destroy_supply_alias, + sizeof(struct regulator_supply_alias_match), + GFP_KERNEL); + if (!match) + return -ENOMEM; + + match->dev = dev; + match->id = id; + + ret = regulator_register_supply_alias(dev, id, alias_dev, alias_id); + if (ret < 0) { + devres_free(match); + return ret; + } + + devres_add(dev, match); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_regulator_register_supply_alias); + +/** + * devm_regulator_unregister_supply_alias - Resource managed + * regulator_unregister_supply_alias() + * + * @dev: device that will be given as the regulator "consumer" + * @id: Supply name or regulator ID + * + * Unregister an alias registered with + * devm_regulator_register_supply_alias(). Normally this function + * will not need to be called and the resource management code + * will ensure that the resource is freed. + */ +void devm_regulator_unregister_supply_alias(struct device *dev, const char *id) +{ + struct regulator_supply_alias_match match; + int rc; + + match.dev = dev; + match.id = id; + + rc = devres_release(dev, devm_regulator_destroy_supply_alias, + devm_regulator_match_supply_alias, &match); + if (rc != 0) + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_regulator_unregister_supply_alias); + +/** + * devm_regulator_bulk_register_supply_alias - Managed register + * multiple aliases + * + * @dev: device that will be given as the regulator "consumer" + * @id: List of supply names or regulator IDs + * @alias_dev: device that should be used to lookup the supply + * @alias_id: List of supply names or regulator IDs that should be used to + * lookup the supply + * @num_id: Number of aliases to register + * + * @return 0 on success, an errno on failure. + * + * This helper function allows drivers to register several supply + * aliases in one operation, the aliases will be automatically + * unregisters when the source device is unbound. If any of the + * aliases cannot be registered any aliases that were registered + * will be removed before returning to the caller. + */ +int devm_regulator_bulk_register_supply_alias(struct device *dev, + const char *const *id, + struct device *alias_dev, + const char *const *alias_id, + int num_id) +{ + int i; + int ret; + + for (i = 0; i < num_id; ++i) { + ret = devm_regulator_register_supply_alias(dev, id[i], + alias_dev, + alias_id[i]); + if (ret < 0) + goto err; + } + + return 0; + +err: + dev_err(dev, + "Failed to create supply alias %s,%s -> %s,%s\n", + id[i], dev_name(dev), alias_id[i], dev_name(alias_dev)); + + while (--i >= 0) + devm_regulator_unregister_supply_alias(dev, id[i]); + + return ret; +} +EXPORT_SYMBOL_GPL(devm_regulator_bulk_register_supply_alias); + +/** + * devm_regulator_bulk_unregister_supply_alias - Managed unregister + * multiple aliases + * + * @dev: device that will be given as the regulator "consumer" + * @id: List of supply names or regulator IDs + * @num_id: Number of aliases to unregister + * + * Unregister aliases registered with + * devm_regulator_bulk_register_supply_alias(). Normally this function + * will not need to be called and the resource management code + * will ensure that the resource is freed. + */ +void devm_regulator_bulk_unregister_supply_alias(struct device *dev, + const char *const *id, + int num_id) +{ + int i; + + for (i = 0; i < num_id; ++i) + devm_regulator_unregister_supply_alias(dev, id[i]); +} +EXPORT_SYMBOL_GPL(devm_regulator_bulk_unregister_supply_alias); + +struct regulator_notifier_match { + struct regulator *regulator; + struct notifier_block *nb; +}; + +static int devm_regulator_match_notifier(struct device *dev, void *res, + void *data) +{ + struct regulator_notifier_match *match = res; + struct regulator_notifier_match *target = data; + + return match->regulator == target->regulator && match->nb == target->nb; +} + +static void devm_regulator_destroy_notifier(struct device *dev, void *res) +{ + struct regulator_notifier_match *match = res; + + regulator_unregister_notifier(match->regulator, match->nb); +} + +/** + * devm_regulator_register_notifier - Resource managed + * regulator_register_notifier + * + * @regulator: regulator source + * @nb: notifier block + * + * The notifier will be registers under the consumer device and be + * automatically be unregistered when the source device is unbound. + */ +int devm_regulator_register_notifier(struct regulator *regulator, + struct notifier_block *nb) +{ + struct regulator_notifier_match *match; + int ret; + + match = devres_alloc(devm_regulator_destroy_notifier, + sizeof(struct regulator_notifier_match), + GFP_KERNEL); + if (!match) + return -ENOMEM; + + match->regulator = regulator; + match->nb = nb; + + ret = regulator_register_notifier(regulator, nb); + if (ret < 0) { + devres_free(match); + return ret; + } + + devres_add(regulator->dev, match); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_regulator_register_notifier); + +/** + * devm_regulator_unregister_notifier - Resource managed + * regulator_unregister_notifier() + * + * @regulator: regulator source + * @nb: notifier block + * + * Unregister a notifier registered with devm_regulator_register_notifier(). + * Normally this function will not need to be called and the resource + * management code will ensure that the resource is freed. + */ +void devm_regulator_unregister_notifier(struct regulator *regulator, + struct notifier_block *nb) +{ + struct regulator_notifier_match match; + int rc; + + match.regulator = regulator; + match.nb = nb; + + rc = devres_release(regulator->dev, devm_regulator_destroy_notifier, + devm_regulator_match_notifier, &match); + if (rc != 0) + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_regulator_unregister_notifier); diff --git a/drivers/regulator/dummy.c b/drivers/regulator/dummy.c new file mode 100644 index 000000000..cde749778 --- /dev/null +++ b/drivers/regulator/dummy.c @@ -0,0 +1,93 @@ +/* + * dummy.c + * + * Copyright 2010 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the + * License, or (at your option) any later version. + * + * This is useful for systems with mixed controllable and + * non-controllable regulators, as well as for allowing testing on + * systems with no controllable regulators. + */ + +#include <linux/err.h> +#include <linux/export.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> + +#include "dummy.h" + +struct regulator_dev *dummy_regulator_rdev; + +static struct regulator_init_data dummy_initdata = { + .constraints = { + .always_on = 1, + }, +}; + +static struct regulator_ops dummy_ops; + +static const struct regulator_desc dummy_desc = { + .name = "regulator-dummy", + .id = -1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &dummy_ops, +}; + +static int dummy_regulator_probe(struct platform_device *pdev) +{ + struct regulator_config config = { }; + int ret; + + config.dev = &pdev->dev; + config.init_data = &dummy_initdata; + + dummy_regulator_rdev = regulator_register(&dummy_desc, &config); + if (IS_ERR(dummy_regulator_rdev)) { + ret = PTR_ERR(dummy_regulator_rdev); + pr_err("Failed to register regulator: %d\n", ret); + return ret; + } + + return 0; +} + +static struct platform_driver dummy_regulator_driver = { + .probe = dummy_regulator_probe, + .driver = { + .name = "reg-dummy", + }, +}; + +static struct platform_device *dummy_pdev; + +void __init regulator_dummy_init(void) +{ + int ret; + + dummy_pdev = platform_device_alloc("reg-dummy", -1); + if (!dummy_pdev) { + pr_err("Failed to allocate dummy regulator device\n"); + return; + } + + ret = platform_device_add(dummy_pdev); + if (ret != 0) { + pr_err("Failed to register dummy regulator device: %d\n", ret); + platform_device_put(dummy_pdev); + return; + } + + ret = platform_driver_register(&dummy_regulator_driver); + if (ret != 0) { + pr_err("Failed to register dummy regulator driver: %d\n", ret); + platform_device_unregister(dummy_pdev); + } +} diff --git a/drivers/regulator/dummy.h b/drivers/regulator/dummy.h new file mode 100644 index 000000000..97a11b7e8 --- /dev/null +++ b/drivers/regulator/dummy.h @@ -0,0 +1,27 @@ +/* + * dummy.h + * + * Copyright 2010 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the + * License, or (at your option) any later version. + * + * This is useful for systems with mixed controllable and + * non-controllable regulators, as well as for allowing testing on + * systems with no controllable regulators. + */ + +#ifndef _DUMMY_H +#define _DUMMY_H + +struct regulator_dev; + +extern struct regulator_dev *dummy_regulator_rdev; + +void __init regulator_dummy_init(void); + +#endif diff --git a/drivers/regulator/fan53555.c b/drivers/regulator/fan53555.c new file mode 100644 index 000000000..a3bc80371 --- /dev/null +++ b/drivers/regulator/fan53555.c @@ -0,0 +1,496 @@ +/* + * FAN53555 Fairchild Digitally Programmable TinyBuck Regulator Driver. + * + * Supported Part Numbers: + * FAN53555UC00X/01X/03X/04X/05X + * + * Copyright (c) 2012 Marvell Technology Ltd. + * Yunfan Zhang <yfzhang@marvell.com> + * + * This package 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/module.h> +#include <linux/param.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/of_device.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/regulator/fan53555.h> + +/* Voltage setting */ +#define FAN53555_VSEL0 0x00 +#define FAN53555_VSEL1 0x01 +/* Control register */ +#define FAN53555_CONTROL 0x02 +/* IC Type */ +#define FAN53555_ID1 0x03 +/* IC mask version */ +#define FAN53555_ID2 0x04 +/* Monitor register */ +#define FAN53555_MONITOR 0x05 + +/* VSEL bit definitions */ +#define VSEL_BUCK_EN (1 << 7) +#define VSEL_MODE (1 << 6) +#define VSEL_NSEL_MASK 0x3F +/* Chip ID and Verison */ +#define DIE_ID 0x0F /* ID1 */ +#define DIE_REV 0x0F /* ID2 */ +/* Control bit definitions */ +#define CTL_OUTPUT_DISCHG (1 << 7) +#define CTL_SLEW_MASK (0x7 << 4) +#define CTL_SLEW_SHIFT 4 +#define CTL_RESET (1 << 2) + +#define FAN53555_NVOLTAGES 64 /* Numbers of voltages */ + +enum fan53555_vendor { + FAN53555_VENDOR_FAIRCHILD = 0, + FAN53555_VENDOR_SILERGY, +}; + +/* IC Type */ +enum { + FAN53555_CHIP_ID_00 = 0, + FAN53555_CHIP_ID_01, + FAN53555_CHIP_ID_02, + FAN53555_CHIP_ID_03, + FAN53555_CHIP_ID_04, + FAN53555_CHIP_ID_05, + FAN53555_CHIP_ID_08 = 8, +}; + +/* IC mask revision */ +enum { + FAN53555_CHIP_REV_00 = 0x3, + FAN53555_CHIP_REV_13 = 0xf, +}; + +enum { + SILERGY_SYR82X = 8, +}; + +struct fan53555_device_info { + enum fan53555_vendor vendor; + struct regmap *regmap; + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *rdev; + struct regulator_init_data *regulator; + /* IC Type and Rev */ + int chip_id; + int chip_rev; + /* Voltage setting register */ + unsigned int vol_reg; + unsigned int sleep_reg; + /* Voltage range and step(linear) */ + unsigned int vsel_min; + unsigned int vsel_step; + /* Voltage slew rate limiting */ + unsigned int slew_rate; + /* Sleep voltage cache */ + unsigned int sleep_vol_cache; +}; + +static int fan53555_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + struct fan53555_device_info *di = rdev_get_drvdata(rdev); + int ret; + + if (di->sleep_vol_cache == uV) + return 0; + ret = regulator_map_voltage_linear(rdev, uV, uV); + if (ret < 0) + return ret; + ret = regmap_update_bits(di->regmap, di->sleep_reg, + VSEL_NSEL_MASK, ret); + if (ret < 0) + return ret; + /* Cache the sleep voltage setting. + * Might not be the real voltage which is rounded */ + di->sleep_vol_cache = uV; + + return 0; +} + +static int fan53555_set_suspend_enable(struct regulator_dev *rdev) +{ + struct fan53555_device_info *di = rdev_get_drvdata(rdev); + + return regmap_update_bits(di->regmap, di->sleep_reg, + VSEL_BUCK_EN, VSEL_BUCK_EN); +} + +static int fan53555_set_suspend_disable(struct regulator_dev *rdev) +{ + struct fan53555_device_info *di = rdev_get_drvdata(rdev); + + return regmap_update_bits(di->regmap, di->sleep_reg, + VSEL_BUCK_EN, 0); +} + +static int fan53555_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct fan53555_device_info *di = rdev_get_drvdata(rdev); + + switch (mode) { + case REGULATOR_MODE_FAST: + regmap_update_bits(di->regmap, di->vol_reg, + VSEL_MODE, VSEL_MODE); + break; + case REGULATOR_MODE_NORMAL: + regmap_update_bits(di->regmap, di->vol_reg, VSEL_MODE, 0); + break; + default: + return -EINVAL; + } + return 0; +} + +static unsigned int fan53555_get_mode(struct regulator_dev *rdev) +{ + struct fan53555_device_info *di = rdev_get_drvdata(rdev); + unsigned int val; + int ret = 0; + + ret = regmap_read(di->regmap, di->vol_reg, &val); + if (ret < 0) + return ret; + if (val & VSEL_MODE) + return REGULATOR_MODE_FAST; + else + return REGULATOR_MODE_NORMAL; +} + +static const int slew_rates[] = { + 64000, + 32000, + 16000, + 8000, + 4000, + 2000, + 1000, + 500, +}; + +static int fan53555_set_ramp(struct regulator_dev *rdev, int ramp) +{ + struct fan53555_device_info *di = rdev_get_drvdata(rdev); + int regval = -1, i; + + for (i = 0; i < ARRAY_SIZE(slew_rates); i++) { + if (ramp <= slew_rates[i]) + regval = i; + else + break; + } + + if (regval < 0) { + dev_err(di->dev, "unsupported ramp value %d\n", ramp); + return -EINVAL; + } + + return regmap_update_bits(di->regmap, FAN53555_CONTROL, + CTL_SLEW_MASK, regval << CTL_SLEW_SHIFT); +} + +static const struct regulator_ops fan53555_regulator_ops = { + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .map_voltage = regulator_map_voltage_linear, + .list_voltage = regulator_list_voltage_linear, + .set_suspend_voltage = fan53555_set_suspend_voltage, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_mode = fan53555_set_mode, + .get_mode = fan53555_get_mode, + .set_ramp_delay = fan53555_set_ramp, + .set_suspend_enable = fan53555_set_suspend_enable, + .set_suspend_disable = fan53555_set_suspend_disable, +}; + +static int fan53555_voltages_setup_fairchild(struct fan53555_device_info *di) +{ + /* Init voltage range and step */ + switch (di->chip_id) { + case FAN53555_CHIP_ID_00: + switch (di->chip_rev) { + case FAN53555_CHIP_REV_00: + di->vsel_min = 600000; + di->vsel_step = 10000; + break; + case FAN53555_CHIP_REV_13: + di->vsel_min = 800000; + di->vsel_step = 10000; + break; + default: + dev_err(di->dev, + "Chip ID %d with rev %d not supported!\n", + di->chip_id, di->chip_rev); + return -EINVAL; + } + break; + case FAN53555_CHIP_ID_01: + case FAN53555_CHIP_ID_03: + case FAN53555_CHIP_ID_05: + case FAN53555_CHIP_ID_08: + di->vsel_min = 600000; + di->vsel_step = 10000; + break; + case FAN53555_CHIP_ID_04: + di->vsel_min = 603000; + di->vsel_step = 12826; + break; + default: + dev_err(di->dev, + "Chip ID %d not supported!\n", di->chip_id); + return -EINVAL; + } + + return 0; +} + +static int fan53555_voltages_setup_silergy(struct fan53555_device_info *di) +{ + /* Init voltage range and step */ + switch (di->chip_id) { + case SILERGY_SYR82X: + di->vsel_min = 712500; + di->vsel_step = 12500; + break; + default: + dev_err(di->dev, + "Chip ID %d not supported!\n", di->chip_id); + return -EINVAL; + } + + return 0; +} + +/* For 00,01,03,05 options: + * VOUT = 0.60V + NSELx * 10mV, from 0.60 to 1.23V. + * For 04 option: + * VOUT = 0.603V + NSELx * 12.826mV, from 0.603 to 1.411V. + * */ +static int fan53555_device_setup(struct fan53555_device_info *di, + struct fan53555_platform_data *pdata) +{ + int ret = 0; + + /* Setup voltage control register */ + switch (pdata->sleep_vsel_id) { + case FAN53555_VSEL_ID_0: + di->sleep_reg = FAN53555_VSEL0; + di->vol_reg = FAN53555_VSEL1; + break; + case FAN53555_VSEL_ID_1: + di->sleep_reg = FAN53555_VSEL1; + di->vol_reg = FAN53555_VSEL0; + break; + default: + dev_err(di->dev, "Invalid VSEL ID!\n"); + return -EINVAL; + } + + switch (di->vendor) { + case FAN53555_VENDOR_FAIRCHILD: + ret = fan53555_voltages_setup_fairchild(di); + break; + case FAN53555_VENDOR_SILERGY: + ret = fan53555_voltages_setup_silergy(di); + break; + default: + dev_err(di->dev, "vendor %d not supported!\n", di->vendor); + return -EINVAL; + } + + return ret; +} + +static int fan53555_regulator_register(struct fan53555_device_info *di, + struct regulator_config *config) +{ + struct regulator_desc *rdesc = &di->desc; + + rdesc->name = "fan53555-reg"; + rdesc->supply_name = "vin"; + rdesc->ops = &fan53555_regulator_ops; + rdesc->type = REGULATOR_VOLTAGE; + rdesc->n_voltages = FAN53555_NVOLTAGES; + rdesc->enable_reg = di->vol_reg; + rdesc->enable_mask = VSEL_BUCK_EN; + rdesc->min_uV = di->vsel_min; + rdesc->uV_step = di->vsel_step; + rdesc->vsel_reg = di->vol_reg; + rdesc->vsel_mask = VSEL_NSEL_MASK; + rdesc->owner = THIS_MODULE; + + di->rdev = devm_regulator_register(di->dev, &di->desc, config); + return PTR_ERR_OR_ZERO(di->rdev); +} + +static const struct regmap_config fan53555_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static struct fan53555_platform_data *fan53555_parse_dt(struct device *dev, + struct device_node *np, + const struct regulator_desc *desc) +{ + struct fan53555_platform_data *pdata; + int ret; + u32 tmp; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + pdata->regulator = of_get_regulator_init_data(dev, np, desc); + + ret = of_property_read_u32(np, "fcs,suspend-voltage-selector", + &tmp); + if (!ret) + pdata->sleep_vsel_id = tmp; + + return pdata; +} + +static const struct of_device_id fan53555_dt_ids[] = { + { + .compatible = "fcs,fan53555", + .data = (void *)FAN53555_VENDOR_FAIRCHILD + }, { + .compatible = "silergy,syr827", + .data = (void *)FAN53555_VENDOR_SILERGY, + }, { + .compatible = "silergy,syr828", + .data = (void *)FAN53555_VENDOR_SILERGY, + }, + { } +}; +MODULE_DEVICE_TABLE(of, fan53555_dt_ids); + +static int fan53555_regulator_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *np = client->dev.of_node; + struct fan53555_device_info *di; + struct fan53555_platform_data *pdata; + struct regulator_config config = { }; + unsigned int val; + int ret; + + di = devm_kzalloc(&client->dev, sizeof(struct fan53555_device_info), + GFP_KERNEL); + if (!di) + return -ENOMEM; + + pdata = dev_get_platdata(&client->dev); + if (!pdata) + pdata = fan53555_parse_dt(&client->dev, np, &di->desc); + + if (!pdata || !pdata->regulator) { + dev_err(&client->dev, "Platform data not found!\n"); + return -ENODEV; + } + + di->regulator = pdata->regulator; + if (client->dev.of_node) { + di->vendor = + (unsigned long)of_device_get_match_data(&client->dev); + } else { + /* if no ramp constraint set, get the pdata ramp_delay */ + if (!di->regulator->constraints.ramp_delay) { + int slew_idx = (pdata->slew_rate & 0x7) + ? pdata->slew_rate : 0; + + di->regulator->constraints.ramp_delay + = slew_rates[slew_idx]; + } + + di->vendor = id->driver_data; + } + + di->regmap = devm_regmap_init_i2c(client, &fan53555_regmap_config); + if (IS_ERR(di->regmap)) { + dev_err(&client->dev, "Failed to allocate regmap!\n"); + return PTR_ERR(di->regmap); + } + di->dev = &client->dev; + i2c_set_clientdata(client, di); + /* Get chip ID */ + ret = regmap_read(di->regmap, FAN53555_ID1, &val); + if (ret < 0) { + dev_err(&client->dev, "Failed to get chip ID!\n"); + return ret; + } + di->chip_id = val & DIE_ID; + /* Get chip revision */ + ret = regmap_read(di->regmap, FAN53555_ID2, &val); + if (ret < 0) { + dev_err(&client->dev, "Failed to get chip Rev!\n"); + return ret; + } + di->chip_rev = val & DIE_REV; + dev_info(&client->dev, "FAN53555 Option[%d] Rev[%d] Detected!\n", + di->chip_id, di->chip_rev); + /* Device init */ + ret = fan53555_device_setup(di, pdata); + if (ret < 0) { + dev_err(&client->dev, "Failed to setup device!\n"); + return ret; + } + /* Register regulator */ + config.dev = di->dev; + config.init_data = di->regulator; + config.regmap = di->regmap; + config.driver_data = di; + config.of_node = np; + + ret = fan53555_regulator_register(di, &config); + if (ret < 0) + dev_err(&client->dev, "Failed to register regulator!\n"); + return ret; + +} + +static const struct i2c_device_id fan53555_id[] = { + { + .name = "fan53555", + .driver_data = FAN53555_VENDOR_FAIRCHILD + }, { + .name = "syr827", + .driver_data = FAN53555_VENDOR_SILERGY + }, { + .name = "syr828", + .driver_data = FAN53555_VENDOR_SILERGY + }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, fan53555_id); + +static struct i2c_driver fan53555_regulator_driver = { + .driver = { + .name = "fan53555-regulator", + .of_match_table = of_match_ptr(fan53555_dt_ids), + }, + .probe = fan53555_regulator_probe, + .id_table = fan53555_id, +}; + +module_i2c_driver(fan53555_regulator_driver); + +MODULE_AUTHOR("Yunfan Zhang <yfzhang@marvell.com>"); +MODULE_DESCRIPTION("FAN53555 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/fixed-helper.c b/drivers/regulator/fixed-helper.c new file mode 100644 index 000000000..777fac6fb --- /dev/null +++ b/drivers/regulator/fixed-helper.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/platform_device.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/fixed.h> + +struct fixed_regulator_data { + struct fixed_voltage_config cfg; + struct regulator_init_data init_data; + struct platform_device pdev; +}; + +static void regulator_fixed_release(struct device *dev) +{ + struct fixed_regulator_data *data = container_of(dev, + struct fixed_regulator_data, pdev.dev); + kfree(data->cfg.supply_name); + kfree(data); +} + +/** + * regulator_register_fixed_name - register a no-op fixed regulator + * @id: platform device id + * @name: name to be used for the regulator + * @supplies: consumers for this regulator + * @num_supplies: number of consumers + * @uv: voltage in microvolts + */ +struct platform_device *regulator_register_always_on(int id, const char *name, + struct regulator_consumer_supply *supplies, int num_supplies, int uv) +{ + struct fixed_regulator_data *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->cfg.supply_name = kstrdup(name, GFP_KERNEL); + if (!data->cfg.supply_name) { + kfree(data); + return NULL; + } + + data->cfg.microvolts = uv; + data->cfg.gpio = -EINVAL; + data->cfg.enabled_at_boot = 1; + data->cfg.init_data = &data->init_data; + + data->init_data.constraints.always_on = 1; + data->init_data.consumer_supplies = supplies; + data->init_data.num_consumer_supplies = num_supplies; + + data->pdev.name = "reg-fixed-voltage"; + data->pdev.id = id; + data->pdev.dev.platform_data = &data->cfg; + data->pdev.dev.release = regulator_fixed_release; + + platform_device_register(&data->pdev); + + return &data->pdev; +} diff --git a/drivers/regulator/fixed.c b/drivers/regulator/fixed.c new file mode 100644 index 000000000..988a7472c --- /dev/null +++ b/drivers/regulator/fixed.c @@ -0,0 +1,225 @@ +/* + * fixed.c + * + * Copyright 2008 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * Copyright (c) 2009 Nokia Corporation + * Roger Quadros <ext-roger.quadros@nokia.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; either version 2 of the + * License, or (at your option) any later version. + * + * This is useful for systems with mixed controllable and + * non-controllable regulators, as well as for allowing testing on + * systems with no controllable regulators. + */ + +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/fixed.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/machine.h> + +struct fixed_voltage_data { + struct regulator_desc desc; + struct regulator_dev *dev; +}; + + +/** + * of_get_fixed_voltage_config - extract fixed_voltage_config structure info + * @dev: device requesting for fixed_voltage_config + * @desc: regulator description + * + * Populates fixed_voltage_config structure by extracting data from device + * tree node, returns a pointer to the populated structure of NULL if memory + * alloc fails. + */ +static struct fixed_voltage_config * +of_get_fixed_voltage_config(struct device *dev, + const struct regulator_desc *desc) +{ + struct fixed_voltage_config *config; + struct device_node *np = dev->of_node; + struct regulator_init_data *init_data; + + config = devm_kzalloc(dev, sizeof(struct fixed_voltage_config), + GFP_KERNEL); + if (!config) + return ERR_PTR(-ENOMEM); + + config->init_data = of_get_regulator_init_data(dev, dev->of_node, desc); + if (!config->init_data) + return ERR_PTR(-EINVAL); + + init_data = config->init_data; + init_data->constraints.apply_uV = 0; + + config->supply_name = init_data->constraints.name; + if (init_data->constraints.min_uV == init_data->constraints.max_uV) { + config->microvolts = init_data->constraints.min_uV; + } else { + dev_err(dev, + "Fixed regulator specified with variable voltages\n"); + return ERR_PTR(-EINVAL); + } + + if (init_data->constraints.boot_on) + config->enabled_at_boot = true; + + config->gpio = of_get_named_gpio(np, "gpio", 0); + if ((config->gpio < 0) && (config->gpio != -ENOENT)) + return ERR_PTR(config->gpio); + + of_property_read_u32(np, "startup-delay-us", &config->startup_delay); + + config->enable_high = of_property_read_bool(np, "enable-active-high"); + config->gpio_is_open_drain = of_property_read_bool(np, + "gpio-open-drain"); + + if (of_find_property(np, "vin-supply", NULL)) + config->input_supply = "vin"; + + return config; +} + +static struct regulator_ops fixed_voltage_ops = { +}; + +static int reg_fixed_voltage_probe(struct platform_device *pdev) +{ + struct fixed_voltage_config *config; + struct fixed_voltage_data *drvdata; + struct regulator_config cfg = { }; + int ret; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct fixed_voltage_data), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + if (pdev->dev.of_node) { + config = of_get_fixed_voltage_config(&pdev->dev, + &drvdata->desc); + if (IS_ERR(config)) + return PTR_ERR(config); + } else { + config = dev_get_platdata(&pdev->dev); + } + + if (!config) + return -ENOMEM; + + drvdata->desc.name = devm_kstrdup(&pdev->dev, + config->supply_name, + GFP_KERNEL); + if (drvdata->desc.name == NULL) { + dev_err(&pdev->dev, "Failed to allocate supply name\n"); + return -ENOMEM; + } + drvdata->desc.type = REGULATOR_VOLTAGE; + drvdata->desc.owner = THIS_MODULE; + drvdata->desc.ops = &fixed_voltage_ops; + + drvdata->desc.enable_time = config->startup_delay; + + if (config->input_supply) { + drvdata->desc.supply_name = devm_kstrdup(&pdev->dev, + config->input_supply, + GFP_KERNEL); + if (!drvdata->desc.supply_name) { + dev_err(&pdev->dev, + "Failed to allocate input supply\n"); + return -ENOMEM; + } + } + + if (config->microvolts) + drvdata->desc.n_voltages = 1; + + drvdata->desc.fixed_uV = config->microvolts; + + if (gpio_is_valid(config->gpio)) { + cfg.ena_gpio = config->gpio; + if (pdev->dev.of_node) + cfg.ena_gpio_initialized = true; + } + cfg.ena_gpio_invert = !config->enable_high; + if (config->enabled_at_boot) { + if (config->enable_high) + cfg.ena_gpio_flags |= GPIOF_OUT_INIT_HIGH; + else + cfg.ena_gpio_flags |= GPIOF_OUT_INIT_LOW; + } else { + if (config->enable_high) + cfg.ena_gpio_flags |= GPIOF_OUT_INIT_LOW; + else + cfg.ena_gpio_flags |= GPIOF_OUT_INIT_HIGH; + } + if (config->gpio_is_open_drain) + cfg.ena_gpio_flags |= GPIOF_OPEN_DRAIN; + + cfg.dev = &pdev->dev; + cfg.init_data = config->init_data; + cfg.driver_data = drvdata; + cfg.of_node = pdev->dev.of_node; + + drvdata->dev = devm_regulator_register(&pdev->dev, &drvdata->desc, + &cfg); + if (IS_ERR(drvdata->dev)) { + ret = PTR_ERR(drvdata->dev); + dev_err(&pdev->dev, "Failed to register regulator: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, drvdata); + + dev_dbg(&pdev->dev, "%s supplying %duV\n", drvdata->desc.name, + drvdata->desc.fixed_uV); + + return 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id fixed_of_match[] = { + { .compatible = "regulator-fixed", }, + {}, +}; +MODULE_DEVICE_TABLE(of, fixed_of_match); +#endif + +static struct platform_driver regulator_fixed_voltage_driver = { + .probe = reg_fixed_voltage_probe, + .driver = { + .name = "reg-fixed-voltage", + .of_match_table = of_match_ptr(fixed_of_match), + }, +}; + +static int __init regulator_fixed_voltage_init(void) +{ + return platform_driver_register(®ulator_fixed_voltage_driver); +} +subsys_initcall(regulator_fixed_voltage_init); + +static void __exit regulator_fixed_voltage_exit(void) +{ + platform_driver_unregister(®ulator_fixed_voltage_driver); +} +module_exit(regulator_fixed_voltage_exit); + +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("Fixed voltage regulator"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:reg-fixed-voltage"); diff --git a/drivers/regulator/gpio-regulator.c b/drivers/regulator/gpio-regulator.c new file mode 100644 index 000000000..b2f5ec4f6 --- /dev/null +++ b/drivers/regulator/gpio-regulator.c @@ -0,0 +1,430 @@ +/* + * gpio-regulator.c + * + * Copyright 2011 Heiko Stuebner <heiko@sntech.de> + * + * based on fixed.c + * + * Copyright 2008 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * Copyright (c) 2009 Nokia Corporation + * Roger Quadros <ext-roger.quadros@nokia.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; either version 2 of the + * License, or (at your option) any later version. + * + * This is useful for systems with mixed controllable and + * non-controllable regulators, as well as for allowing testing on + * systems with no controllable regulators. + */ + +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/gpio-regulator.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_gpio.h> + +struct gpio_regulator_data { + struct regulator_desc desc; + struct regulator_dev *dev; + + struct gpio *gpios; + int nr_gpios; + + struct gpio_regulator_state *states; + int nr_states; + + int state; +}; + +static int gpio_regulator_get_value(struct regulator_dev *dev) +{ + struct gpio_regulator_data *data = rdev_get_drvdata(dev); + int ptr; + + for (ptr = 0; ptr < data->nr_states; ptr++) + if (data->states[ptr].gpios == data->state) + return data->states[ptr].value; + + return -EINVAL; +} + +static int gpio_regulator_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, + unsigned *selector) +{ + struct gpio_regulator_data *data = rdev_get_drvdata(dev); + int ptr, target = 0, state, best_val = INT_MAX; + + for (ptr = 0; ptr < data->nr_states; ptr++) + if (data->states[ptr].value < best_val && + data->states[ptr].value >= min_uV && + data->states[ptr].value <= max_uV) { + target = data->states[ptr].gpios; + best_val = data->states[ptr].value; + if (selector) + *selector = ptr; + } + + if (best_val == INT_MAX) + return -EINVAL; + + for (ptr = 0; ptr < data->nr_gpios; ptr++) { + state = (target & (1 << ptr)) >> ptr; + gpio_set_value_cansleep(data->gpios[ptr].gpio, state); + } + data->state = target; + + return 0; +} + +static int gpio_regulator_list_voltage(struct regulator_dev *dev, + unsigned selector) +{ + struct gpio_regulator_data *data = rdev_get_drvdata(dev); + + if (selector >= data->nr_states) + return -EINVAL; + + return data->states[selector].value; +} + +static int gpio_regulator_set_current_limit(struct regulator_dev *dev, + int min_uA, int max_uA) +{ + struct gpio_regulator_data *data = rdev_get_drvdata(dev); + int ptr, target = 0, state, best_val = 0; + + for (ptr = 0; ptr < data->nr_states; ptr++) + if (data->states[ptr].value > best_val && + data->states[ptr].value >= min_uA && + data->states[ptr].value <= max_uA) { + target = data->states[ptr].gpios; + best_val = data->states[ptr].value; + } + + if (best_val == 0) + return -EINVAL; + + for (ptr = 0; ptr < data->nr_gpios; ptr++) { + state = (target & (1 << ptr)) >> ptr; + gpio_set_value_cansleep(data->gpios[ptr].gpio, state); + } + data->state = target; + + return 0; +} + +static struct regulator_ops gpio_regulator_voltage_ops = { + .get_voltage = gpio_regulator_get_value, + .set_voltage = gpio_regulator_set_voltage, + .list_voltage = gpio_regulator_list_voltage, +}; + +static struct gpio_regulator_config * +of_get_gpio_regulator_config(struct device *dev, struct device_node *np, + const struct regulator_desc *desc) +{ + struct gpio_regulator_config *config; + const char *regtype; + int proplen, gpio, i; + int ret; + + config = devm_kzalloc(dev, + sizeof(struct gpio_regulator_config), + GFP_KERNEL); + if (!config) + return ERR_PTR(-ENOMEM); + + config->init_data = of_get_regulator_init_data(dev, np, desc); + if (!config->init_data) + return ERR_PTR(-EINVAL); + + config->supply_name = config->init_data->constraints.name; + + if (of_property_read_bool(np, "enable-active-high")) + config->enable_high = true; + + if (of_property_read_bool(np, "enable-at-boot")) + config->enabled_at_boot = true; + + of_property_read_u32(np, "startup-delay-us", &config->startup_delay); + + config->enable_gpio = of_get_named_gpio(np, "enable-gpio", 0); + if (config->enable_gpio < 0 && config->enable_gpio != -ENOENT) + return ERR_PTR(config->enable_gpio); + + /* Fetch GPIOs. - optional property*/ + ret = of_gpio_count(np); + if ((ret < 0) && (ret != -ENOENT)) + return ERR_PTR(ret); + + if (ret > 0) { + config->nr_gpios = ret; + config->gpios = devm_kcalloc(dev, + config->nr_gpios, sizeof(struct gpio), + GFP_KERNEL); + if (!config->gpios) + return ERR_PTR(-ENOMEM); + + proplen = of_property_count_u32_elems(np, "gpios-states"); + /* optional property */ + if (proplen < 0) + proplen = 0; + + if (proplen > 0 && proplen != config->nr_gpios) { + dev_warn(dev, "gpios <-> gpios-states mismatch\n"); + proplen = 0; + } + + for (i = 0; i < config->nr_gpios; i++) { + gpio = of_get_named_gpio(np, "gpios", i); + if (gpio < 0) { + if (gpio != -ENOENT) + return ERR_PTR(gpio); + break; + } + config->gpios[i].gpio = gpio; + config->gpios[i].label = config->supply_name; + if (proplen > 0) { + of_property_read_u32_index(np, "gpios-states", + i, &ret); + if (ret) + config->gpios[i].flags = + GPIOF_OUT_INIT_HIGH; + } + } + } + + /* Fetch states. */ + proplen = of_property_count_u32_elems(np, "states"); + if (proplen < 0) { + dev_err(dev, "No 'states' property found\n"); + return ERR_PTR(-EINVAL); + } + + config->states = devm_kcalloc(dev, + proplen / 2, + sizeof(struct gpio_regulator_state), + GFP_KERNEL); + if (!config->states) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < proplen / 2; i++) { + of_property_read_u32_index(np, "states", i * 2, + &config->states[i].value); + of_property_read_u32_index(np, "states", i * 2 + 1, + &config->states[i].gpios); + } + config->nr_states = i; + + config->type = REGULATOR_VOLTAGE; + ret = of_property_read_string(np, "regulator-type", ®type); + if (ret >= 0) { + if (!strncmp("voltage", regtype, 7)) + config->type = REGULATOR_VOLTAGE; + else if (!strncmp("current", regtype, 7)) + config->type = REGULATOR_CURRENT; + else + dev_warn(dev, "Unknown regulator-type '%s'\n", + regtype); + } + + return config; +} + +static struct regulator_ops gpio_regulator_current_ops = { + .get_current_limit = gpio_regulator_get_value, + .set_current_limit = gpio_regulator_set_current_limit, +}; + +static int gpio_regulator_probe(struct platform_device *pdev) +{ + struct gpio_regulator_config *config = dev_get_platdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; + struct gpio_regulator_data *drvdata; + struct regulator_config cfg = { }; + int ptr, ret, state; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct gpio_regulator_data), + GFP_KERNEL); + if (drvdata == NULL) + return -ENOMEM; + + if (np) { + config = of_get_gpio_regulator_config(&pdev->dev, np, + &drvdata->desc); + if (IS_ERR(config)) + return PTR_ERR(config); + } + + drvdata->desc.name = kstrdup(config->supply_name, GFP_KERNEL); + if (drvdata->desc.name == NULL) { + dev_err(&pdev->dev, "Failed to allocate supply name\n"); + return -ENOMEM; + } + + if (config->nr_gpios != 0) { + drvdata->gpios = kmemdup(config->gpios, + config->nr_gpios * sizeof(struct gpio), + GFP_KERNEL); + if (drvdata->gpios == NULL) { + dev_err(&pdev->dev, "Failed to allocate gpio data\n"); + ret = -ENOMEM; + goto err_name; + } + + drvdata->nr_gpios = config->nr_gpios; + ret = gpio_request_array(drvdata->gpios, drvdata->nr_gpios); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "Could not obtain regulator setting GPIOs: %d\n", + ret); + goto err_memgpio; + } + } + + drvdata->states = kmemdup(config->states, + config->nr_states * + sizeof(struct gpio_regulator_state), + GFP_KERNEL); + if (drvdata->states == NULL) { + dev_err(&pdev->dev, "Failed to allocate state data\n"); + ret = -ENOMEM; + goto err_stategpio; + } + drvdata->nr_states = config->nr_states; + + drvdata->desc.owner = THIS_MODULE; + drvdata->desc.enable_time = config->startup_delay; + + /* handle regulator type*/ + switch (config->type) { + case REGULATOR_VOLTAGE: + drvdata->desc.type = REGULATOR_VOLTAGE; + drvdata->desc.ops = &gpio_regulator_voltage_ops; + drvdata->desc.n_voltages = config->nr_states; + break; + case REGULATOR_CURRENT: + drvdata->desc.type = REGULATOR_CURRENT; + drvdata->desc.ops = &gpio_regulator_current_ops; + break; + default: + dev_err(&pdev->dev, "No regulator type set\n"); + ret = -EINVAL; + goto err_memstate; + } + + /* build initial state from gpio init data. */ + state = 0; + for (ptr = 0; ptr < drvdata->nr_gpios; ptr++) { + if (config->gpios[ptr].flags & GPIOF_OUT_INIT_HIGH) + state |= (1 << ptr); + } + drvdata->state = state; + + cfg.dev = &pdev->dev; + cfg.init_data = config->init_data; + cfg.driver_data = drvdata; + cfg.of_node = np; + + if (gpio_is_valid(config->enable_gpio)) { + cfg.ena_gpio = config->enable_gpio; + cfg.ena_gpio_initialized = true; + } + cfg.ena_gpio_invert = !config->enable_high; + if (config->enabled_at_boot) { + if (config->enable_high) + cfg.ena_gpio_flags |= GPIOF_OUT_INIT_HIGH; + else + cfg.ena_gpio_flags |= GPIOF_OUT_INIT_LOW; + } else { + if (config->enable_high) + cfg.ena_gpio_flags |= GPIOF_OUT_INIT_LOW; + else + cfg.ena_gpio_flags |= GPIOF_OUT_INIT_HIGH; + } + + drvdata->dev = regulator_register(&drvdata->desc, &cfg); + if (IS_ERR(drvdata->dev)) { + ret = PTR_ERR(drvdata->dev); + dev_err(&pdev->dev, "Failed to register regulator: %d\n", ret); + goto err_memstate; + } + + platform_set_drvdata(pdev, drvdata); + + return 0; + +err_memstate: + kfree(drvdata->states); +err_stategpio: + gpio_free_array(drvdata->gpios, drvdata->nr_gpios); +err_memgpio: + kfree(drvdata->gpios); +err_name: + kfree(drvdata->desc.name); + return ret; +} + +static int gpio_regulator_remove(struct platform_device *pdev) +{ + struct gpio_regulator_data *drvdata = platform_get_drvdata(pdev); + + regulator_unregister(drvdata->dev); + + gpio_free_array(drvdata->gpios, drvdata->nr_gpios); + + kfree(drvdata->states); + kfree(drvdata->gpios); + + kfree(drvdata->desc.name); + + return 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id regulator_gpio_of_match[] = { + { .compatible = "regulator-gpio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, regulator_gpio_of_match); +#endif + +static struct platform_driver gpio_regulator_driver = { + .probe = gpio_regulator_probe, + .remove = gpio_regulator_remove, + .driver = { + .name = "gpio-regulator", + .of_match_table = of_match_ptr(regulator_gpio_of_match), + }, +}; + +static int __init gpio_regulator_init(void) +{ + return platform_driver_register(&gpio_regulator_driver); +} +subsys_initcall(gpio_regulator_init); + +static void __exit gpio_regulator_exit(void) +{ + platform_driver_unregister(&gpio_regulator_driver); +} +module_exit(gpio_regulator_exit); + +MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); +MODULE_DESCRIPTION("gpio voltage regulator"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-regulator"); diff --git a/drivers/regulator/helpers.c b/drivers/regulator/helpers.c new file mode 100644 index 000000000..2ae7c3ac5 --- /dev/null +++ b/drivers/regulator/helpers.c @@ -0,0 +1,530 @@ +/* + * helpers.c -- Voltage/Current Regulator framework helper functions. + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * Copyright 2008 SlimLogic Ltd. + * + * 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/err.h> +#include <linux/delay.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/module.h> + +/** + * regulator_is_enabled_regmap - standard is_enabled() for regmap users + * + * @rdev: regulator to operate on + * + * Regulators that use regmap for their register I/O can set the + * enable_reg and enable_mask fields in their descriptor and then use + * this as their is_enabled operation, saving some code. + */ +int regulator_is_enabled_regmap(struct regulator_dev *rdev) +{ + unsigned int val; + int ret; + + ret = regmap_read(rdev->regmap, rdev->desc->enable_reg, &val); + if (ret != 0) + return ret; + + val &= rdev->desc->enable_mask; + + if (rdev->desc->enable_is_inverted) { + if (rdev->desc->enable_val) + return val != rdev->desc->enable_val; + return val == 0; + } else { + if (rdev->desc->enable_val) + return val == rdev->desc->enable_val; + return val != 0; + } +} +EXPORT_SYMBOL_GPL(regulator_is_enabled_regmap); + +/** + * regulator_enable_regmap - standard enable() for regmap users + * + * @rdev: regulator to operate on + * + * Regulators that use regmap for their register I/O can set the + * enable_reg and enable_mask fields in their descriptor and then use + * this as their enable() operation, saving some code. + */ +int regulator_enable_regmap(struct regulator_dev *rdev) +{ + unsigned int val; + + if (rdev->desc->enable_is_inverted) { + val = rdev->desc->disable_val; + } else { + val = rdev->desc->enable_val; + if (!val) + val = rdev->desc->enable_mask; + } + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, val); +} +EXPORT_SYMBOL_GPL(regulator_enable_regmap); + +/** + * regulator_disable_regmap - standard disable() for regmap users + * + * @rdev: regulator to operate on + * + * Regulators that use regmap for their register I/O can set the + * enable_reg and enable_mask fields in their descriptor and then use + * this as their disable() operation, saving some code. + */ +int regulator_disable_regmap(struct regulator_dev *rdev) +{ + unsigned int val; + + if (rdev->desc->enable_is_inverted) { + val = rdev->desc->enable_val; + if (!val) + val = rdev->desc->enable_mask; + } else { + val = rdev->desc->disable_val; + } + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, val); +} +EXPORT_SYMBOL_GPL(regulator_disable_regmap); + +/** + * regulator_get_voltage_sel_regmap - standard get_voltage_sel for regmap users + * + * @rdev: regulator to operate on + * + * Regulators that use regmap for their register I/O can set the + * vsel_reg and vsel_mask fields in their descriptor and then use this + * as their get_voltage_vsel operation, saving some code. + */ +int regulator_get_voltage_sel_regmap(struct regulator_dev *rdev) +{ + unsigned int val; + int ret; + + ret = regmap_read(rdev->regmap, rdev->desc->vsel_reg, &val); + if (ret != 0) + return ret; + + val &= rdev->desc->vsel_mask; + val >>= ffs(rdev->desc->vsel_mask) - 1; + + return val; +} +EXPORT_SYMBOL_GPL(regulator_get_voltage_sel_regmap); + +/** + * regulator_set_voltage_sel_regmap - standard set_voltage_sel for regmap users + * + * @rdev: regulator to operate on + * @sel: Selector to set + * + * Regulators that use regmap for their register I/O can set the + * vsel_reg and vsel_mask fields in their descriptor and then use this + * as their set_voltage_vsel operation, saving some code. + */ +int regulator_set_voltage_sel_regmap(struct regulator_dev *rdev, unsigned sel) +{ + int ret; + + sel <<= ffs(rdev->desc->vsel_mask) - 1; + + ret = regmap_update_bits(rdev->regmap, rdev->desc->vsel_reg, + rdev->desc->vsel_mask, sel); + if (ret) + return ret; + + if (rdev->desc->apply_bit) + ret = regmap_update_bits(rdev->regmap, rdev->desc->apply_reg, + rdev->desc->apply_bit, + rdev->desc->apply_bit); + return ret; +} +EXPORT_SYMBOL_GPL(regulator_set_voltage_sel_regmap); + +/** + * regulator_map_voltage_iterate - map_voltage() based on list_voltage() + * + * @rdev: Regulator to operate on + * @min_uV: Lower bound for voltage + * @max_uV: Upper bound for voltage + * + * Drivers implementing set_voltage_sel() and list_voltage() can use + * this as their map_voltage() operation. It will find a suitable + * voltage by calling list_voltage() until it gets something in bounds + * for the requested voltages. + */ +int regulator_map_voltage_iterate(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + int best_val = INT_MAX; + int selector = 0; + int i, ret; + + /* Find the smallest voltage that falls within the specified + * range. + */ + for (i = 0; i < rdev->desc->n_voltages; i++) { + ret = rdev->desc->ops->list_voltage(rdev, i); + if (ret < 0) + continue; + + if (ret < best_val && ret >= min_uV && ret <= max_uV) { + best_val = ret; + selector = i; + } + } + + if (best_val != INT_MAX) + return selector; + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(regulator_map_voltage_iterate); + +/** + * regulator_map_voltage_ascend - map_voltage() for ascendant voltage list + * + * @rdev: Regulator to operate on + * @min_uV: Lower bound for voltage + * @max_uV: Upper bound for voltage + * + * Drivers that have ascendant voltage list can use this as their + * map_voltage() operation. + */ +int regulator_map_voltage_ascend(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + int i, ret; + + for (i = 0; i < rdev->desc->n_voltages; i++) { + ret = rdev->desc->ops->list_voltage(rdev, i); + if (ret < 0) + continue; + + if (ret > max_uV) + break; + + if (ret >= min_uV && ret <= max_uV) + return i; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(regulator_map_voltage_ascend); + +/** + * regulator_map_voltage_linear - map_voltage() for simple linear mappings + * + * @rdev: Regulator to operate on + * @min_uV: Lower bound for voltage + * @max_uV: Upper bound for voltage + * + * Drivers providing min_uV and uV_step in their regulator_desc can + * use this as their map_voltage() operation. + */ +int regulator_map_voltage_linear(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + int ret, voltage; + + /* Allow uV_step to be 0 for fixed voltage */ + if (rdev->desc->n_voltages == 1 && rdev->desc->uV_step == 0) { + if (min_uV <= rdev->desc->min_uV && rdev->desc->min_uV <= max_uV) + return 0; + else + return -EINVAL; + } + + if (!rdev->desc->uV_step) { + BUG_ON(!rdev->desc->uV_step); + return -EINVAL; + } + + if (min_uV < rdev->desc->min_uV) + min_uV = rdev->desc->min_uV; + + ret = DIV_ROUND_UP(min_uV - rdev->desc->min_uV, rdev->desc->uV_step); + if (ret < 0) + return ret; + + ret += rdev->desc->linear_min_sel; + + /* Map back into a voltage to verify we're still in bounds */ + voltage = rdev->desc->ops->list_voltage(rdev, ret); + if (voltage < min_uV || voltage > max_uV) + return -EINVAL; + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_map_voltage_linear); + +/** + * regulator_map_voltage_linear_range - map_voltage() for multiple linear ranges + * + * @rdev: Regulator to operate on + * @min_uV: Lower bound for voltage + * @max_uV: Upper bound for voltage + * + * Drivers providing linear_ranges in their descriptor can use this as + * their map_voltage() callback. + */ +int regulator_map_voltage_linear_range(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + const struct regulator_linear_range *range; + int ret = -EINVAL; + int voltage, i; + + if (!rdev->desc->n_linear_ranges) { + BUG_ON(!rdev->desc->n_linear_ranges); + return -EINVAL; + } + + for (i = 0; i < rdev->desc->n_linear_ranges; i++) { + int linear_max_uV; + + range = &rdev->desc->linear_ranges[i]; + linear_max_uV = range->min_uV + + (range->max_sel - range->min_sel) * range->uV_step; + + if (!(min_uV <= linear_max_uV && max_uV >= range->min_uV)) + continue; + + if (min_uV <= range->min_uV) + min_uV = range->min_uV; + + /* range->uV_step == 0 means fixed voltage range */ + if (range->uV_step == 0) { + ret = 0; + } else { + ret = DIV_ROUND_UP(min_uV - range->min_uV, + range->uV_step); + if (ret < 0) + return ret; + } + + ret += range->min_sel; + + break; + } + + if (i == rdev->desc->n_linear_ranges) + return -EINVAL; + + /* Map back into a voltage to verify we're still in bounds */ + voltage = rdev->desc->ops->list_voltage(rdev, ret); + if (voltage < min_uV || voltage > max_uV) + return -EINVAL; + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_map_voltage_linear_range); + +/** + * regulator_list_voltage_linear - List voltages with simple calculation + * + * @rdev: Regulator device + * @selector: Selector to convert into a voltage + * + * Regulators with a simple linear mapping between voltages and + * selectors can set min_uV and uV_step in the regulator descriptor + * and then use this function as their list_voltage() operation, + */ +int regulator_list_voltage_linear(struct regulator_dev *rdev, + unsigned int selector) +{ + if (selector >= rdev->desc->n_voltages) + return -EINVAL; + if (selector < rdev->desc->linear_min_sel) + return 0; + + selector -= rdev->desc->linear_min_sel; + + return rdev->desc->min_uV + (rdev->desc->uV_step * selector); +} +EXPORT_SYMBOL_GPL(regulator_list_voltage_linear); + +/** + * regulator_list_voltage_linear_range - List voltages for linear ranges + * + * @rdev: Regulator device + * @selector: Selector to convert into a voltage + * + * Regulators with a series of simple linear mappings between voltages + * and selectors can set linear_ranges in the regulator descriptor and + * then use this function as their list_voltage() operation, + */ +int regulator_list_voltage_linear_range(struct regulator_dev *rdev, + unsigned int selector) +{ + const struct regulator_linear_range *range; + int i; + + if (!rdev->desc->n_linear_ranges) { + BUG_ON(!rdev->desc->n_linear_ranges); + return -EINVAL; + } + + for (i = 0; i < rdev->desc->n_linear_ranges; i++) { + range = &rdev->desc->linear_ranges[i]; + + if (!(selector >= range->min_sel && + selector <= range->max_sel)) + continue; + + selector -= range->min_sel; + + return range->min_uV + (range->uV_step * selector); + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(regulator_list_voltage_linear_range); + +/** + * regulator_list_voltage_table - List voltages with table based mapping + * + * @rdev: Regulator device + * @selector: Selector to convert into a voltage + * + * Regulators with table based mapping between voltages and + * selectors can set volt_table in the regulator descriptor + * and then use this function as their list_voltage() operation. + */ +int regulator_list_voltage_table(struct regulator_dev *rdev, + unsigned int selector) +{ + if (!rdev->desc->volt_table) { + BUG_ON(!rdev->desc->volt_table); + return -EINVAL; + } + + if (selector >= rdev->desc->n_voltages) + return -EINVAL; + + return rdev->desc->volt_table[selector]; +} +EXPORT_SYMBOL_GPL(regulator_list_voltage_table); + +/** + * regulator_set_bypass_regmap - Default set_bypass() using regmap + * + * @rdev: device to operate on. + * @enable: state to set. + */ +int regulator_set_bypass_regmap(struct regulator_dev *rdev, bool enable) +{ + unsigned int val; + + if (enable) { + val = rdev->desc->bypass_val_on; + if (!val) + val = rdev->desc->bypass_mask; + } else { + val = rdev->desc->bypass_val_off; + } + + return regmap_update_bits(rdev->regmap, rdev->desc->bypass_reg, + rdev->desc->bypass_mask, val); +} +EXPORT_SYMBOL_GPL(regulator_set_bypass_regmap); + +/** + * regulator_set_soft_start_regmap - Default set_soft_start() using regmap + * + * @rdev: device to operate on. + */ +int regulator_set_soft_start_regmap(struct regulator_dev *rdev) +{ + unsigned int val; + + val = rdev->desc->soft_start_val_on; + if (!val) + val = rdev->desc->soft_start_mask; + + return regmap_update_bits(rdev->regmap, rdev->desc->soft_start_reg, + rdev->desc->soft_start_mask, val); +} +EXPORT_SYMBOL_GPL(regulator_set_soft_start_regmap); + +/** + * regulator_set_pull_down_regmap - Default set_pull_down() using regmap + * + * @rdev: device to operate on. + */ +int regulator_set_pull_down_regmap(struct regulator_dev *rdev) +{ + unsigned int val; + + val = rdev->desc->pull_down_val_on; + if (!val) + val = rdev->desc->pull_down_mask; + + return regmap_update_bits(rdev->regmap, rdev->desc->pull_down_reg, + rdev->desc->pull_down_mask, val); +} +EXPORT_SYMBOL_GPL(regulator_set_pull_down_regmap); + +/** + * regulator_get_bypass_regmap - Default get_bypass() using regmap + * + * @rdev: device to operate on. + * @enable: current state. + */ +int regulator_get_bypass_regmap(struct regulator_dev *rdev, bool *enable) +{ + unsigned int val; + unsigned int val_on = rdev->desc->bypass_val_on; + int ret; + + ret = regmap_read(rdev->regmap, rdev->desc->bypass_reg, &val); + if (ret != 0) + return ret; + + if (!val_on) + val_on = rdev->desc->bypass_mask; + + *enable = (val & rdev->desc->bypass_mask) == val_on; + + return 0; +} +EXPORT_SYMBOL_GPL(regulator_get_bypass_regmap); + +/** + * regulator_set_active_discharge_regmap - Default set_active_discharge() + * using regmap + * + * @rdev: device to operate on. + * @enable: state to set, 0 to disable and 1 to enable. + */ +int regulator_set_active_discharge_regmap(struct regulator_dev *rdev, + bool enable) +{ + unsigned int val; + + if (enable) + val = rdev->desc->active_discharge_on; + else + val = rdev->desc->active_discharge_off; + + return regmap_update_bits(rdev->regmap, + rdev->desc->active_discharge_reg, + rdev->desc->active_discharge_mask, val); +} +EXPORT_SYMBOL_GPL(regulator_set_active_discharge_regmap); diff --git a/drivers/regulator/hi6421-regulator.c b/drivers/regulator/hi6421-regulator.c new file mode 100644 index 000000000..259c3a865 --- /dev/null +++ b/drivers/regulator/hi6421-regulator.c @@ -0,0 +1,641 @@ +/* + * Device driver for regulators in Hi6421 IC + * + * Copyright (c) <2011-2014> HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright (c) <2013-2014> Linaro Ltd. + * http://www.linaro.org + * + * Author: Guodong Xu <guodong.xu@linaro.org> + * + * 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/slab.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/hi6421-pmic.h> + +/* + * struct hi6421_regulator_pdata - Hi6421 regulator data of platform device + * @lock: mutex to serialize regulator enable + */ +struct hi6421_regulator_pdata { + struct mutex lock; +}; + +/* + * struct hi6421_regulator_info - hi6421 regulator information + * @desc: regulator description + * @mode_mask: ECO mode bitmask of LDOs; for BUCKs, this masks sleep + * @eco_microamp: eco mode load upper limit (in uA), valid for LDOs only + */ +struct hi6421_regulator_info { + struct regulator_desc desc; + u8 mode_mask; + u32 eco_microamp; +}; + +/* HI6421 regulators */ +enum hi6421_regulator_id { + HI6421_LDO0, + HI6421_LDO1, + HI6421_LDO2, + HI6421_LDO3, + HI6421_LDO4, + HI6421_LDO5, + HI6421_LDO6, + HI6421_LDO7, + HI6421_LDO8, + HI6421_LDO9, + HI6421_LDO10, + HI6421_LDO11, + HI6421_LDO12, + HI6421_LDO13, + HI6421_LDO14, + HI6421_LDO15, + HI6421_LDO16, + HI6421_LDO17, + HI6421_LDO18, + HI6421_LDO19, + HI6421_LDO20, + HI6421_LDOAUDIO, + HI6421_BUCK0, + HI6421_BUCK1, + HI6421_BUCK2, + HI6421_BUCK3, + HI6421_BUCK4, + HI6421_BUCK5, + HI6421_NUM_REGULATORS, +}; + +#define HI6421_REGULATOR_OF_MATCH(_name, id) \ +{ \ + .name = #_name, \ + .driver_data = (void *) HI6421_##id, \ +} + +static struct of_regulator_match hi6421_regulator_match[] = { + HI6421_REGULATOR_OF_MATCH(hi6421_vout0, LDO0), + HI6421_REGULATOR_OF_MATCH(hi6421_vout1, LDO1), + HI6421_REGULATOR_OF_MATCH(hi6421_vout2, LDO2), + HI6421_REGULATOR_OF_MATCH(hi6421_vout3, LDO3), + HI6421_REGULATOR_OF_MATCH(hi6421_vout4, LDO4), + HI6421_REGULATOR_OF_MATCH(hi6421_vout5, LDO5), + HI6421_REGULATOR_OF_MATCH(hi6421_vout6, LDO6), + HI6421_REGULATOR_OF_MATCH(hi6421_vout7, LDO7), + HI6421_REGULATOR_OF_MATCH(hi6421_vout8, LDO8), + HI6421_REGULATOR_OF_MATCH(hi6421_vout9, LDO9), + HI6421_REGULATOR_OF_MATCH(hi6421_vout10, LDO10), + HI6421_REGULATOR_OF_MATCH(hi6421_vout11, LDO11), + HI6421_REGULATOR_OF_MATCH(hi6421_vout12, LDO12), + HI6421_REGULATOR_OF_MATCH(hi6421_vout13, LDO13), + HI6421_REGULATOR_OF_MATCH(hi6421_vout14, LDO14), + HI6421_REGULATOR_OF_MATCH(hi6421_vout15, LDO15), + HI6421_REGULATOR_OF_MATCH(hi6421_vout16, LDO16), + HI6421_REGULATOR_OF_MATCH(hi6421_vout17, LDO17), + HI6421_REGULATOR_OF_MATCH(hi6421_vout18, LDO18), + HI6421_REGULATOR_OF_MATCH(hi6421_vout19, LDO19), + HI6421_REGULATOR_OF_MATCH(hi6421_vout20, LDO20), + HI6421_REGULATOR_OF_MATCH(hi6421_vout_audio, LDOAUDIO), + HI6421_REGULATOR_OF_MATCH(hi6421_buck0, BUCK0), + HI6421_REGULATOR_OF_MATCH(hi6421_buck1, BUCK1), + HI6421_REGULATOR_OF_MATCH(hi6421_buck2, BUCK2), + HI6421_REGULATOR_OF_MATCH(hi6421_buck3, BUCK3), + HI6421_REGULATOR_OF_MATCH(hi6421_buck4, BUCK4), + HI6421_REGULATOR_OF_MATCH(hi6421_buck5, BUCK5), +}; + +/* LDO 0, 4~7, 9~14, 16~20 have same voltage table. */ +static const unsigned int ldo_0_voltages[] = { + 1500000, 1800000, 2400000, 2500000, + 2600000, 2700000, 2850000, 3000000, +}; + +/* LDO 8, 15 have same voltage table. */ +static const unsigned int ldo_8_voltages[] = { + 1500000, 1800000, 2400000, 2600000, + 2700000, 2850000, 3000000, 3300000, +}; + +/* Ranges are sorted in ascending order. */ +static const struct regulator_linear_range ldo_audio_volt_range[] = { + REGULATOR_LINEAR_RANGE(2800000, 0, 3, 50000), + REGULATOR_LINEAR_RANGE(3000000, 4, 7, 100000), +}; + +static const unsigned int buck_3_voltages[] = { + 950000, 1050000, 1100000, 1117000, + 1134000, 1150000, 1167000, 1200000, +}; + +static const unsigned int buck_4_voltages[] = { + 1150000, 1200000, 1250000, 1350000, + 1700000, 1800000, 1900000, 2000000, +}; + +static const unsigned int buck_5_voltages[] = { + 1150000, 1200000, 1250000, 1350000, + 1600000, 1700000, 1800000, 1900000, +}; + +static const struct regulator_ops hi6421_ldo_ops; +static const struct regulator_ops hi6421_ldo_linear_ops; +static const struct regulator_ops hi6421_ldo_linear_range_ops; +static const struct regulator_ops hi6421_buck012_ops; +static const struct regulator_ops hi6421_buck345_ops; + +#define HI6421_LDO_ENABLE_TIME (350) +/* + * _id - LDO id name string + * v_table - voltage table + * vreg - voltage select register + * vmask - voltage select mask + * ereg - enable register + * emask - enable mask + * odelay - off/on delay time in uS + * ecomask - eco mode mask + * ecoamp - eco mode load uppler limit in uA + */ +#define HI6421_LDO(_id, v_table, vreg, vmask, ereg, emask, \ + odelay, ecomask, ecoamp) \ + [HI6421_##_id] = { \ + .desc = { \ + .name = #_id, \ + .ops = &hi6421_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = HI6421_##_id, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(v_table), \ + .volt_table = v_table, \ + .vsel_reg = HI6421_REG_TO_BUS_ADDR(vreg), \ + .vsel_mask = vmask, \ + .enable_reg = HI6421_REG_TO_BUS_ADDR(ereg), \ + .enable_mask = emask, \ + .enable_time = HI6421_LDO_ENABLE_TIME, \ + .off_on_delay = odelay, \ + }, \ + .mode_mask = ecomask, \ + .eco_microamp = ecoamp, \ + } + +/* HI6421 LDO1~3 are linear voltage regulators at fixed uV_step + * + * _id - LDO id name string + * _min_uV - minimum voltage supported in uV + * n_volt - number of votages available + * vstep - voltage increase in each linear step in uV + * vreg - voltage select register + * vmask - voltage select mask + * ereg - enable register + * emask - enable mask + * odelay - off/on delay time in uS + * ecomask - eco mode mask + * ecoamp - eco mode load uppler limit in uA + */ +#define HI6421_LDO_LINEAR(_id, _min_uV, n_volt, vstep, vreg, vmask, \ + ereg, emask, odelay, ecomask, ecoamp) \ + [HI6421_##_id] = { \ + .desc = { \ + .name = #_id, \ + .ops = &hi6421_ldo_linear_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = HI6421_##_id, \ + .owner = THIS_MODULE, \ + .min_uV = _min_uV, \ + .n_voltages = n_volt, \ + .uV_step = vstep, \ + .vsel_reg = HI6421_REG_TO_BUS_ADDR(vreg), \ + .vsel_mask = vmask, \ + .enable_reg = HI6421_REG_TO_BUS_ADDR(ereg), \ + .enable_mask = emask, \ + .enable_time = HI6421_LDO_ENABLE_TIME, \ + .off_on_delay = odelay, \ + }, \ + .mode_mask = ecomask, \ + .eco_microamp = ecoamp, \ + } + +/* HI6421 LDOAUDIO is a linear voltage regulator with two 4-step ranges + * + * _id - LDO id name string + * n_volt - number of votages available + * volt_ranges - array of regulator_linear_range + * vstep - voltage increase in each linear step in uV + * vreg - voltage select register + * vmask - voltage select mask + * ereg - enable register + * emask - enable mask + * odelay - off/on delay time in uS + * ecomask - eco mode mask + * ecoamp - eco mode load uppler limit in uA + */ +#define HI6421_LDO_LINEAR_RANGE(_id, n_volt, volt_ranges, vreg, vmask, \ + ereg, emask, odelay, ecomask, ecoamp) \ + [HI6421_##_id] = { \ + .desc = { \ + .name = #_id, \ + .ops = &hi6421_ldo_linear_range_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = HI6421_##_id, \ + .owner = THIS_MODULE, \ + .n_voltages = n_volt, \ + .linear_ranges = volt_ranges, \ + .n_linear_ranges = ARRAY_SIZE(volt_ranges), \ + .vsel_reg = HI6421_REG_TO_BUS_ADDR(vreg), \ + .vsel_mask = vmask, \ + .enable_reg = HI6421_REG_TO_BUS_ADDR(ereg), \ + .enable_mask = emask, \ + .enable_time = HI6421_LDO_ENABLE_TIME, \ + .off_on_delay = odelay, \ + }, \ + .mode_mask = ecomask, \ + .eco_microamp = ecoamp, \ + } + +/* HI6421 BUCK0/1/2 are linear voltage regulators at fixed uV_step + * + * _id - BUCK0/1/2 id name string + * vreg - voltage select register + * vmask - voltage select mask + * ereg - enable register + * emask - enable mask + * sleepmask - mask of sleep mode + * etime - enable time + * odelay - off/on delay time in uS + */ +#define HI6421_BUCK012(_id, vreg, vmask, ereg, emask, sleepmask, \ + etime, odelay) \ + [HI6421_##_id] = { \ + .desc = { \ + .name = #_id, \ + .ops = &hi6421_buck012_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = HI6421_##_id, \ + .owner = THIS_MODULE, \ + .min_uV = 700000, \ + .n_voltages = 128, \ + .uV_step = 7086, \ + .vsel_reg = HI6421_REG_TO_BUS_ADDR(vreg), \ + .vsel_mask = vmask, \ + .enable_reg = HI6421_REG_TO_BUS_ADDR(ereg), \ + .enable_mask = emask, \ + .enable_time = etime, \ + .off_on_delay = odelay, \ + }, \ + .mode_mask = sleepmask, \ + } + +/* HI6421 BUCK3/4/5 share similar configurations as LDOs, with exception + * that it supports SLEEP mode, so has different .ops. + * + * _id - LDO id name string + * v_table - voltage table + * vreg - voltage select register + * vmask - voltage select mask + * ereg - enable register + * emask - enable mask + * odelay - off/on delay time in uS + * sleepmask - mask of sleep mode + */ +#define HI6421_BUCK345(_id, v_table, vreg, vmask, ereg, emask, \ + odelay, sleepmask) \ + [HI6421_##_id] = { \ + .desc = { \ + .name = #_id, \ + .ops = &hi6421_buck345_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = HI6421_##_id, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(v_table), \ + .volt_table = v_table, \ + .vsel_reg = HI6421_REG_TO_BUS_ADDR(vreg), \ + .vsel_mask = vmask, \ + .enable_reg = HI6421_REG_TO_BUS_ADDR(ereg), \ + .enable_mask = emask, \ + .enable_time = HI6421_LDO_ENABLE_TIME, \ + .off_on_delay = odelay, \ + }, \ + .mode_mask = sleepmask, \ + } + +/* HI6421 regulator information */ +static struct hi6421_regulator_info + hi6421_regulator_info[HI6421_NUM_REGULATORS] = { + HI6421_LDO(LDO0, ldo_0_voltages, 0x20, 0x07, 0x20, 0x10, + 10000, 0x20, 8000), + HI6421_LDO_LINEAR(LDO1, 1700000, 4, 100000, 0x21, 0x03, 0x21, 0x10, + 10000, 0x20, 5000), + HI6421_LDO_LINEAR(LDO2, 1050000, 8, 50000, 0x22, 0x07, 0x22, 0x10, + 20000, 0x20, 8000), + HI6421_LDO_LINEAR(LDO3, 1050000, 8, 50000, 0x23, 0x07, 0x23, 0x10, + 20000, 0x20, 8000), + HI6421_LDO(LDO4, ldo_0_voltages, 0x24, 0x07, 0x24, 0x10, + 20000, 0x20, 8000), + HI6421_LDO(LDO5, ldo_0_voltages, 0x25, 0x07, 0x25, 0x10, + 20000, 0x20, 8000), + HI6421_LDO(LDO6, ldo_0_voltages, 0x26, 0x07, 0x26, 0x10, + 20000, 0x20, 8000), + HI6421_LDO(LDO7, ldo_0_voltages, 0x27, 0x07, 0x27, 0x10, + 20000, 0x20, 5000), + HI6421_LDO(LDO8, ldo_8_voltages, 0x28, 0x07, 0x28, 0x10, + 20000, 0x20, 8000), + HI6421_LDO(LDO9, ldo_0_voltages, 0x29, 0x07, 0x29, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO10, ldo_0_voltages, 0x2a, 0x07, 0x2a, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO11, ldo_0_voltages, 0x2b, 0x07, 0x2b, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO12, ldo_0_voltages, 0x2c, 0x07, 0x2c, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO13, ldo_0_voltages, 0x2d, 0x07, 0x2d, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO14, ldo_0_voltages, 0x2e, 0x07, 0x2e, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO15, ldo_8_voltages, 0x2f, 0x07, 0x2f, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO16, ldo_0_voltages, 0x30, 0x07, 0x30, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO17, ldo_0_voltages, 0x31, 0x07, 0x31, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO18, ldo_0_voltages, 0x32, 0x07, 0x32, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO19, ldo_0_voltages, 0x33, 0x07, 0x33, 0x10, + 40000, 0x20, 8000), + HI6421_LDO(LDO20, ldo_0_voltages, 0x34, 0x07, 0x34, 0x10, + 40000, 0x20, 8000), + HI6421_LDO_LINEAR_RANGE(LDOAUDIO, 8, ldo_audio_volt_range, 0x36, + 0x70, 0x36, 0x01, 40000, 0x02, 5000), + HI6421_BUCK012(BUCK0, 0x0d, 0x7f, 0x0c, 0x01, 0x10, 400, 20000), + HI6421_BUCK012(BUCK1, 0x0f, 0x7f, 0x0e, 0x01, 0x10, 400, 20000), + HI6421_BUCK012(BUCK2, 0x11, 0x7f, 0x10, 0x01, 0x10, 350, 100), + HI6421_BUCK345(BUCK3, buck_3_voltages, 0x13, 0x07, 0x12, 0x01, + 20000, 0x10), + HI6421_BUCK345(BUCK4, buck_4_voltages, 0x15, 0x07, 0x14, 0x01, + 20000, 0x10), + HI6421_BUCK345(BUCK5, buck_5_voltages, 0x17, 0x07, 0x16, 0x01, + 20000, 0x10), +}; + +static int hi6421_regulator_enable(struct regulator_dev *rdev) +{ + struct hi6421_regulator_pdata *pdata; + + pdata = dev_get_drvdata(rdev->dev.parent); + /* hi6421 spec requires regulator enablement must be serialized: + * - Because when BUCK, LDO switching from off to on, it will have + * a huge instantaneous current; so you can not turn on two or + * more LDO or BUCKs simultaneously, or it may burn the chip. + */ + mutex_lock(&pdata->lock); + + /* call regulator regmap helper */ + regulator_enable_regmap(rdev); + + mutex_unlock(&pdata->lock); + return 0; +} + +static unsigned int hi6421_regulator_ldo_get_mode(struct regulator_dev *rdev) +{ + struct hi6421_regulator_info *info = rdev_get_drvdata(rdev); + u32 reg_val; + + regmap_read(rdev->regmap, rdev->desc->enable_reg, ®_val); + if (reg_val & info->mode_mask) + return REGULATOR_MODE_IDLE; + + return REGULATOR_MODE_NORMAL; +} + +static unsigned int hi6421_regulator_buck_get_mode(struct regulator_dev *rdev) +{ + struct hi6421_regulator_info *info = rdev_get_drvdata(rdev); + u32 reg_val; + + regmap_read(rdev->regmap, rdev->desc->enable_reg, ®_val); + if (reg_val & info->mode_mask) + return REGULATOR_MODE_STANDBY; + + return REGULATOR_MODE_NORMAL; +} + +static int hi6421_regulator_ldo_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct hi6421_regulator_info *info = rdev_get_drvdata(rdev); + u32 new_mode; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + new_mode = 0; + break; + case REGULATOR_MODE_IDLE: + new_mode = info->mode_mask; + break; + default: + return -EINVAL; + } + + /* set mode */ + regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + info->mode_mask, new_mode); + + return 0; +} + +static int hi6421_regulator_buck_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct hi6421_regulator_info *info = rdev_get_drvdata(rdev); + u32 new_mode; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + new_mode = 0; + break; + case REGULATOR_MODE_STANDBY: + new_mode = info->mode_mask; + break; + default: + return -EINVAL; + } + + /* set mode */ + regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + info->mode_mask, new_mode); + + return 0; +} + +static unsigned int +hi6421_regulator_ldo_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, int load_uA) +{ + struct hi6421_regulator_info *info = rdev_get_drvdata(rdev); + + if (load_uA > info->eco_microamp) + return REGULATOR_MODE_NORMAL; + + return REGULATOR_MODE_IDLE; +} + +static const struct regulator_ops hi6421_ldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = hi6421_regulator_enable, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_mode = hi6421_regulator_ldo_get_mode, + .set_mode = hi6421_regulator_ldo_set_mode, + .get_optimum_mode = hi6421_regulator_ldo_get_optimum_mode, +}; + +static const struct regulator_ops hi6421_ldo_linear_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = hi6421_regulator_enable, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_mode = hi6421_regulator_ldo_get_mode, + .set_mode = hi6421_regulator_ldo_set_mode, + .get_optimum_mode = hi6421_regulator_ldo_get_optimum_mode, +}; + +static const struct regulator_ops hi6421_ldo_linear_range_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = hi6421_regulator_enable, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_mode = hi6421_regulator_ldo_get_mode, + .set_mode = hi6421_regulator_ldo_set_mode, + .get_optimum_mode = hi6421_regulator_ldo_get_optimum_mode, +}; + +static const struct regulator_ops hi6421_buck012_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = hi6421_regulator_enable, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_mode = hi6421_regulator_buck_get_mode, + .set_mode = hi6421_regulator_buck_set_mode, +}; + +static const struct regulator_ops hi6421_buck345_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = hi6421_regulator_enable, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_mode = hi6421_regulator_buck_get_mode, + .set_mode = hi6421_regulator_buck_set_mode, +}; + +static int hi6421_regulator_register(struct platform_device *pdev, + struct regmap *rmap, + struct regulator_init_data *init_data, + int id, struct device_node *np) +{ + struct hi6421_regulator_info *info = NULL; + struct regulator_config config = { }; + struct regulator_dev *rdev; + + /* assign per-regulator data */ + info = &hi6421_regulator_info[id]; + + config.dev = &pdev->dev; + config.init_data = init_data; + config.driver_data = info; + config.regmap = rmap; + config.of_node = np; + + /* register regulator with framework */ + rdev = devm_regulator_register(&pdev->dev, &info->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + info->desc.name); + return PTR_ERR(rdev); + } + + return 0; +} + +static int hi6421_regulator_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np; + struct hi6421_pmic *pmic; + struct hi6421_regulator_pdata *pdata; + int i, ret = 0; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + mutex_init(&pdata->lock); + platform_set_drvdata(pdev, pdata); + + np = of_get_child_by_name(dev->parent->of_node, "regulators"); + if (!np) + return -ENODEV; + + ret = of_regulator_match(dev, np, + hi6421_regulator_match, + ARRAY_SIZE(hi6421_regulator_match)); + of_node_put(np); + if (ret < 0) { + dev_err(dev, "Error parsing regulator init data: %d\n", ret); + return ret; + } + + pmic = dev_get_drvdata(dev->parent); + + for (i = 0; i < ARRAY_SIZE(hi6421_regulator_info); i++) { + ret = hi6421_regulator_register(pdev, pmic->regmap, + hi6421_regulator_match[i].init_data, i, + hi6421_regulator_match[i].of_node); + if (ret) + return ret; + } + + return 0; +} + +static const struct platform_device_id hi6421_regulator_table[] = { + { .name = "hi6421-regulator" }, + {}, +}; +MODULE_DEVICE_TABLE(platform, hi6421_regulator_table); + +static struct platform_driver hi6421_regulator_driver = { + .id_table = hi6421_regulator_table, + .driver = { + .name = "hi6421-regulator", + }, + .probe = hi6421_regulator_probe, +}; +module_platform_driver(hi6421_regulator_driver); + +MODULE_AUTHOR("Guodong Xu <guodong.xu@linaro.org>"); +MODULE_DESCRIPTION("Hi6421 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/hi6421v530-regulator.c b/drivers/regulator/hi6421v530-regulator.c new file mode 100644 index 000000000..c09bc7153 --- /dev/null +++ b/drivers/regulator/hi6421v530-regulator.c @@ -0,0 +1,214 @@ +/* + * Device driver for regulators in Hi6421V530 IC + * + * Copyright (c) <2017> HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright (c) <2017> Linaro Ltd. + * http://www.linaro.org + * + * Author: Wang Xiaoyin <hw.wangxiaoyin@hisilicon.com> + * Guodong Xu <guodong.xu@linaro.org> + * + * 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/mfd/hi6421-pmic.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> + +/* + * struct hi6421v530_regulator_info - hi6421v530 regulator information + * @desc: regulator description + * @mode_mask: ECO mode bitmask of LDOs; for BUCKs, this masks sleep + * @eco_microamp: eco mode load upper limit (in uA), valid for LDOs only + */ +struct hi6421v530_regulator_info { + struct regulator_desc rdesc; + u8 mode_mask; + u32 eco_microamp; +}; + +/* HI6421v530 regulators */ +enum hi6421v530_regulator_id { + HI6421V530_LDO3, + HI6421V530_LDO9, + HI6421V530_LDO11, + HI6421V530_LDO15, + HI6421V530_LDO16, +}; + +static const unsigned int ldo_3_voltages[] = { + 1800000, 1825000, 1850000, 1875000, + 1900000, 1925000, 1950000, 1975000, + 2000000, 2025000, 2050000, 2075000, + 2100000, 2125000, 2150000, 2200000, +}; + +static const unsigned int ldo_9_11_voltages[] = { + 1750000, 1800000, 1825000, 2800000, + 2850000, 2950000, 3000000, 3300000, +}; + +static const unsigned int ldo_15_16_voltages[] = { + 1750000, 1800000, 2400000, 2600000, + 2700000, 2850000, 2950000, 3000000, +}; + +static const struct regulator_ops hi6421v530_ldo_ops; + +#define HI6421V530_LDO_ENABLE_TIME (350) + +/* + * _id - LDO id name string + * v_table - voltage table + * vreg - voltage select register + * vmask - voltage select mask + * ereg - enable register + * emask - enable mask + * odelay - off/on delay time in uS + * ecomask - eco mode mask + * ecoamp - eco mode load uppler limit in uA + */ +#define HI6421V530_LDO(_ID, v_table, vreg, vmask, ereg, emask, \ + odelay, ecomask, ecoamp) { \ + .rdesc = { \ + .name = #_ID, \ + .of_match = of_match_ptr(#_ID), \ + .regulators_node = of_match_ptr("regulators"), \ + .ops = &hi6421v530_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = HI6421V530_##_ID, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(v_table), \ + .volt_table = v_table, \ + .vsel_reg = HI6421_REG_TO_BUS_ADDR(vreg), \ + .vsel_mask = vmask, \ + .enable_reg = HI6421_REG_TO_BUS_ADDR(ereg), \ + .enable_mask = emask, \ + .enable_time = HI6421V530_LDO_ENABLE_TIME, \ + .off_on_delay = odelay, \ + }, \ + .mode_mask = ecomask, \ + .eco_microamp = ecoamp, \ +} + +/* HI6421V530 regulator information */ + +static struct hi6421v530_regulator_info hi6421v530_regulator_info[] = { + HI6421V530_LDO(LDO3, ldo_3_voltages, 0x061, 0xf, 0x060, 0x2, + 20000, 0x6, 8000), + HI6421V530_LDO(LDO9, ldo_9_11_voltages, 0x06b, 0x7, 0x06a, 0x2, + 40000, 0x6, 8000), + HI6421V530_LDO(LDO11, ldo_9_11_voltages, 0x06f, 0x7, 0x06e, 0x2, + 40000, 0x6, 8000), + HI6421V530_LDO(LDO15, ldo_15_16_voltages, 0x077, 0x7, 0x076, 0x2, + 40000, 0x6, 8000), + HI6421V530_LDO(LDO16, ldo_15_16_voltages, 0x079, 0x7, 0x078, 0x2, + 40000, 0x6, 8000), +}; + +static unsigned int hi6421v530_regulator_ldo_get_mode( + struct regulator_dev *rdev) +{ + struct hi6421v530_regulator_info *info; + unsigned int reg_val; + + info = rdev_get_drvdata(rdev); + regmap_read(rdev->regmap, rdev->desc->enable_reg, ®_val); + + if (reg_val & (info->mode_mask)) + return REGULATOR_MODE_IDLE; + + return REGULATOR_MODE_NORMAL; +} + +static int hi6421v530_regulator_ldo_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct hi6421v530_regulator_info *info; + unsigned int new_mode; + + info = rdev_get_drvdata(rdev); + switch (mode) { + case REGULATOR_MODE_NORMAL: + new_mode = 0; + break; + case REGULATOR_MODE_IDLE: + new_mode = info->mode_mask; + break; + default: + return -EINVAL; + } + + regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + info->mode_mask, new_mode); + + return 0; +} + + +static const struct regulator_ops hi6421v530_ldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_mode = hi6421v530_regulator_ldo_get_mode, + .set_mode = hi6421v530_regulator_ldo_set_mode, +}; + +static int hi6421v530_regulator_probe(struct platform_device *pdev) +{ + struct hi6421_pmic *pmic; + struct regulator_dev *rdev; + struct regulator_config config = { }; + unsigned int i; + + pmic = dev_get_drvdata(pdev->dev.parent); + if (!pmic) { + dev_err(&pdev->dev, "no pmic in the regulator parent node\n"); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(hi6421v530_regulator_info); i++) { + config.dev = pdev->dev.parent; + config.regmap = pmic->regmap; + config.driver_data = &hi6421v530_regulator_info[i]; + + rdev = devm_regulator_register(&pdev->dev, + &hi6421v530_regulator_info[i].rdesc, + &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + hi6421v530_regulator_info[i].rdesc.name); + return PTR_ERR(rdev); + } + } + return 0; +} + +static const struct platform_device_id hi6421v530_regulator_table[] = { + { .name = "hi6421v530-regulator" }, + {}, +}; +MODULE_DEVICE_TABLE(platform, hi6421v530_regulator_table); + +static struct platform_driver hi6421v530_regulator_driver = { + .id_table = hi6421v530_regulator_table, + .driver = { + .name = "hi6421v530-regulator", + }, + .probe = hi6421v530_regulator_probe, +}; +module_platform_driver(hi6421v530_regulator_driver); + +MODULE_AUTHOR("Wang Xiaoyin <hw.wangxiaoyin@hisilicon.com>"); +MODULE_DESCRIPTION("Hi6421v530 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/hi655x-regulator.c b/drivers/regulator/hi655x-regulator.c new file mode 100644 index 000000000..36ae54b53 --- /dev/null +++ b/drivers/regulator/hi655x-regulator.c @@ -0,0 +1,234 @@ +/* + * Device driver for regulators in Hi655x IC + * + * Copyright (c) 2016 Hisilicon. + * + * Authors: + * Chen Feng <puck.chen@hisilicon.com> + * Fei Wang <w.f@huawei.com> + * + * 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/bitops.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/hi655x-pmic.h> + +struct hi655x_regulator { + unsigned int disable_reg; + unsigned int status_reg; + unsigned int ctrl_regs; + unsigned int ctrl_mask; + struct regulator_desc rdesc; +}; + +/* LDO7 & LDO10 */ +static const unsigned int ldo7_voltages[] = { + 1800000, 1850000, 2850000, 2900000, + 3000000, 3100000, 3200000, 3300000, +}; + +static const unsigned int ldo19_voltages[] = { + 1800000, 1850000, 1900000, 1750000, + 2800000, 2850000, 2900000, 3000000, +}; + +static const unsigned int ldo22_voltages[] = { + 900000, 1000000, 1050000, 1100000, + 1150000, 1175000, 1185000, 1200000, +}; + +enum hi655x_regulator_id { + HI655X_LDO0, + HI655X_LDO1, + HI655X_LDO2, + HI655X_LDO3, + HI655X_LDO4, + HI655X_LDO5, + HI655X_LDO6, + HI655X_LDO7, + HI655X_LDO8, + HI655X_LDO9, + HI655X_LDO10, + HI655X_LDO11, + HI655X_LDO12, + HI655X_LDO13, + HI655X_LDO14, + HI655X_LDO15, + HI655X_LDO16, + HI655X_LDO17, + HI655X_LDO18, + HI655X_LDO19, + HI655X_LDO20, + HI655X_LDO21, + HI655X_LDO22, +}; + +static int hi655x_is_enabled(struct regulator_dev *rdev) +{ + unsigned int value = 0; + + struct hi655x_regulator *regulator = rdev_get_drvdata(rdev); + + regmap_read(rdev->regmap, regulator->status_reg, &value); + return (value & BIT(regulator->ctrl_mask)); +} + +static int hi655x_disable(struct regulator_dev *rdev) +{ + int ret = 0; + + struct hi655x_regulator *regulator = rdev_get_drvdata(rdev); + + ret = regmap_write(rdev->regmap, regulator->disable_reg, + BIT(regulator->ctrl_mask)); + return ret; +} + +static const struct regulator_ops hi655x_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = hi655x_disable, + .is_enabled = hi655x_is_enabled, + .list_voltage = regulator_list_voltage_table, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_ops hi655x_ldo_linear_ops = { + .enable = regulator_enable_regmap, + .disable = hi655x_disable, + .is_enabled = hi655x_is_enabled, + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +#define HI655X_LDO(_ID, vreg, vmask, ereg, dreg, \ + sreg, cmask, vtable) { \ + .rdesc = { \ + .name = #_ID, \ + .of_match = of_match_ptr(#_ID), \ + .ops = &hi655x_regulator_ops, \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = HI655X_##_ID, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(vtable), \ + .volt_table = vtable, \ + .vsel_reg = HI655X_BUS_ADDR(vreg), \ + .vsel_mask = vmask, \ + .enable_reg = HI655X_BUS_ADDR(ereg), \ + .enable_mask = BIT(cmask), \ + }, \ + .disable_reg = HI655X_BUS_ADDR(dreg), \ + .status_reg = HI655X_BUS_ADDR(sreg), \ + .ctrl_mask = cmask, \ +} + +#define HI655X_LDO_LINEAR(_ID, vreg, vmask, ereg, dreg, \ + sreg, cmask, minv, nvolt, vstep) { \ + .rdesc = { \ + .name = #_ID, \ + .of_match = of_match_ptr(#_ID), \ + .ops = &hi655x_ldo_linear_ops, \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = HI655X_##_ID, \ + .owner = THIS_MODULE, \ + .min_uV = minv, \ + .n_voltages = nvolt, \ + .uV_step = vstep, \ + .vsel_reg = HI655X_BUS_ADDR(vreg), \ + .vsel_mask = vmask, \ + .enable_reg = HI655X_BUS_ADDR(ereg), \ + .enable_mask = BIT(cmask), \ + }, \ + .disable_reg = HI655X_BUS_ADDR(dreg), \ + .status_reg = HI655X_BUS_ADDR(sreg), \ + .ctrl_mask = cmask, \ +} + +static struct hi655x_regulator regulators[] = { + HI655X_LDO_LINEAR(LDO2, 0x72, 0x07, 0x29, 0x2a, 0x2b, 0x01, + 2500000, 8, 100000), + HI655X_LDO(LDO7, 0x78, 0x07, 0x29, 0x2a, 0x2b, 0x06, ldo7_voltages), + HI655X_LDO(LDO10, 0x78, 0x07, 0x29, 0x2a, 0x2b, 0x01, ldo7_voltages), + HI655X_LDO_LINEAR(LDO13, 0x7e, 0x07, 0x2c, 0x2d, 0x2e, 0x04, + 1600000, 8, 50000), + HI655X_LDO_LINEAR(LDO14, 0x7f, 0x07, 0x2c, 0x2d, 0x2e, 0x05, + 2500000, 8, 100000), + HI655X_LDO_LINEAR(LDO15, 0x80, 0x07, 0x2c, 0x2d, 0x2e, 0x06, + 1600000, 8, 50000), + HI655X_LDO_LINEAR(LDO17, 0x82, 0x07, 0x2f, 0x30, 0x31, 0x00, + 2500000, 8, 100000), + HI655X_LDO(LDO19, 0x84, 0x07, 0x2f, 0x30, 0x31, 0x02, ldo19_voltages), + HI655X_LDO_LINEAR(LDO21, 0x86, 0x07, 0x2f, 0x30, 0x31, 0x04, + 1650000, 8, 50000), + HI655X_LDO(LDO22, 0x87, 0x07, 0x2f, 0x30, 0x31, 0x05, ldo22_voltages), +}; + +static int hi655x_regulator_probe(struct platform_device *pdev) +{ + unsigned int i; + struct hi655x_regulator *regulator; + struct hi655x_pmic *pmic; + struct regulator_config config = { }; + struct regulator_dev *rdev; + + pmic = dev_get_drvdata(pdev->dev.parent); + if (!pmic) { + dev_err(&pdev->dev, "no pmic in the regulator parent node\n"); + return -ENODEV; + } + + regulator = devm_kzalloc(&pdev->dev, sizeof(*regulator), GFP_KERNEL); + if (!regulator) + return -ENOMEM; + + platform_set_drvdata(pdev, regulator); + + config.dev = pdev->dev.parent; + config.regmap = pmic->regmap; + config.driver_data = regulator; + for (i = 0; i < ARRAY_SIZE(regulators); i++) { + rdev = devm_regulator_register(&pdev->dev, + ®ulators[i].rdesc, + &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + regulator->rdesc.name); + return PTR_ERR(rdev); + } + } + return 0; +} + +static const struct platform_device_id hi655x_regulator_table[] = { + { .name = "hi655x-regulator" }, + {}, +}; +MODULE_DEVICE_TABLE(platform, hi655x_regulator_table); + +static struct platform_driver hi655x_regulator_driver = { + .id_table = hi655x_regulator_table, + .driver = { + .name = "hi655x-regulator", + }, + .probe = hi655x_regulator_probe, +}; +module_platform_driver(hi655x_regulator_driver); + +MODULE_AUTHOR("Chen Feng <puck.chen@hisilicon.com>"); +MODULE_DESCRIPTION("Hisilicon Hi655x regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/internal.h b/drivers/regulator/internal.h new file mode 100644 index 000000000..943926a15 --- /dev/null +++ b/drivers/regulator/internal.h @@ -0,0 +1,116 @@ +/* + * internal.h -- Voltage/Current Regulator framework internal code + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * Copyright 2008 SlimLogic Ltd. + * + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * + * 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. + * + */ + +#ifndef __REGULATOR_INTERNAL_H +#define __REGULATOR_INTERNAL_H + +#include <linux/suspend.h> + +#define REGULATOR_STATES_NUM (PM_SUSPEND_MAX + 1) + +struct regulator_voltage { + int min_uV; + int max_uV; +}; + +/* + * struct regulator + * + * One for each consumer device. + * @voltage - a voltage array for each state of runtime, i.e.: + * PM_SUSPEND_ON + * PM_SUSPEND_TO_IDLE + * PM_SUSPEND_STANDBY + * PM_SUSPEND_MEM + * PM_SUSPEND_MAX + */ +struct regulator { + struct device *dev; + struct list_head list; + unsigned int always_on:1; + unsigned int bypass:1; + int uA_load; + struct regulator_voltage voltage[REGULATOR_STATES_NUM]; + const char *supply_name; + struct device_attribute dev_attr; + struct regulator_dev *rdev; + struct dentry *debugfs; +}; + +extern struct class regulator_class; + +static inline struct regulator_dev *dev_to_rdev(struct device *dev) +{ + return container_of(dev, struct regulator_dev, dev); +} + +#ifdef CONFIG_OF +struct regulator_dev *of_find_regulator_by_node(struct device_node *np); +struct regulator_init_data *regulator_of_get_init_data(struct device *dev, + const struct regulator_desc *desc, + struct regulator_config *config, + struct device_node **node); + +struct regulator_dev *of_parse_coupled_regulator(struct regulator_dev *rdev, + int index); + +int of_get_n_coupled(struct regulator_dev *rdev); + +bool of_check_coupling_data(struct regulator_dev *rdev); + +#else +static inline struct regulator_dev * +of_find_regulator_by_node(struct device_node *np) +{ + return NULL; +} + +static inline struct regulator_init_data * +regulator_of_get_init_data(struct device *dev, + const struct regulator_desc *desc, + struct regulator_config *config, + struct device_node **node) +{ + return NULL; +} + +static inline struct regulator_dev * +of_parse_coupled_regulator(struct regulator_dev *rdev, + int index) +{ + return NULL; +} + +static inline int of_get_n_coupled(struct regulator_dev *rdev) +{ + return 0; +} + +static inline bool of_check_coupling_data(struct regulator_dev *rdev) +{ + return false; +} + +#endif +enum regulator_get_type { + NORMAL_GET, + EXCLUSIVE_GET, + OPTIONAL_GET, + MAX_GET_TYPE +}; + +struct regulator *_regulator_get(struct device *dev, const char *id, + enum regulator_get_type get_type); +#endif diff --git a/drivers/regulator/isl6271a-regulator.c b/drivers/regulator/isl6271a-regulator.c new file mode 100644 index 000000000..4abd8e9c8 --- /dev/null +++ b/drivers/regulator/isl6271a-regulator.c @@ -0,0 +1,179 @@ +/* + * isl6271a-regulator.c + * + * Support for Intersil ISL6271A voltage regulator + * + * Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.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/i2c.h> +#include <linux/slab.h> + +#define ISL6271A_VOLTAGE_MIN 850000 +#define ISL6271A_VOLTAGE_MAX 1600000 +#define ISL6271A_VOLTAGE_STEP 50000 + +/* PMIC details */ +struct isl_pmic { + struct i2c_client *client; + struct regulator_dev *rdev[3]; + struct mutex mtx; +}; + +static int isl6271a_get_voltage_sel(struct regulator_dev *dev) +{ + struct isl_pmic *pmic = rdev_get_drvdata(dev); + int idx; + + mutex_lock(&pmic->mtx); + + idx = i2c_smbus_read_byte(pmic->client); + if (idx < 0) + dev_err(&pmic->client->dev, "Error getting voltage\n"); + + mutex_unlock(&pmic->mtx); + return idx; +} + +static int isl6271a_set_voltage_sel(struct regulator_dev *dev, + unsigned selector) +{ + struct isl_pmic *pmic = rdev_get_drvdata(dev); + int err; + + mutex_lock(&pmic->mtx); + + err = i2c_smbus_write_byte(pmic->client, selector); + if (err < 0) + dev_err(&pmic->client->dev, "Error setting voltage\n"); + + mutex_unlock(&pmic->mtx); + return err; +} + +static struct regulator_ops isl_core_ops = { + .get_voltage_sel = isl6271a_get_voltage_sel, + .set_voltage_sel = isl6271a_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, +}; + +static struct regulator_ops isl_fixed_ops = { + .list_voltage = regulator_list_voltage_linear, +}; + +static const struct regulator_desc isl_rd[] = { + { + .name = "Core Buck", + .id = 0, + .n_voltages = 16, + .ops = &isl_core_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .min_uV = ISL6271A_VOLTAGE_MIN, + .uV_step = ISL6271A_VOLTAGE_STEP, + }, { + .name = "LDO1", + .id = 1, + .n_voltages = 1, + .ops = &isl_fixed_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .min_uV = 1100000, + }, { + .name = "LDO2", + .id = 2, + .n_voltages = 1, + .ops = &isl_fixed_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .min_uV = 1300000, + }, +}; + +static int isl6271a_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regulator_config config = { }; + struct regulator_init_data *init_data = dev_get_platdata(&i2c->dev); + struct isl_pmic *pmic; + int i; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + pmic = devm_kzalloc(&i2c->dev, sizeof(struct isl_pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + pmic->client = i2c; + + mutex_init(&pmic->mtx); + + for (i = 0; i < 3; i++) { + config.dev = &i2c->dev; + if (i == 0) + config.init_data = init_data; + else + config.init_data = NULL; + config.driver_data = pmic; + + pmic->rdev[i] = devm_regulator_register(&i2c->dev, &isl_rd[i], + &config); + if (IS_ERR(pmic->rdev[i])) { + dev_err(&i2c->dev, "failed to register %s\n", id->name); + return PTR_ERR(pmic->rdev[i]); + } + } + + i2c_set_clientdata(i2c, pmic); + + return 0; +} + +static const struct i2c_device_id isl6271a_id[] = { + {.name = "isl6271a", 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, isl6271a_id); + +static struct i2c_driver isl6271a_i2c_driver = { + .driver = { + .name = "isl6271a", + }, + .probe = isl6271a_probe, + .id_table = isl6271a_id, +}; + +static int __init isl6271a_init(void) +{ + return i2c_add_driver(&isl6271a_i2c_driver); +} + +static void __exit isl6271a_cleanup(void) +{ + i2c_del_driver(&isl6271a_i2c_driver); +} + +subsys_initcall(isl6271a_init); +module_exit(isl6271a_cleanup); + +MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); +MODULE_DESCRIPTION("Intersil ISL6271A voltage regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/isl9305.c b/drivers/regulator/isl9305.c new file mode 100644 index 000000000..257c1943e --- /dev/null +++ b/drivers/regulator/isl9305.c @@ -0,0 +1,209 @@ +/* + * isl9305 - Intersil ISL9305 DCDC regulator + * + * Copyright 2014 Linaro Ltd + * + * Author: Mark Brown <broonie@kernel.org> + * + * 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/module.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/platform_data/isl9305.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/slab.h> + +/* + * Registers + */ +#define ISL9305_DCD1OUT 0x0 +#define ISL9305_DCD2OUT 0x1 +#define ISL9305_LDO1OUT 0x2 +#define ISL9305_LDO2OUT 0x3 +#define ISL9305_DCD_PARAMETER 0x4 +#define ISL9305_SYSTEM_PARAMETER 0x5 +#define ISL9305_DCD_SRCTL 0x6 + +#define ISL9305_MAX_REG ISL9305_DCD_SRCTL + +/* + * DCD_PARAMETER + */ +#define ISL9305_DCD_PHASE 0x40 +#define ISL9305_DCD2_ULTRA 0x20 +#define ISL9305_DCD1_ULTRA 0x10 +#define ISL9305_DCD2_BLD 0x08 +#define ISL9305_DCD1_BLD 0x04 +#define ISL9305_DCD2_MODE 0x02 +#define ISL9305_DCD1_MODE 0x01 + +/* + * SYSTEM_PARAMETER + */ +#define ISL9305_I2C_EN 0x40 +#define ISL9305_DCDPOR_MASK 0x30 +#define ISL9305_LDO2_EN 0x08 +#define ISL9305_LDO1_EN 0x04 +#define ISL9305_DCD2_EN 0x02 +#define ISL9305_DCD1_EN 0x01 + +/* + * DCD_SRCTL + */ +#define ISL9305_DCD2SR_MASK 0xc0 +#define ISL9305_DCD1SR_MASK 0x07 + +static const struct regulator_ops isl9305_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_desc isl9305_regulators[] = { + [ISL9305_DCD1] = { + .name = "DCD1", + .of_match = of_match_ptr("dcd1"), + .regulators_node = of_match_ptr("regulators"), + .n_voltages = 0x70, + .min_uV = 825000, + .uV_step = 25000, + .vsel_reg = ISL9305_DCD1OUT, + .vsel_mask = 0x7f, + .enable_reg = ISL9305_SYSTEM_PARAMETER, + .enable_mask = ISL9305_DCD1_EN, + .supply_name = "VINDCD1", + .ops = &isl9305_ops, + }, + [ISL9305_DCD2] = { + .name = "DCD2", + .of_match = of_match_ptr("dcd2"), + .regulators_node = of_match_ptr("regulators"), + .n_voltages = 0x70, + .min_uV = 825000, + .uV_step = 25000, + .vsel_reg = ISL9305_DCD2OUT, + .vsel_mask = 0x7f, + .enable_reg = ISL9305_SYSTEM_PARAMETER, + .enable_mask = ISL9305_DCD2_EN, + .supply_name = "VINDCD2", + .ops = &isl9305_ops, + }, + [ISL9305_LDO1] = { + .name = "LDO1", + .of_match = of_match_ptr("ldo1"), + .regulators_node = of_match_ptr("regulators"), + .n_voltages = 0x37, + .min_uV = 900000, + .uV_step = 50000, + .vsel_reg = ISL9305_LDO1OUT, + .vsel_mask = 0x3f, + .enable_reg = ISL9305_SYSTEM_PARAMETER, + .enable_mask = ISL9305_LDO1_EN, + .supply_name = "VINLDO1", + .ops = &isl9305_ops, + }, + [ISL9305_LDO2] = { + .name = "LDO2", + .of_match = of_match_ptr("ldo2"), + .regulators_node = of_match_ptr("regulators"), + .n_voltages = 0x37, + .min_uV = 900000, + .uV_step = 50000, + .vsel_reg = ISL9305_LDO2OUT, + .vsel_mask = 0x3f, + .enable_reg = ISL9305_SYSTEM_PARAMETER, + .enable_mask = ISL9305_LDO2_EN, + .supply_name = "VINLDO2", + .ops = &isl9305_ops, + }, +}; + +static const struct regmap_config isl9305_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = ISL9305_MAX_REG, + .cache_type = REGCACHE_RBTREE, +}; + +static int isl9305_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regulator_config config = { }; + struct isl9305_pdata *pdata = i2c->dev.platform_data; + struct regulator_dev *rdev; + struct regmap *regmap; + int i, ret; + + regmap = devm_regmap_init_i2c(i2c, &isl9305_regmap); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&i2c->dev, "Failed to create regmap: %d\n", ret); + return ret; + } + + config.dev = &i2c->dev; + + for (i = 0; i < ARRAY_SIZE(isl9305_regulators); i++) { + if (pdata) + config.init_data = pdata->init_data[i]; + else + config.init_data = NULL; + + rdev = devm_regulator_register(&i2c->dev, + &isl9305_regulators[i], + &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(&i2c->dev, "Failed to register %s: %d\n", + isl9305_regulators[i].name, ret); + return ret; + } + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id isl9305_dt_ids[] = { + { .compatible = "isl,isl9305" }, /* for backward compat., don't use */ + { .compatible = "isil,isl9305" }, + { .compatible = "isl,isl9305h" }, /* for backward compat., don't use */ + { .compatible = "isil,isl9305h" }, + {}, +}; +MODULE_DEVICE_TABLE(of, isl9305_dt_ids); +#endif + +static const struct i2c_device_id isl9305_i2c_id[] = { + { "isl9305", }, + { "isl9305h", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl9305_i2c_id); + +static struct i2c_driver isl9305_regulator_driver = { + .driver = { + .name = "isl9305", + .of_match_table = of_match_ptr(isl9305_dt_ids), + }, + .probe = isl9305_i2c_probe, + .id_table = isl9305_i2c_id, +}; + +module_i2c_driver(isl9305_regulator_driver); + +MODULE_AUTHOR("Mark Brown"); +MODULE_DESCRIPTION("Intersil ISL9305 DCDC regulator"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/lm363x-regulator.c b/drivers/regulator/lm363x-regulator.c new file mode 100644 index 000000000..27c0a67cf --- /dev/null +++ b/drivers/regulator/lm363x-regulator.c @@ -0,0 +1,291 @@ +/* + * TI LM363X Regulator Driver + * + * Copyright 2015 Texas Instruments + * + * Author: Milo Kim <milo.kim@ti.com> + * + * 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/err.h> +#include <linux/kernel.h> +#include <linux/mfd/ti-lmu.h> +#include <linux/mfd/ti-lmu-register.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/slab.h> + +/* LM3631 */ +#define LM3631_BOOST_VSEL_MAX 0x25 +#define LM3631_LDO_VSEL_MAX 0x28 +#define LM3631_CONT_VSEL_MAX 0x03 +#define LM3631_VBOOST_MIN 4500000 +#define LM3631_VCONT_MIN 1800000 +#define LM3631_VLDO_MIN 4000000 +#define ENABLE_TIME_USEC 1000 + +/* LM3632 */ +#define LM3632_BOOST_VSEL_MAX 0x26 +#define LM3632_LDO_VSEL_MAX 0x28 +#define LM3632_VBOOST_MIN 4500000 +#define LM3632_VLDO_MIN 4000000 + +/* Common */ +#define LM363X_STEP_50mV 50000 +#define LM363X_STEP_500mV 500000 + +static const int ldo_cont_enable_time[] = { + 0, 2000, 5000, 10000, 20000, 50000, 100000, 200000, +}; + +static int lm363x_regulator_enable_time(struct regulator_dev *rdev) +{ + enum lm363x_regulator_id id = rdev_get_id(rdev); + u8 val, addr, mask; + + switch (id) { + case LM3631_LDO_CONT: + addr = LM3631_REG_ENTIME_VCONT; + mask = LM3631_ENTIME_CONT_MASK; + break; + case LM3631_LDO_OREF: + addr = LM3631_REG_ENTIME_VOREF; + mask = LM3631_ENTIME_MASK; + break; + case LM3631_LDO_POS: + addr = LM3631_REG_ENTIME_VPOS; + mask = LM3631_ENTIME_MASK; + break; + case LM3631_LDO_NEG: + addr = LM3631_REG_ENTIME_VNEG; + mask = LM3631_ENTIME_MASK; + break; + default: + return 0; + } + + if (regmap_read(rdev->regmap, addr, (unsigned int *)&val)) + return -EINVAL; + + val = (val & mask) >> LM3631_ENTIME_SHIFT; + + if (id == LM3631_LDO_CONT) + return ldo_cont_enable_time[val]; + else + return ENABLE_TIME_USEC * val; +} + +static struct regulator_ops lm363x_boost_voltage_table_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + +static struct regulator_ops lm363x_regulator_voltage_table_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .enable_time = lm363x_regulator_enable_time, +}; + +static const struct regulator_desc lm363x_regulator_desc[] = { + /* LM3631 */ + { + .name = "vboost", + .of_match = "vboost", + .id = LM3631_BOOST, + .ops = &lm363x_boost_voltage_table_ops, + .n_voltages = LM3631_BOOST_VSEL_MAX + 1, + .min_uV = LM3631_VBOOST_MIN, + .uV_step = LM363X_STEP_50mV, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LM3631_REG_VOUT_BOOST, + .vsel_mask = LM3631_VOUT_MASK, + }, + { + .name = "ldo_cont", + .of_match = "vcont", + .id = LM3631_LDO_CONT, + .ops = &lm363x_regulator_voltage_table_ops, + .n_voltages = LM3631_CONT_VSEL_MAX + 1, + .min_uV = LM3631_VCONT_MIN, + .uV_step = LM363X_STEP_500mV, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LM3631_REG_VOUT_CONT, + .vsel_mask = LM3631_VOUT_CONT_MASK, + .enable_reg = LM3631_REG_LDO_CTRL2, + .enable_mask = LM3631_EN_CONT_MASK, + }, + { + .name = "ldo_oref", + .of_match = "voref", + .id = LM3631_LDO_OREF, + .ops = &lm363x_regulator_voltage_table_ops, + .n_voltages = LM3631_LDO_VSEL_MAX + 1, + .min_uV = LM3631_VLDO_MIN, + .uV_step = LM363X_STEP_50mV, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LM3631_REG_VOUT_OREF, + .vsel_mask = LM3631_VOUT_MASK, + .enable_reg = LM3631_REG_LDO_CTRL1, + .enable_mask = LM3631_EN_OREF_MASK, + }, + { + .name = "ldo_vpos", + .of_match = "vpos", + .id = LM3631_LDO_POS, + .ops = &lm363x_regulator_voltage_table_ops, + .n_voltages = LM3631_LDO_VSEL_MAX + 1, + .min_uV = LM3631_VLDO_MIN, + .uV_step = LM363X_STEP_50mV, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LM3631_REG_VOUT_POS, + .vsel_mask = LM3631_VOUT_MASK, + .enable_reg = LM3631_REG_LDO_CTRL1, + .enable_mask = LM3631_EN_VPOS_MASK, + }, + { + .name = "ldo_vneg", + .of_match = "vneg", + .id = LM3631_LDO_NEG, + .ops = &lm363x_regulator_voltage_table_ops, + .n_voltages = LM3631_LDO_VSEL_MAX + 1, + .min_uV = LM3631_VLDO_MIN, + .uV_step = LM363X_STEP_50mV, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LM3631_REG_VOUT_NEG, + .vsel_mask = LM3631_VOUT_MASK, + .enable_reg = LM3631_REG_LDO_CTRL1, + .enable_mask = LM3631_EN_VNEG_MASK, + }, + /* LM3632 */ + { + .name = "vboost", + .of_match = "vboost", + .id = LM3632_BOOST, + .ops = &lm363x_boost_voltage_table_ops, + .n_voltages = LM3632_BOOST_VSEL_MAX + 1, + .min_uV = LM3632_VBOOST_MIN, + .uV_step = LM363X_STEP_50mV, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LM3632_REG_VOUT_BOOST, + .vsel_mask = LM3632_VOUT_MASK, + }, + { + .name = "ldo_vpos", + .of_match = "vpos", + .id = LM3632_LDO_POS, + .ops = &lm363x_regulator_voltage_table_ops, + .n_voltages = LM3632_LDO_VSEL_MAX + 1, + .min_uV = LM3632_VLDO_MIN, + .uV_step = LM363X_STEP_50mV, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LM3632_REG_VOUT_POS, + .vsel_mask = LM3632_VOUT_MASK, + .enable_reg = LM3632_REG_BIAS_CONFIG, + .enable_mask = LM3632_EN_VPOS_MASK, + }, + { + .name = "ldo_vneg", + .of_match = "vneg", + .id = LM3632_LDO_NEG, + .ops = &lm363x_regulator_voltage_table_ops, + .n_voltages = LM3632_LDO_VSEL_MAX + 1, + .min_uV = LM3632_VLDO_MIN, + .uV_step = LM363X_STEP_50mV, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LM3632_REG_VOUT_NEG, + .vsel_mask = LM3632_VOUT_MASK, + .enable_reg = LM3632_REG_BIAS_CONFIG, + .enable_mask = LM3632_EN_VNEG_MASK, + }, +}; + +static struct gpio_desc *lm363x_regulator_of_get_enable_gpio(struct device *dev, int id) +{ + /* + * Check LCM_EN1/2_GPIO is configured. + * Those pins are used for enabling VPOS/VNEG LDOs. + */ + switch (id) { + case LM3632_LDO_POS: + return devm_gpiod_get_index_optional(dev, "enable", 0, GPIOD_OUT_LOW); + case LM3632_LDO_NEG: + return devm_gpiod_get_index_optional(dev, "enable", 1, GPIOD_OUT_LOW); + default: + return NULL; + } +} + +static int lm363x_regulator_probe(struct platform_device *pdev) +{ + struct ti_lmu *lmu = dev_get_drvdata(pdev->dev.parent); + struct regmap *regmap = lmu->regmap; + struct regulator_config cfg = { }; + struct regulator_dev *rdev; + struct device *dev = &pdev->dev; + int id = pdev->id; + struct gpio_desc *gpiod; + int ret; + + cfg.dev = dev; + cfg.regmap = regmap; + + /* + * LM3632 LDOs can be controlled by external pin. + * Register update is required if the pin is used. + */ + gpiod = lm363x_regulator_of_get_enable_gpio(dev, id); + if (gpiod) { + cfg.ena_gpiod = gpiod; + + ret = regmap_update_bits(regmap, LM3632_REG_BIAS_CONFIG, + LM3632_EXT_EN_MASK, + LM3632_EXT_EN_MASK); + if (ret) { + dev_err(dev, "External pin err: %d\n", ret); + return ret; + } + } + + rdev = devm_regulator_register(dev, &lm363x_regulator_desc[id], &cfg); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(dev, "[%d] regulator register err: %d\n", id, ret); + return ret; + } + + return 0; +} + +static struct platform_driver lm363x_regulator_driver = { + .probe = lm363x_regulator_probe, + .driver = { + .name = "lm363x-regulator", + }, +}; + +module_platform_driver(lm363x_regulator_driver); + +MODULE_DESCRIPTION("TI LM363X Regulator Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:lm363x-regulator"); diff --git a/drivers/regulator/lp3971.c b/drivers/regulator/lp3971.c new file mode 100644 index 000000000..204b5c527 --- /dev/null +++ b/drivers/regulator/lp3971.c @@ -0,0 +1,464 @@ +/* + * Regulator driver for National Semiconductors LP3971 PMIC chip + * + * Copyright (C) 2009 Samsung Electronics + * Author: Marek Szyprowski <m.szyprowski@samsung.com> + * + * Based on wm8350.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/module.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/lp3971.h> +#include <linux/slab.h> + +struct lp3971 { + struct device *dev; + struct mutex io_lock; + struct i2c_client *i2c; +}; + +static u8 lp3971_reg_read(struct lp3971 *lp3971, u8 reg); +static int lp3971_set_bits(struct lp3971 *lp3971, u8 reg, u16 mask, u16 val); + +#define LP3971_SYS_CONTROL1_REG 0x07 + +/* System control register 1 initial value, + bits 4 and 5 are EPROM programmable */ +#define SYS_CONTROL1_INIT_VAL 0x40 +#define SYS_CONTROL1_INIT_MASK 0xCF + +#define LP3971_BUCK_VOL_ENABLE_REG 0x10 +#define LP3971_BUCK_VOL_CHANGE_REG 0x20 + +/* Voltage control registers shift: + LP3971_BUCK1 -> 0 + LP3971_BUCK2 -> 4 + LP3971_BUCK3 -> 6 +*/ +#define BUCK_VOL_CHANGE_SHIFT(x) (((!!x) << 2) | (x & ~0x01)) +#define BUCK_VOL_CHANGE_FLAG_GO 0x01 +#define BUCK_VOL_CHANGE_FLAG_TARGET 0x02 +#define BUCK_VOL_CHANGE_FLAG_MASK 0x03 + +#define LP3971_BUCK1_BASE 0x23 +#define LP3971_BUCK2_BASE 0x29 +#define LP3971_BUCK3_BASE 0x32 + +static const int buck_base_addr[] = { + LP3971_BUCK1_BASE, + LP3971_BUCK2_BASE, + LP3971_BUCK3_BASE, +}; + +#define LP3971_BUCK_TARGET_VOL1_REG(x) (buck_base_addr[x]) +#define LP3971_BUCK_TARGET_VOL2_REG(x) (buck_base_addr[x]+1) + +static const unsigned int buck_voltage_map[] = { + 0, 800000, 850000, 900000, 950000, 1000000, 1050000, 1100000, + 1150000, 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, + 1550000, 1600000, 1650000, 1700000, 1800000, 1900000, 2500000, 2800000, + 3000000, 3300000, +}; + +#define BUCK_TARGET_VOL_MASK 0x3f + +#define LP3971_BUCK_RAMP_REG(x) (buck_base_addr[x]+2) + +#define LP3971_LDO_ENABLE_REG 0x12 +#define LP3971_LDO_VOL_CONTR_BASE 0x39 + +/* Voltage control registers: + LP3971_LDO1 -> LP3971_LDO_VOL_CONTR_BASE + 0 + LP3971_LDO2 -> LP3971_LDO_VOL_CONTR_BASE + 0 + LP3971_LDO3 -> LP3971_LDO_VOL_CONTR_BASE + 1 + LP3971_LDO4 -> LP3971_LDO_VOL_CONTR_BASE + 1 + LP3971_LDO5 -> LP3971_LDO_VOL_CONTR_BASE + 2 +*/ +#define LP3971_LDO_VOL_CONTR_REG(x) (LP3971_LDO_VOL_CONTR_BASE + (x >> 1)) + +/* Voltage control registers shift: + LP3971_LDO1 -> 0, LP3971_LDO2 -> 4 + LP3971_LDO3 -> 0, LP3971_LDO4 -> 4 + LP3971_LDO5 -> 0 +*/ +#define LDO_VOL_CONTR_SHIFT(x) ((x & 1) << 2) +#define LDO_VOL_CONTR_MASK 0x0f + +static const unsigned int ldo45_voltage_map[] = { + 1000000, 1050000, 1100000, 1150000, 1200000, 1250000, 1300000, 1350000, + 1400000, 1500000, 1800000, 1900000, 2500000, 2800000, 3000000, 3300000, +}; + +static const unsigned int ldo123_voltage_map[] = { + 1800000, 1900000, 2000000, 2100000, 2200000, 2300000, 2400000, 2500000, + 2600000, 2700000, 2800000, 2900000, 3000000, 3100000, 3200000, 3300000, +}; + +#define LDO_VOL_MIN_IDX 0x00 +#define LDO_VOL_MAX_IDX 0x0f + +static int lp3971_ldo_is_enabled(struct regulator_dev *dev) +{ + struct lp3971 *lp3971 = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev) - LP3971_LDO1; + u16 mask = 1 << (1 + ldo); + u16 val; + + val = lp3971_reg_read(lp3971, LP3971_LDO_ENABLE_REG); + return (val & mask) != 0; +} + +static int lp3971_ldo_enable(struct regulator_dev *dev) +{ + struct lp3971 *lp3971 = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev) - LP3971_LDO1; + u16 mask = 1 << (1 + ldo); + + return lp3971_set_bits(lp3971, LP3971_LDO_ENABLE_REG, mask, mask); +} + +static int lp3971_ldo_disable(struct regulator_dev *dev) +{ + struct lp3971 *lp3971 = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev) - LP3971_LDO1; + u16 mask = 1 << (1 + ldo); + + return lp3971_set_bits(lp3971, LP3971_LDO_ENABLE_REG, mask, 0); +} + +static int lp3971_ldo_get_voltage_sel(struct regulator_dev *dev) +{ + struct lp3971 *lp3971 = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev) - LP3971_LDO1; + u16 val, reg; + + reg = lp3971_reg_read(lp3971, LP3971_LDO_VOL_CONTR_REG(ldo)); + val = (reg >> LDO_VOL_CONTR_SHIFT(ldo)) & LDO_VOL_CONTR_MASK; + + return val; +} + +static int lp3971_ldo_set_voltage_sel(struct regulator_dev *dev, + unsigned int selector) +{ + struct lp3971 *lp3971 = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev) - LP3971_LDO1; + + return lp3971_set_bits(lp3971, LP3971_LDO_VOL_CONTR_REG(ldo), + LDO_VOL_CONTR_MASK << LDO_VOL_CONTR_SHIFT(ldo), + selector << LDO_VOL_CONTR_SHIFT(ldo)); +} + +static struct regulator_ops lp3971_ldo_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .is_enabled = lp3971_ldo_is_enabled, + .enable = lp3971_ldo_enable, + .disable = lp3971_ldo_disable, + .get_voltage_sel = lp3971_ldo_get_voltage_sel, + .set_voltage_sel = lp3971_ldo_set_voltage_sel, +}; + +static int lp3971_dcdc_is_enabled(struct regulator_dev *dev) +{ + struct lp3971 *lp3971 = rdev_get_drvdata(dev); + int buck = rdev_get_id(dev) - LP3971_DCDC1; + u16 mask = 1 << (buck * 2); + u16 val; + + val = lp3971_reg_read(lp3971, LP3971_BUCK_VOL_ENABLE_REG); + return (val & mask) != 0; +} + +static int lp3971_dcdc_enable(struct regulator_dev *dev) +{ + struct lp3971 *lp3971 = rdev_get_drvdata(dev); + int buck = rdev_get_id(dev) - LP3971_DCDC1; + u16 mask = 1 << (buck * 2); + + return lp3971_set_bits(lp3971, LP3971_BUCK_VOL_ENABLE_REG, mask, mask); +} + +static int lp3971_dcdc_disable(struct regulator_dev *dev) +{ + struct lp3971 *lp3971 = rdev_get_drvdata(dev); + int buck = rdev_get_id(dev) - LP3971_DCDC1; + u16 mask = 1 << (buck * 2); + + return lp3971_set_bits(lp3971, LP3971_BUCK_VOL_ENABLE_REG, mask, 0); +} + +static int lp3971_dcdc_get_voltage_sel(struct regulator_dev *dev) +{ + struct lp3971 *lp3971 = rdev_get_drvdata(dev); + int buck = rdev_get_id(dev) - LP3971_DCDC1; + u16 reg; + + reg = lp3971_reg_read(lp3971, LP3971_BUCK_TARGET_VOL1_REG(buck)); + reg &= BUCK_TARGET_VOL_MASK; + + return reg; +} + +static int lp3971_dcdc_set_voltage_sel(struct regulator_dev *dev, + unsigned int selector) +{ + struct lp3971 *lp3971 = rdev_get_drvdata(dev); + int buck = rdev_get_id(dev) - LP3971_DCDC1; + int ret; + + ret = lp3971_set_bits(lp3971, LP3971_BUCK_TARGET_VOL1_REG(buck), + BUCK_TARGET_VOL_MASK, selector); + if (ret) + return ret; + + ret = lp3971_set_bits(lp3971, LP3971_BUCK_VOL_CHANGE_REG, + BUCK_VOL_CHANGE_FLAG_MASK << BUCK_VOL_CHANGE_SHIFT(buck), + BUCK_VOL_CHANGE_FLAG_GO << BUCK_VOL_CHANGE_SHIFT(buck)); + if (ret) + return ret; + + return lp3971_set_bits(lp3971, LP3971_BUCK_VOL_CHANGE_REG, + BUCK_VOL_CHANGE_FLAG_MASK << BUCK_VOL_CHANGE_SHIFT(buck), + 0 << BUCK_VOL_CHANGE_SHIFT(buck)); +} + +static struct regulator_ops lp3971_dcdc_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .is_enabled = lp3971_dcdc_is_enabled, + .enable = lp3971_dcdc_enable, + .disable = lp3971_dcdc_disable, + .get_voltage_sel = lp3971_dcdc_get_voltage_sel, + .set_voltage_sel = lp3971_dcdc_set_voltage_sel, +}; + +static const struct regulator_desc regulators[] = { + { + .name = "LDO1", + .id = LP3971_LDO1, + .ops = &lp3971_ldo_ops, + .n_voltages = ARRAY_SIZE(ldo123_voltage_map), + .volt_table = ldo123_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "LDO2", + .id = LP3971_LDO2, + .ops = &lp3971_ldo_ops, + .n_voltages = ARRAY_SIZE(ldo123_voltage_map), + .volt_table = ldo123_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "LDO3", + .id = LP3971_LDO3, + .ops = &lp3971_ldo_ops, + .n_voltages = ARRAY_SIZE(ldo123_voltage_map), + .volt_table = ldo123_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "LDO4", + .id = LP3971_LDO4, + .ops = &lp3971_ldo_ops, + .n_voltages = ARRAY_SIZE(ldo45_voltage_map), + .volt_table = ldo45_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "LDO5", + .id = LP3971_LDO5, + .ops = &lp3971_ldo_ops, + .n_voltages = ARRAY_SIZE(ldo45_voltage_map), + .volt_table = ldo45_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "DCDC1", + .id = LP3971_DCDC1, + .ops = &lp3971_dcdc_ops, + .n_voltages = ARRAY_SIZE(buck_voltage_map), + .volt_table = buck_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "DCDC2", + .id = LP3971_DCDC2, + .ops = &lp3971_dcdc_ops, + .n_voltages = ARRAY_SIZE(buck_voltage_map), + .volt_table = buck_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "DCDC3", + .id = LP3971_DCDC3, + .ops = &lp3971_dcdc_ops, + .n_voltages = ARRAY_SIZE(buck_voltage_map), + .volt_table = buck_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, +}; + +static int lp3971_i2c_read(struct i2c_client *i2c, char reg, int count, + u16 *dest) +{ + int 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 lp3971_i2c_write(struct i2c_client *i2c, char reg, int count, + const u16 *src) +{ + if (count != 1) + return -EIO; + return i2c_smbus_write_byte_data(i2c, reg, *src); +} + +static u8 lp3971_reg_read(struct lp3971 *lp3971, u8 reg) +{ + u16 val = 0; + + mutex_lock(&lp3971->io_lock); + + lp3971_i2c_read(lp3971->i2c, reg, 1, &val); + + dev_dbg(lp3971->dev, "reg read 0x%02x -> 0x%02x\n", (int)reg, + (unsigned)val&0xff); + + mutex_unlock(&lp3971->io_lock); + + return val & 0xff; +} + +static int lp3971_set_bits(struct lp3971 *lp3971, u8 reg, u16 mask, u16 val) +{ + u16 tmp; + int ret; + + mutex_lock(&lp3971->io_lock); + + ret = lp3971_i2c_read(lp3971->i2c, reg, 1, &tmp); + if (ret == 0) { + tmp = (tmp & ~mask) | val; + ret = lp3971_i2c_write(lp3971->i2c, reg, 1, &tmp); + dev_dbg(lp3971->dev, "reg write 0x%02x -> 0x%02x\n", (int)reg, + (unsigned)val&0xff); + } + mutex_unlock(&lp3971->io_lock); + + return ret; +} + +static int setup_regulators(struct lp3971 *lp3971, + struct lp3971_platform_data *pdata) +{ + int i, err; + + /* Instantiate the regulators */ + for (i = 0; i < pdata->num_regulators; i++) { + struct regulator_config config = { }; + struct lp3971_regulator_subdev *reg = &pdata->regulators[i]; + struct regulator_dev *rdev; + + config.dev = lp3971->dev; + config.init_data = reg->initdata; + config.driver_data = lp3971; + + rdev = devm_regulator_register(lp3971->dev, + ®ulators[reg->id], &config); + if (IS_ERR(rdev)) { + err = PTR_ERR(rdev); + dev_err(lp3971->dev, "regulator init failed: %d\n", + err); + return err; + } + } + + return 0; +} + +static int lp3971_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct lp3971 *lp3971; + struct lp3971_platform_data *pdata = dev_get_platdata(&i2c->dev); + int ret; + u16 val; + + if (!pdata) { + dev_dbg(&i2c->dev, "No platform init data supplied\n"); + return -ENODEV; + } + + lp3971 = devm_kzalloc(&i2c->dev, sizeof(struct lp3971), GFP_KERNEL); + if (lp3971 == NULL) + return -ENOMEM; + + lp3971->i2c = i2c; + lp3971->dev = &i2c->dev; + + mutex_init(&lp3971->io_lock); + + /* Detect LP3971 */ + ret = lp3971_i2c_read(i2c, LP3971_SYS_CONTROL1_REG, 1, &val); + if (ret == 0 && (val & SYS_CONTROL1_INIT_MASK) != SYS_CONTROL1_INIT_VAL) + ret = -ENODEV; + if (ret < 0) { + dev_err(&i2c->dev, "failed to detect device\n"); + return ret; + } + + ret = setup_regulators(lp3971, pdata); + if (ret < 0) + return ret; + + i2c_set_clientdata(i2c, lp3971); + return 0; +} + +static const struct i2c_device_id lp3971_i2c_id[] = { + { "lp3971", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp3971_i2c_id); + +static struct i2c_driver lp3971_i2c_driver = { + .driver = { + .name = "LP3971", + }, + .probe = lp3971_i2c_probe, + .id_table = lp3971_i2c_id, +}; + +module_i2c_driver(lp3971_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marek Szyprowski <m.szyprowski@samsung.com>"); +MODULE_DESCRIPTION("LP3971 PMIC driver"); diff --git a/drivers/regulator/lp3972.c b/drivers/regulator/lp3972.c new file mode 100644 index 000000000..ff0c275f9 --- /dev/null +++ b/drivers/regulator/lp3972.c @@ -0,0 +1,572 @@ +/* + * Regulator driver for National Semiconductors LP3972 PMIC chip + * + * Based on lp3971.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/module.h> +#include <linux/kernel.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/lp3972.h> +#include <linux/slab.h> + +struct lp3972 { + struct device *dev; + struct mutex io_lock; + struct i2c_client *i2c; +}; + +/* LP3972 Control Registers */ +#define LP3972_SCR_REG 0x07 +#define LP3972_OVER1_REG 0x10 +#define LP3972_OVSR1_REG 0x11 +#define LP3972_OVER2_REG 0x12 +#define LP3972_OVSR2_REG 0x13 +#define LP3972_VCC1_REG 0x20 +#define LP3972_ADTV1_REG 0x23 +#define LP3972_ADTV2_REG 0x24 +#define LP3972_AVRC_REG 0x25 +#define LP3972_CDTC1_REG 0x26 +#define LP3972_CDTC2_REG 0x27 +#define LP3972_SDTV1_REG 0x29 +#define LP3972_SDTV2_REG 0x2A +#define LP3972_MDTV1_REG 0x32 +#define LP3972_MDTV2_REG 0x33 +#define LP3972_L2VCR_REG 0x39 +#define LP3972_L34VCR_REG 0x3A +#define LP3972_SCR1_REG 0x80 +#define LP3972_SCR2_REG 0x81 +#define LP3972_OEN3_REG 0x82 +#define LP3972_OSR3_REG 0x83 +#define LP3972_LOER4_REG 0x84 +#define LP3972_B2TV_REG 0x85 +#define LP3972_B3TV_REG 0x86 +#define LP3972_B32RC_REG 0x87 +#define LP3972_ISRA_REG 0x88 +#define LP3972_BCCR_REG 0x89 +#define LP3972_II1RR_REG 0x8E +#define LP3972_II2RR_REG 0x8F + +#define LP3972_SYS_CONTROL1_REG LP3972_SCR1_REG +/* System control register 1 initial value, + * bits 5, 6 and 7 are EPROM programmable */ +#define SYS_CONTROL1_INIT_VAL 0x02 +#define SYS_CONTROL1_INIT_MASK 0x1F + +#define LP3972_VOL_CHANGE_REG LP3972_VCC1_REG +#define LP3972_VOL_CHANGE_FLAG_GO 0x01 +#define LP3972_VOL_CHANGE_FLAG_MASK 0x03 + +/* LDO output enable mask */ +#define LP3972_OEN3_L1EN BIT(0) +#define LP3972_OVER2_LDO2_EN BIT(2) +#define LP3972_OVER2_LDO3_EN BIT(3) +#define LP3972_OVER2_LDO4_EN BIT(4) +#define LP3972_OVER1_S_EN BIT(2) + +static const unsigned int ldo1_voltage_map[] = { + 1700000, 1725000, 1750000, 1775000, 1800000, 1825000, 1850000, 1875000, + 1900000, 1925000, 1950000, 1975000, 2000000, +}; + +static const unsigned int ldo23_voltage_map[] = { + 1800000, 1900000, 2000000, 2100000, 2200000, 2300000, 2400000, 2500000, + 2600000, 2700000, 2800000, 2900000, 3000000, 3100000, 3200000, 3300000, +}; + +static const unsigned int ldo4_voltage_map[] = { + 1000000, 1050000, 1100000, 1150000, 1200000, 1250000, 1300000, 1350000, + 1400000, 1500000, 1800000, 1900000, 2500000, 2800000, 3000000, 3300000, +}; + +static const unsigned int ldo5_voltage_map[] = { + 0, 0, 0, 0, 0, 850000, 875000, 900000, + 925000, 950000, 975000, 1000000, 1025000, 1050000, 1075000, 1100000, + 1125000, 1150000, 1175000, 1200000, 1225000, 1250000, 1275000, 1300000, + 1325000, 1350000, 1375000, 1400000, 1425000, 1450000, 1475000, 1500000, +}; + +static const unsigned int buck1_voltage_map[] = { + 725000, 750000, 775000, 800000, 825000, 850000, 875000, 900000, + 925000, 950000, 975000, 1000000, 1025000, 1050000, 1075000, 1100000, + 1125000, 1150000, 1175000, 1200000, 1225000, 1250000, 1275000, 1300000, + 1325000, 1350000, 1375000, 1400000, 1425000, 1450000, 1475000, 1500000, +}; + +static const unsigned int buck23_voltage_map[] = { + 0, 800000, 850000, 900000, 950000, 1000000, 1050000, 1100000, + 1150000, 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, + 1550000, 1600000, 1650000, 1700000, 1800000, 1900000, 2500000, 2800000, + 3000000, 3300000, +}; + +static const int ldo_output_enable_mask[] = { + LP3972_OEN3_L1EN, + LP3972_OVER2_LDO2_EN, + LP3972_OVER2_LDO3_EN, + LP3972_OVER2_LDO4_EN, + LP3972_OVER1_S_EN, +}; + +static const int ldo_output_enable_addr[] = { + LP3972_OEN3_REG, + LP3972_OVER2_REG, + LP3972_OVER2_REG, + LP3972_OVER2_REG, + LP3972_OVER1_REG, +}; + +static const int ldo_vol_ctl_addr[] = { + LP3972_MDTV1_REG, + LP3972_L2VCR_REG, + LP3972_L34VCR_REG, + LP3972_L34VCR_REG, + LP3972_SDTV1_REG, +}; + +static const int buck_vol_enable_addr[] = { + LP3972_OVER1_REG, + LP3972_OEN3_REG, + LP3972_OEN3_REG, +}; + +static const int buck_base_addr[] = { + LP3972_ADTV1_REG, + LP3972_B2TV_REG, + LP3972_B3TV_REG, +}; + +#define LP3972_LDO_OUTPUT_ENABLE_MASK(x) (ldo_output_enable_mask[x]) +#define LP3972_LDO_OUTPUT_ENABLE_REG(x) (ldo_output_enable_addr[x]) + +/* LDO voltage control registers shift: + LP3972_LDO1 -> 0, LP3972_LDO2 -> 4 + LP3972_LDO3 -> 0, LP3972_LDO4 -> 4 + LP3972_LDO5 -> 0 +*/ +#define LP3972_LDO_VOL_CONTR_SHIFT(x) (((x) & 1) << 2) +#define LP3972_LDO_VOL_CONTR_REG(x) (ldo_vol_ctl_addr[x]) +#define LP3972_LDO_VOL_CHANGE_SHIFT(x) ((x) ? 4 : 6) + +#define LP3972_LDO_VOL_MASK(x) (((x) % 4) ? 0x0f : 0x1f) +#define LP3972_LDO_VOL_MIN_IDX(x) (((x) == 4) ? 0x05 : 0x00) +#define LP3972_LDO_VOL_MAX_IDX(x) ((x) ? (((x) == 4) ? 0x1f : 0x0f) : 0x0c) + +#define LP3972_BUCK_VOL_ENABLE_REG(x) (buck_vol_enable_addr[x]) +#define LP3972_BUCK_VOL1_REG(x) (buck_base_addr[x]) +#define LP3972_BUCK_VOL_MASK 0x1f + +static int lp3972_i2c_read(struct i2c_client *i2c, char reg, int count, + u16 *dest) +{ + int 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 lp3972_i2c_write(struct i2c_client *i2c, char reg, int count, + const u16 *src) +{ + if (count != 1) + return -EIO; + return i2c_smbus_write_byte_data(i2c, reg, *src); +} + +static u8 lp3972_reg_read(struct lp3972 *lp3972, u8 reg) +{ + u16 val = 0; + + mutex_lock(&lp3972->io_lock); + + lp3972_i2c_read(lp3972->i2c, reg, 1, &val); + + dev_dbg(lp3972->dev, "reg read 0x%02x -> 0x%02x\n", (int)reg, + (unsigned)val & 0xff); + + mutex_unlock(&lp3972->io_lock); + + return val & 0xff; +} + +static int lp3972_set_bits(struct lp3972 *lp3972, u8 reg, u16 mask, u16 val) +{ + u16 tmp; + int ret; + + mutex_lock(&lp3972->io_lock); + + ret = lp3972_i2c_read(lp3972->i2c, reg, 1, &tmp); + if (ret == 0) { + tmp = (tmp & ~mask) | val; + ret = lp3972_i2c_write(lp3972->i2c, reg, 1, &tmp); + dev_dbg(lp3972->dev, "reg write 0x%02x -> 0x%02x\n", (int)reg, + (unsigned)val & 0xff); + } + mutex_unlock(&lp3972->io_lock); + + return ret; +} + +static int lp3972_ldo_is_enabled(struct regulator_dev *dev) +{ + struct lp3972 *lp3972 = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev) - LP3972_LDO1; + u16 mask = LP3972_LDO_OUTPUT_ENABLE_MASK(ldo); + u16 val; + + val = lp3972_reg_read(lp3972, LP3972_LDO_OUTPUT_ENABLE_REG(ldo)); + return !!(val & mask); +} + +static int lp3972_ldo_enable(struct regulator_dev *dev) +{ + struct lp3972 *lp3972 = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev) - LP3972_LDO1; + u16 mask = LP3972_LDO_OUTPUT_ENABLE_MASK(ldo); + + return lp3972_set_bits(lp3972, LP3972_LDO_OUTPUT_ENABLE_REG(ldo), + mask, mask); +} + +static int lp3972_ldo_disable(struct regulator_dev *dev) +{ + struct lp3972 *lp3972 = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev) - LP3972_LDO1; + u16 mask = LP3972_LDO_OUTPUT_ENABLE_MASK(ldo); + + return lp3972_set_bits(lp3972, LP3972_LDO_OUTPUT_ENABLE_REG(ldo), + mask, 0); +} + +static int lp3972_ldo_get_voltage_sel(struct regulator_dev *dev) +{ + struct lp3972 *lp3972 = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev) - LP3972_LDO1; + u16 mask = LP3972_LDO_VOL_MASK(ldo); + u16 val, reg; + + reg = lp3972_reg_read(lp3972, LP3972_LDO_VOL_CONTR_REG(ldo)); + val = (reg >> LP3972_LDO_VOL_CONTR_SHIFT(ldo)) & mask; + + return val; +} + +static int lp3972_ldo_set_voltage_sel(struct regulator_dev *dev, + unsigned int selector) +{ + struct lp3972 *lp3972 = rdev_get_drvdata(dev); + int ldo = rdev_get_id(dev) - LP3972_LDO1; + int shift, ret; + + shift = LP3972_LDO_VOL_CONTR_SHIFT(ldo); + ret = lp3972_set_bits(lp3972, LP3972_LDO_VOL_CONTR_REG(ldo), + LP3972_LDO_VOL_MASK(ldo) << shift, selector << shift); + + if (ret) + return ret; + + /* + * LDO1 and LDO5 support voltage control by either target voltage1 + * or target voltage2 register. + * We use target voltage1 register for LDO1 and LDO5 in this driver. + * We need to update voltage change control register(0x20) to enable + * LDO1 and LDO5 to change to their programmed target values. + */ + switch (ldo) { + case LP3972_LDO1: + case LP3972_LDO5: + shift = LP3972_LDO_VOL_CHANGE_SHIFT(ldo); + ret = lp3972_set_bits(lp3972, LP3972_VOL_CHANGE_REG, + LP3972_VOL_CHANGE_FLAG_MASK << shift, + LP3972_VOL_CHANGE_FLAG_GO << shift); + if (ret) + return ret; + + ret = lp3972_set_bits(lp3972, LP3972_VOL_CHANGE_REG, + LP3972_VOL_CHANGE_FLAG_MASK << shift, 0); + break; + } + + return ret; +} + +static struct regulator_ops lp3972_ldo_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .is_enabled = lp3972_ldo_is_enabled, + .enable = lp3972_ldo_enable, + .disable = lp3972_ldo_disable, + .get_voltage_sel = lp3972_ldo_get_voltage_sel, + .set_voltage_sel = lp3972_ldo_set_voltage_sel, +}; + +static int lp3972_dcdc_is_enabled(struct regulator_dev *dev) +{ + struct lp3972 *lp3972 = rdev_get_drvdata(dev); + int buck = rdev_get_id(dev) - LP3972_DCDC1; + u16 mask = 1 << (buck * 2); + u16 val; + + val = lp3972_reg_read(lp3972, LP3972_BUCK_VOL_ENABLE_REG(buck)); + return !!(val & mask); +} + +static int lp3972_dcdc_enable(struct regulator_dev *dev) +{ + struct lp3972 *lp3972 = rdev_get_drvdata(dev); + int buck = rdev_get_id(dev) - LP3972_DCDC1; + u16 mask = 1 << (buck * 2); + u16 val; + + val = lp3972_set_bits(lp3972, LP3972_BUCK_VOL_ENABLE_REG(buck), + mask, mask); + return val; +} + +static int lp3972_dcdc_disable(struct regulator_dev *dev) +{ + struct lp3972 *lp3972 = rdev_get_drvdata(dev); + int buck = rdev_get_id(dev) - LP3972_DCDC1; + u16 mask = 1 << (buck * 2); + u16 val; + + val = lp3972_set_bits(lp3972, LP3972_BUCK_VOL_ENABLE_REG(buck), + mask, 0); + return val; +} + +static int lp3972_dcdc_get_voltage_sel(struct regulator_dev *dev) +{ + struct lp3972 *lp3972 = rdev_get_drvdata(dev); + int buck = rdev_get_id(dev) - LP3972_DCDC1; + u16 reg; + + reg = lp3972_reg_read(lp3972, LP3972_BUCK_VOL1_REG(buck)); + reg &= LP3972_BUCK_VOL_MASK; + + return reg; +} + +static int lp3972_dcdc_set_voltage_sel(struct regulator_dev *dev, + unsigned int selector) +{ + struct lp3972 *lp3972 = rdev_get_drvdata(dev); + int buck = rdev_get_id(dev) - LP3972_DCDC1; + int ret; + + ret = lp3972_set_bits(lp3972, LP3972_BUCK_VOL1_REG(buck), + LP3972_BUCK_VOL_MASK, selector); + if (ret) + return ret; + + if (buck != 0) + return ret; + + ret = lp3972_set_bits(lp3972, LP3972_VOL_CHANGE_REG, + LP3972_VOL_CHANGE_FLAG_MASK, LP3972_VOL_CHANGE_FLAG_GO); + if (ret) + return ret; + + return lp3972_set_bits(lp3972, LP3972_VOL_CHANGE_REG, + LP3972_VOL_CHANGE_FLAG_MASK, 0); +} + +static struct regulator_ops lp3972_dcdc_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .is_enabled = lp3972_dcdc_is_enabled, + .enable = lp3972_dcdc_enable, + .disable = lp3972_dcdc_disable, + .get_voltage_sel = lp3972_dcdc_get_voltage_sel, + .set_voltage_sel = lp3972_dcdc_set_voltage_sel, +}; + +static const struct regulator_desc regulators[] = { + { + .name = "LDO1", + .id = LP3972_LDO1, + .ops = &lp3972_ldo_ops, + .n_voltages = ARRAY_SIZE(ldo1_voltage_map), + .volt_table = ldo1_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "LDO2", + .id = LP3972_LDO2, + .ops = &lp3972_ldo_ops, + .n_voltages = ARRAY_SIZE(ldo23_voltage_map), + .volt_table = ldo23_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "LDO3", + .id = LP3972_LDO3, + .ops = &lp3972_ldo_ops, + .n_voltages = ARRAY_SIZE(ldo23_voltage_map), + .volt_table = ldo23_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "LDO4", + .id = LP3972_LDO4, + .ops = &lp3972_ldo_ops, + .n_voltages = ARRAY_SIZE(ldo4_voltage_map), + .volt_table = ldo4_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "LDO5", + .id = LP3972_LDO5, + .ops = &lp3972_ldo_ops, + .n_voltages = ARRAY_SIZE(ldo5_voltage_map), + .volt_table = ldo5_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "DCDC1", + .id = LP3972_DCDC1, + .ops = &lp3972_dcdc_ops, + .n_voltages = ARRAY_SIZE(buck1_voltage_map), + .volt_table = buck1_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "DCDC2", + .id = LP3972_DCDC2, + .ops = &lp3972_dcdc_ops, + .n_voltages = ARRAY_SIZE(buck23_voltage_map), + .volt_table = buck23_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "DCDC3", + .id = LP3972_DCDC3, + .ops = &lp3972_dcdc_ops, + .n_voltages = ARRAY_SIZE(buck23_voltage_map), + .volt_table = buck23_voltage_map, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, +}; + +static int setup_regulators(struct lp3972 *lp3972, + struct lp3972_platform_data *pdata) +{ + int i, err; + + /* Instantiate the regulators */ + for (i = 0; i < pdata->num_regulators; i++) { + struct lp3972_regulator_subdev *reg = &pdata->regulators[i]; + struct regulator_config config = { }; + struct regulator_dev *rdev; + + config.dev = lp3972->dev; + config.init_data = reg->initdata; + config.driver_data = lp3972; + + rdev = devm_regulator_register(lp3972->dev, + ®ulators[reg->id], &config); + if (IS_ERR(rdev)) { + err = PTR_ERR(rdev); + dev_err(lp3972->dev, "regulator init failed: %d\n", + err); + return err; + } + } + + return 0; +} + +static int lp3972_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct lp3972 *lp3972; + struct lp3972_platform_data *pdata = dev_get_platdata(&i2c->dev); + int ret; + u16 val; + + if (!pdata) { + dev_dbg(&i2c->dev, "No platform init data supplied\n"); + return -ENODEV; + } + + lp3972 = devm_kzalloc(&i2c->dev, sizeof(struct lp3972), GFP_KERNEL); + if (!lp3972) + return -ENOMEM; + + lp3972->i2c = i2c; + lp3972->dev = &i2c->dev; + + mutex_init(&lp3972->io_lock); + + /* Detect LP3972 */ + ret = lp3972_i2c_read(i2c, LP3972_SYS_CONTROL1_REG, 1, &val); + if (ret == 0 && + (val & SYS_CONTROL1_INIT_MASK) != SYS_CONTROL1_INIT_VAL) { + ret = -ENODEV; + dev_err(&i2c->dev, "chip reported: val = 0x%x\n", val); + } + if (ret < 0) { + dev_err(&i2c->dev, "failed to detect device. ret = %d\n", ret); + return ret; + } + + ret = setup_regulators(lp3972, pdata); + if (ret < 0) + return ret; + + i2c_set_clientdata(i2c, lp3972); + return 0; +} + +static const struct i2c_device_id lp3972_i2c_id[] = { + { "lp3972", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp3972_i2c_id); + +static struct i2c_driver lp3972_i2c_driver = { + .driver = { + .name = "lp3972", + }, + .probe = lp3972_i2c_probe, + .id_table = lp3972_i2c_id, +}; + +static int __init lp3972_module_init(void) +{ + return i2c_add_driver(&lp3972_i2c_driver); +} +subsys_initcall(lp3972_module_init); + +static void __exit lp3972_module_exit(void) +{ + i2c_del_driver(&lp3972_i2c_driver); +} +module_exit(lp3972_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Axel Lin <axel.lin@gmail.com>"); +MODULE_DESCRIPTION("LP3972 PMIC driver"); diff --git a/drivers/regulator/lp872x.c b/drivers/regulator/lp872x.c new file mode 100644 index 000000000..38992112f --- /dev/null +++ b/drivers/regulator/lp872x.c @@ -0,0 +1,1019 @@ +/* + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * 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/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/regulator/lp872x.h> +#include <linux/regulator/driver.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/regulator/of_regulator.h> + +/* Registers : LP8720/8725 shared */ +#define LP872X_GENERAL_CFG 0x00 +#define LP872X_LDO1_VOUT 0x01 +#define LP872X_LDO2_VOUT 0x02 +#define LP872X_LDO3_VOUT 0x03 +#define LP872X_LDO4_VOUT 0x04 +#define LP872X_LDO5_VOUT 0x05 + +/* Registers : LP8720 */ +#define LP8720_BUCK_VOUT1 0x06 +#define LP8720_BUCK_VOUT2 0x07 +#define LP8720_ENABLE 0x08 + +/* Registers : LP8725 */ +#define LP8725_LILO1_VOUT 0x06 +#define LP8725_LILO2_VOUT 0x07 +#define LP8725_BUCK1_VOUT1 0x08 +#define LP8725_BUCK1_VOUT2 0x09 +#define LP8725_BUCK2_VOUT1 0x0A +#define LP8725_BUCK2_VOUT2 0x0B +#define LP8725_BUCK_CTRL 0x0C +#define LP8725_LDO_CTRL 0x0D + +/* Mask/shift : LP8720/LP8725 shared */ +#define LP872X_VOUT_M 0x1F +#define LP872X_START_DELAY_M 0xE0 +#define LP872X_START_DELAY_S 5 +#define LP872X_EN_LDO1_M BIT(0) +#define LP872X_EN_LDO2_M BIT(1) +#define LP872X_EN_LDO3_M BIT(2) +#define LP872X_EN_LDO4_M BIT(3) +#define LP872X_EN_LDO5_M BIT(4) + +/* Mask/shift : LP8720 */ +#define LP8720_TIMESTEP_S 0 /* Addr 00h */ +#define LP8720_TIMESTEP_M BIT(0) +#define LP8720_EXT_DVS_M BIT(2) +#define LP8720_BUCK_FPWM_S 5 /* Addr 07h */ +#define LP8720_BUCK_FPWM_M BIT(5) +#define LP8720_EN_BUCK_M BIT(5) /* Addr 08h */ +#define LP8720_DVS_SEL_M BIT(7) + +/* Mask/shift : LP8725 */ +#define LP8725_TIMESTEP_M 0xC0 /* Addr 00h */ +#define LP8725_TIMESTEP_S 6 +#define LP8725_BUCK1_EN_M BIT(0) +#define LP8725_DVS1_M BIT(2) +#define LP8725_DVS2_M BIT(3) +#define LP8725_BUCK2_EN_M BIT(4) +#define LP8725_BUCK_CL_M 0xC0 /* Addr 09h, 0Bh */ +#define LP8725_BUCK_CL_S 6 +#define LP8725_BUCK1_FPWM_S 1 /* Addr 0Ch */ +#define LP8725_BUCK1_FPWM_M BIT(1) +#define LP8725_BUCK2_FPWM_S 5 +#define LP8725_BUCK2_FPWM_M BIT(5) +#define LP8725_EN_LILO1_M BIT(5) /* Addr 0Dh */ +#define LP8725_EN_LILO2_M BIT(6) + +/* PWM mode */ +#define LP872X_FORCE_PWM 1 +#define LP872X_AUTO_PWM 0 + +#define LP8720_NUM_REGULATORS 6 +#define LP8725_NUM_REGULATORS 9 +#define EXTERN_DVS_USED 0 +#define MAX_DELAY 6 + +/* Default DVS Mode */ +#define LP8720_DEFAULT_DVS 0 +#define LP8725_DEFAULT_DVS BIT(2) + +/* dump registers in regmap-debugfs */ +#define MAX_REGISTERS 0x0F + +enum lp872x_id { + LP8720, + LP8725, +}; + +struct lp872x { + struct regmap *regmap; + struct device *dev; + enum lp872x_id chipid; + struct lp872x_platform_data *pdata; + int num_regulators; + enum lp872x_dvs_state dvs_pin; +}; + +/* LP8720/LP8725 shared voltage table for LDOs */ +static const unsigned int lp872x_ldo_vtbl[] = { + 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, 1550000, + 1600000, 1650000, 1700000, 1750000, 1800000, 1850000, 1900000, 2000000, + 2100000, 2200000, 2300000, 2400000, 2500000, 2600000, 2650000, 2700000, + 2750000, 2800000, 2850000, 2900000, 2950000, 3000000, 3100000, 3300000, +}; + +/* LP8720 LDO4 voltage table */ +static const unsigned int lp8720_ldo4_vtbl[] = { + 800000, 850000, 900000, 1000000, 1100000, 1200000, 1250000, 1300000, + 1350000, 1400000, 1450000, 1500000, 1550000, 1600000, 1650000, 1700000, + 1750000, 1800000, 1850000, 1900000, 2000000, 2100000, 2200000, 2300000, + 2400000, 2500000, 2600000, 2650000, 2700000, 2750000, 2800000, 2850000, +}; + +/* LP8725 LILO(Low Input Low Output) voltage table */ +static const unsigned int lp8725_lilo_vtbl[] = { + 800000, 850000, 900000, 950000, 1000000, 1050000, 1100000, 1150000, + 1200000, 1250000, 1300000, 1350000, 1400000, 1500000, 1600000, 1700000, + 1800000, 1900000, 2000000, 2100000, 2200000, 2300000, 2400000, 2500000, + 2600000, 2700000, 2800000, 2850000, 2900000, 3000000, 3100000, 3300000, +}; + +/* LP8720 BUCK voltage table */ +#define EXT_R 0 /* external resistor divider */ +static const unsigned int lp8720_buck_vtbl[] = { + EXT_R, 800000, 850000, 900000, 950000, 1000000, 1050000, 1100000, + 1150000, 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, + 1550000, 1600000, 1650000, 1700000, 1750000, 1800000, 1850000, 1900000, + 1950000, 2000000, 2050000, 2100000, 2150000, 2200000, 2250000, 2300000, +}; + +/* LP8725 BUCK voltage table */ +static const unsigned int lp8725_buck_vtbl[] = { + 800000, 850000, 900000, 950000, 1000000, 1050000, 1100000, 1150000, + 1200000, 1250000, 1300000, 1350000, 1400000, 1500000, 1600000, 1700000, + 1750000, 1800000, 1850000, 1900000, 2000000, 2100000, 2200000, 2300000, + 2400000, 2500000, 2600000, 2700000, 2800000, 2850000, 2900000, 3000000, +}; + +/* LP8725 BUCK current limit */ +static const unsigned int lp8725_buck_uA[] = { + 460000, 780000, 1050000, 1370000, +}; + +static int lp872x_read_byte(struct lp872x *lp, u8 addr, u8 *data) +{ + int ret; + unsigned int val; + + ret = regmap_read(lp->regmap, addr, &val); + if (ret < 0) { + dev_err(lp->dev, "failed to read 0x%.2x\n", addr); + return ret; + } + + *data = (u8)val; + return 0; +} + +static inline int lp872x_write_byte(struct lp872x *lp, u8 addr, u8 data) +{ + return regmap_write(lp->regmap, addr, data); +} + +static inline int lp872x_update_bits(struct lp872x *lp, u8 addr, + unsigned int mask, u8 data) +{ + return regmap_update_bits(lp->regmap, addr, mask, data); +} + +static int lp872x_get_timestep_usec(struct lp872x *lp) +{ + enum lp872x_id chip = lp->chipid; + u8 val, mask, shift; + int *time_usec, size, ret; + int lp8720_time_usec[] = { 25, 50 }; + int lp8725_time_usec[] = { 32, 64, 128, 256 }; + + switch (chip) { + case LP8720: + mask = LP8720_TIMESTEP_M; + shift = LP8720_TIMESTEP_S; + time_usec = &lp8720_time_usec[0]; + size = ARRAY_SIZE(lp8720_time_usec); + break; + case LP8725: + mask = LP8725_TIMESTEP_M; + shift = LP8725_TIMESTEP_S; + time_usec = &lp8725_time_usec[0]; + size = ARRAY_SIZE(lp8725_time_usec); + break; + default: + return -EINVAL; + } + + ret = lp872x_read_byte(lp, LP872X_GENERAL_CFG, &val); + if (ret) + return ret; + + val = (val & mask) >> shift; + if (val >= size) + return -EINVAL; + + return *(time_usec + val); +} + +static int lp872x_regulator_enable_time(struct regulator_dev *rdev) +{ + struct lp872x *lp = rdev_get_drvdata(rdev); + enum lp872x_regulator_id rid = rdev_get_id(rdev); + int time_step_us = lp872x_get_timestep_usec(lp); + int ret; + u8 addr, val; + + if (time_step_us < 0) + return time_step_us; + + switch (rid) { + case LP8720_ID_LDO1 ... LP8720_ID_BUCK: + addr = LP872X_LDO1_VOUT + rid; + break; + case LP8725_ID_LDO1 ... LP8725_ID_BUCK1: + addr = LP872X_LDO1_VOUT + rid - LP8725_ID_BASE; + break; + case LP8725_ID_BUCK2: + addr = LP8725_BUCK2_VOUT1; + break; + default: + return -EINVAL; + } + + ret = lp872x_read_byte(lp, addr, &val); + if (ret) + return ret; + + val = (val & LP872X_START_DELAY_M) >> LP872X_START_DELAY_S; + + return val > MAX_DELAY ? 0 : val * time_step_us; +} + +static void lp872x_set_dvs(struct lp872x *lp, enum lp872x_dvs_sel dvs_sel, + int gpio) +{ + enum lp872x_dvs_state state; + + state = dvs_sel == SEL_V1 ? DVS_HIGH : DVS_LOW; + gpio_set_value(gpio, state); + lp->dvs_pin = state; +} + +static u8 lp872x_select_buck_vout_addr(struct lp872x *lp, + enum lp872x_regulator_id buck) +{ + u8 val, addr; + + if (lp872x_read_byte(lp, LP872X_GENERAL_CFG, &val)) + return 0; + + switch (buck) { + case LP8720_ID_BUCK: + if (val & LP8720_EXT_DVS_M) { + addr = (lp->dvs_pin == DVS_HIGH) ? + LP8720_BUCK_VOUT1 : LP8720_BUCK_VOUT2; + } else { + if (lp872x_read_byte(lp, LP8720_ENABLE, &val)) + return 0; + + addr = val & LP8720_DVS_SEL_M ? + LP8720_BUCK_VOUT1 : LP8720_BUCK_VOUT2; + } + break; + case LP8725_ID_BUCK1: + if (val & LP8725_DVS1_M) + addr = LP8725_BUCK1_VOUT1; + else + addr = (lp->dvs_pin == DVS_HIGH) ? + LP8725_BUCK1_VOUT1 : LP8725_BUCK1_VOUT2; + break; + case LP8725_ID_BUCK2: + addr = val & LP8725_DVS2_M ? + LP8725_BUCK2_VOUT1 : LP8725_BUCK2_VOUT2; + break; + default: + return 0; + } + + return addr; +} + +static bool lp872x_is_valid_buck_addr(u8 addr) +{ + switch (addr) { + case LP8720_BUCK_VOUT1: + case LP8720_BUCK_VOUT2: + case LP8725_BUCK1_VOUT1: + case LP8725_BUCK1_VOUT2: + case LP8725_BUCK2_VOUT1: + case LP8725_BUCK2_VOUT2: + return true; + default: + return false; + } +} + +static int lp872x_buck_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct lp872x *lp = rdev_get_drvdata(rdev); + enum lp872x_regulator_id buck = rdev_get_id(rdev); + u8 addr, mask = LP872X_VOUT_M; + struct lp872x_dvs *dvs = lp->pdata ? lp->pdata->dvs : NULL; + + if (dvs && gpio_is_valid(dvs->gpio)) + lp872x_set_dvs(lp, dvs->vsel, dvs->gpio); + + addr = lp872x_select_buck_vout_addr(lp, buck); + if (!lp872x_is_valid_buck_addr(addr)) + return -EINVAL; + + return lp872x_update_bits(lp, addr, mask, selector); +} + +static int lp872x_buck_get_voltage_sel(struct regulator_dev *rdev) +{ + struct lp872x *lp = rdev_get_drvdata(rdev); + enum lp872x_regulator_id buck = rdev_get_id(rdev); + u8 addr, val; + int ret; + + addr = lp872x_select_buck_vout_addr(lp, buck); + if (!lp872x_is_valid_buck_addr(addr)) + return -EINVAL; + + ret = lp872x_read_byte(lp, addr, &val); + if (ret) + return ret; + + return val & LP872X_VOUT_M; +} + +static int lp8725_buck_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct lp872x *lp = rdev_get_drvdata(rdev); + enum lp872x_regulator_id buck = rdev_get_id(rdev); + int i; + u8 addr; + + switch (buck) { + case LP8725_ID_BUCK1: + addr = LP8725_BUCK1_VOUT2; + break; + case LP8725_ID_BUCK2: + addr = LP8725_BUCK2_VOUT2; + break; + default: + return -EINVAL; + } + + for (i = ARRAY_SIZE(lp8725_buck_uA) - 1; i >= 0; i--) { + if (lp8725_buck_uA[i] >= min_uA && + lp8725_buck_uA[i] <= max_uA) + return lp872x_update_bits(lp, addr, + LP8725_BUCK_CL_M, + i << LP8725_BUCK_CL_S); + } + + return -EINVAL; +} + +static int lp8725_buck_get_current_limit(struct regulator_dev *rdev) +{ + struct lp872x *lp = rdev_get_drvdata(rdev); + enum lp872x_regulator_id buck = rdev_get_id(rdev); + u8 addr, val; + int ret; + + switch (buck) { + case LP8725_ID_BUCK1: + addr = LP8725_BUCK1_VOUT2; + break; + case LP8725_ID_BUCK2: + addr = LP8725_BUCK2_VOUT2; + break; + default: + return -EINVAL; + } + + ret = lp872x_read_byte(lp, addr, &val); + if (ret) + return ret; + + val = (val & LP8725_BUCK_CL_M) >> LP8725_BUCK_CL_S; + + return (val < ARRAY_SIZE(lp8725_buck_uA)) ? + lp8725_buck_uA[val] : -EINVAL; +} + +static int lp872x_buck_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct lp872x *lp = rdev_get_drvdata(rdev); + enum lp872x_regulator_id buck = rdev_get_id(rdev); + u8 addr, mask, shift, val; + + switch (buck) { + case LP8720_ID_BUCK: + addr = LP8720_BUCK_VOUT2; + mask = LP8720_BUCK_FPWM_M; + shift = LP8720_BUCK_FPWM_S; + break; + case LP8725_ID_BUCK1: + addr = LP8725_BUCK_CTRL; + mask = LP8725_BUCK1_FPWM_M; + shift = LP8725_BUCK1_FPWM_S; + break; + case LP8725_ID_BUCK2: + addr = LP8725_BUCK_CTRL; + mask = LP8725_BUCK2_FPWM_M; + shift = LP8725_BUCK2_FPWM_S; + break; + default: + return -EINVAL; + } + + if (mode == REGULATOR_MODE_FAST) + val = LP872X_FORCE_PWM << shift; + else if (mode == REGULATOR_MODE_NORMAL) + val = LP872X_AUTO_PWM << shift; + else + return -EINVAL; + + return lp872x_update_bits(lp, addr, mask, val); +} + +static unsigned int lp872x_buck_get_mode(struct regulator_dev *rdev) +{ + struct lp872x *lp = rdev_get_drvdata(rdev); + enum lp872x_regulator_id buck = rdev_get_id(rdev); + u8 addr, mask, val; + int ret; + + switch (buck) { + case LP8720_ID_BUCK: + addr = LP8720_BUCK_VOUT2; + mask = LP8720_BUCK_FPWM_M; + break; + case LP8725_ID_BUCK1: + addr = LP8725_BUCK_CTRL; + mask = LP8725_BUCK1_FPWM_M; + break; + case LP8725_ID_BUCK2: + addr = LP8725_BUCK_CTRL; + mask = LP8725_BUCK2_FPWM_M; + break; + default: + return -EINVAL; + } + + ret = lp872x_read_byte(lp, addr, &val); + if (ret) + return ret; + + return val & mask ? REGULATOR_MODE_FAST : REGULATOR_MODE_NORMAL; +} + +static struct regulator_ops lp872x_ldo_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .enable_time = lp872x_regulator_enable_time, +}; + +static struct regulator_ops lp8720_buck_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_voltage_sel = lp872x_buck_set_voltage_sel, + .get_voltage_sel = lp872x_buck_get_voltage_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .enable_time = lp872x_regulator_enable_time, + .set_mode = lp872x_buck_set_mode, + .get_mode = lp872x_buck_get_mode, +}; + +static struct regulator_ops lp8725_buck_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_voltage_sel = lp872x_buck_set_voltage_sel, + .get_voltage_sel = lp872x_buck_get_voltage_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .enable_time = lp872x_regulator_enable_time, + .set_mode = lp872x_buck_set_mode, + .get_mode = lp872x_buck_get_mode, + .set_current_limit = lp8725_buck_set_current_limit, + .get_current_limit = lp8725_buck_get_current_limit, +}; + +static struct regulator_desc lp8720_regulator_desc[] = { + { + .name = "ldo1", + .of_match = of_match_ptr("ldo1"), + .id = LP8720_ID_LDO1, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp872x_ldo_vtbl), + .volt_table = lp872x_ldo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP872X_LDO1_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8720_ENABLE, + .enable_mask = LP872X_EN_LDO1_M, + }, + { + .name = "ldo2", + .of_match = of_match_ptr("ldo2"), + .id = LP8720_ID_LDO2, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp872x_ldo_vtbl), + .volt_table = lp872x_ldo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP872X_LDO2_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8720_ENABLE, + .enable_mask = LP872X_EN_LDO2_M, + }, + { + .name = "ldo3", + .of_match = of_match_ptr("ldo3"), + .id = LP8720_ID_LDO3, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp872x_ldo_vtbl), + .volt_table = lp872x_ldo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP872X_LDO3_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8720_ENABLE, + .enable_mask = LP872X_EN_LDO3_M, + }, + { + .name = "ldo4", + .of_match = of_match_ptr("ldo4"), + .id = LP8720_ID_LDO4, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp8720_ldo4_vtbl), + .volt_table = lp8720_ldo4_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP872X_LDO4_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8720_ENABLE, + .enable_mask = LP872X_EN_LDO4_M, + }, + { + .name = "ldo5", + .of_match = of_match_ptr("ldo5"), + .id = LP8720_ID_LDO5, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp872x_ldo_vtbl), + .volt_table = lp872x_ldo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP872X_LDO5_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8720_ENABLE, + .enable_mask = LP872X_EN_LDO5_M, + }, + { + .name = "buck", + .of_match = of_match_ptr("buck"), + .id = LP8720_ID_BUCK, + .ops = &lp8720_buck_ops, + .n_voltages = ARRAY_SIZE(lp8720_buck_vtbl), + .volt_table = lp8720_buck_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8720_ENABLE, + .enable_mask = LP8720_EN_BUCK_M, + }, +}; + +static struct regulator_desc lp8725_regulator_desc[] = { + { + .name = "ldo1", + .of_match = of_match_ptr("ldo1"), + .id = LP8725_ID_LDO1, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp872x_ldo_vtbl), + .volt_table = lp872x_ldo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP872X_LDO1_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8725_LDO_CTRL, + .enable_mask = LP872X_EN_LDO1_M, + }, + { + .name = "ldo2", + .of_match = of_match_ptr("ldo2"), + .id = LP8725_ID_LDO2, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp872x_ldo_vtbl), + .volt_table = lp872x_ldo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP872X_LDO2_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8725_LDO_CTRL, + .enable_mask = LP872X_EN_LDO2_M, + }, + { + .name = "ldo3", + .of_match = of_match_ptr("ldo3"), + .id = LP8725_ID_LDO3, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp872x_ldo_vtbl), + .volt_table = lp872x_ldo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP872X_LDO3_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8725_LDO_CTRL, + .enable_mask = LP872X_EN_LDO3_M, + }, + { + .name = "ldo4", + .of_match = of_match_ptr("ldo4"), + .id = LP8725_ID_LDO4, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp872x_ldo_vtbl), + .volt_table = lp872x_ldo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP872X_LDO4_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8725_LDO_CTRL, + .enable_mask = LP872X_EN_LDO4_M, + }, + { + .name = "ldo5", + .of_match = of_match_ptr("ldo5"), + .id = LP8725_ID_LDO5, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp872x_ldo_vtbl), + .volt_table = lp872x_ldo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP872X_LDO5_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8725_LDO_CTRL, + .enable_mask = LP872X_EN_LDO5_M, + }, + { + .name = "lilo1", + .of_match = of_match_ptr("lilo1"), + .id = LP8725_ID_LILO1, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp8725_lilo_vtbl), + .volt_table = lp8725_lilo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8725_LILO1_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8725_LDO_CTRL, + .enable_mask = LP8725_EN_LILO1_M, + }, + { + .name = "lilo2", + .of_match = of_match_ptr("lilo2"), + .id = LP8725_ID_LILO2, + .ops = &lp872x_ldo_ops, + .n_voltages = ARRAY_SIZE(lp8725_lilo_vtbl), + .volt_table = lp8725_lilo_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8725_LILO2_VOUT, + .vsel_mask = LP872X_VOUT_M, + .enable_reg = LP8725_LDO_CTRL, + .enable_mask = LP8725_EN_LILO2_M, + }, + { + .name = "buck1", + .of_match = of_match_ptr("buck1"), + .id = LP8725_ID_BUCK1, + .ops = &lp8725_buck_ops, + .n_voltages = ARRAY_SIZE(lp8725_buck_vtbl), + .volt_table = lp8725_buck_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP872X_GENERAL_CFG, + .enable_mask = LP8725_BUCK1_EN_M, + }, + { + .name = "buck2", + .of_match = of_match_ptr("buck2"), + .id = LP8725_ID_BUCK2, + .ops = &lp8725_buck_ops, + .n_voltages = ARRAY_SIZE(lp8725_buck_vtbl), + .volt_table = lp8725_buck_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP872X_GENERAL_CFG, + .enable_mask = LP8725_BUCK2_EN_M, + }, +}; + +static int lp872x_init_dvs(struct lp872x *lp) +{ + int ret, gpio; + struct lp872x_dvs *dvs = lp->pdata ? lp->pdata->dvs : NULL; + enum lp872x_dvs_state pinstate; + u8 mask[] = { LP8720_EXT_DVS_M, LP8725_DVS1_M | LP8725_DVS2_M }; + u8 default_dvs_mode[] = { LP8720_DEFAULT_DVS, LP8725_DEFAULT_DVS }; + + if (!dvs) + goto set_default_dvs_mode; + + gpio = dvs->gpio; + if (!gpio_is_valid(gpio)) + goto set_default_dvs_mode; + + pinstate = dvs->init_state; + ret = devm_gpio_request_one(lp->dev, gpio, pinstate, "LP872X DVS"); + if (ret) { + dev_err(lp->dev, "gpio request err: %d\n", ret); + return ret; + } + + lp->dvs_pin = pinstate; + + return 0; + +set_default_dvs_mode: + return lp872x_update_bits(lp, LP872X_GENERAL_CFG, mask[lp->chipid], + default_dvs_mode[lp->chipid]); +} + +static int lp872x_hw_enable(struct lp872x *lp) +{ + int ret, gpio; + + if (!lp->pdata) + return -EINVAL; + + gpio = lp->pdata->enable_gpio; + if (!gpio_is_valid(gpio)) + return 0; + + /* Always set enable GPIO high. */ + ret = devm_gpio_request_one(lp->dev, gpio, GPIOF_OUT_INIT_HIGH, "LP872X EN"); + if (ret) { + dev_err(lp->dev, "gpio request err: %d\n", ret); + return ret; + } + + /* Each chip has a different enable delay. */ + if (lp->chipid == LP8720) + usleep_range(LP8720_ENABLE_DELAY, 1.5 * LP8720_ENABLE_DELAY); + else + usleep_range(LP8725_ENABLE_DELAY, 1.5 * LP8725_ENABLE_DELAY); + + return 0; +} + +static int lp872x_config(struct lp872x *lp) +{ + struct lp872x_platform_data *pdata = lp->pdata; + int ret; + + if (!pdata || !pdata->update_config) + goto init_dvs; + + ret = lp872x_write_byte(lp, LP872X_GENERAL_CFG, pdata->general_config); + if (ret) + return ret; + +init_dvs: + return lp872x_init_dvs(lp); +} + +static struct regulator_init_data +*lp872x_find_regulator_init_data(int id, struct lp872x *lp) +{ + struct lp872x_platform_data *pdata = lp->pdata; + int i; + + if (!pdata) + return NULL; + + for (i = 0; i < lp->num_regulators; i++) { + if (pdata->regulator_data[i].id == id) + return pdata->regulator_data[i].init_data; + } + + return NULL; +} + +static int lp872x_regulator_register(struct lp872x *lp) +{ + struct regulator_desc *desc; + struct regulator_config cfg = { }; + struct regulator_dev *rdev; + int i; + + for (i = 0; i < lp->num_regulators; i++) { + desc = (lp->chipid == LP8720) ? &lp8720_regulator_desc[i] : + &lp8725_regulator_desc[i]; + + cfg.dev = lp->dev; + cfg.init_data = lp872x_find_regulator_init_data(desc->id, lp); + cfg.driver_data = lp; + cfg.regmap = lp->regmap; + + rdev = devm_regulator_register(lp->dev, desc, &cfg); + if (IS_ERR(rdev)) { + dev_err(lp->dev, "regulator register err"); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct regmap_config lp872x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX_REGISTERS, +}; + +#ifdef CONFIG_OF + +#define LP872X_VALID_OPMODE (REGULATOR_MODE_FAST | REGULATOR_MODE_NORMAL) + +static struct of_regulator_match lp8720_matches[] = { + { .name = "ldo1", .driver_data = (void *)LP8720_ID_LDO1, }, + { .name = "ldo2", .driver_data = (void *)LP8720_ID_LDO2, }, + { .name = "ldo3", .driver_data = (void *)LP8720_ID_LDO3, }, + { .name = "ldo4", .driver_data = (void *)LP8720_ID_LDO4, }, + { .name = "ldo5", .driver_data = (void *)LP8720_ID_LDO5, }, + { .name = "buck", .driver_data = (void *)LP8720_ID_BUCK, }, +}; + +static struct of_regulator_match lp8725_matches[] = { + { .name = "ldo1", .driver_data = (void *)LP8725_ID_LDO1, }, + { .name = "ldo2", .driver_data = (void *)LP8725_ID_LDO2, }, + { .name = "ldo3", .driver_data = (void *)LP8725_ID_LDO3, }, + { .name = "ldo4", .driver_data = (void *)LP8725_ID_LDO4, }, + { .name = "ldo5", .driver_data = (void *)LP8725_ID_LDO5, }, + { .name = "lilo1", .driver_data = (void *)LP8725_ID_LILO1, }, + { .name = "lilo2", .driver_data = (void *)LP8725_ID_LILO2, }, + { .name = "buck1", .driver_data = (void *)LP8725_ID_BUCK1, }, + { .name = "buck2", .driver_data = (void *)LP8725_ID_BUCK2, }, +}; + +static struct lp872x_platform_data +*lp872x_populate_pdata_from_dt(struct device *dev, enum lp872x_id which) +{ + struct device_node *np = dev->of_node; + struct lp872x_platform_data *pdata; + struct of_regulator_match *match; + int num_matches; + int count; + int i; + u8 dvs_state; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + of_property_read_u8(np, "ti,general-config", &pdata->general_config); + if (of_find_property(np, "ti,update-config", NULL)) + pdata->update_config = true; + + pdata->dvs = devm_kzalloc(dev, sizeof(struct lp872x_dvs), GFP_KERNEL); + if (!pdata->dvs) + return ERR_PTR(-ENOMEM); + + pdata->dvs->gpio = of_get_named_gpio(np, "ti,dvs-gpio", 0); + of_property_read_u8(np, "ti,dvs-vsel", (u8 *)&pdata->dvs->vsel); + of_property_read_u8(np, "ti,dvs-state", &dvs_state); + pdata->dvs->init_state = dvs_state ? DVS_HIGH : DVS_LOW; + + pdata->enable_gpio = of_get_named_gpio(np, "enable-gpios", 0); + + if (of_get_child_count(np) == 0) + goto out; + + switch (which) { + case LP8720: + match = lp8720_matches; + num_matches = ARRAY_SIZE(lp8720_matches); + break; + case LP8725: + match = lp8725_matches; + num_matches = ARRAY_SIZE(lp8725_matches); + break; + default: + goto out; + } + + count = of_regulator_match(dev, np, match, num_matches); + if (count <= 0) + goto out; + + for (i = 0; i < num_matches; i++) { + pdata->regulator_data[i].id = + (enum lp872x_regulator_id)match[i].driver_data; + pdata->regulator_data[i].init_data = match[i].init_data; + } +out: + return pdata; +} +#else +static struct lp872x_platform_data +*lp872x_populate_pdata_from_dt(struct device *dev, enum lp872x_id which) +{ + return NULL; +} +#endif + +static int lp872x_probe(struct i2c_client *cl, const struct i2c_device_id *id) +{ + struct lp872x *lp; + struct lp872x_platform_data *pdata; + int ret; + const int lp872x_num_regulators[] = { + [LP8720] = LP8720_NUM_REGULATORS, + [LP8725] = LP8725_NUM_REGULATORS, + }; + + if (cl->dev.of_node) { + pdata = lp872x_populate_pdata_from_dt(&cl->dev, + (enum lp872x_id)id->driver_data); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } else { + pdata = dev_get_platdata(&cl->dev); + } + + lp = devm_kzalloc(&cl->dev, sizeof(struct lp872x), GFP_KERNEL); + if (!lp) + return -ENOMEM; + + lp->num_regulators = lp872x_num_regulators[id->driver_data]; + + lp->regmap = devm_regmap_init_i2c(cl, &lp872x_regmap_config); + if (IS_ERR(lp->regmap)) { + ret = PTR_ERR(lp->regmap); + dev_err(&cl->dev, "regmap init i2c err: %d\n", ret); + return ret; + } + + lp->dev = &cl->dev; + lp->pdata = pdata; + lp->chipid = id->driver_data; + i2c_set_clientdata(cl, lp); + + ret = lp872x_hw_enable(lp); + if (ret) + return ret; + + ret = lp872x_config(lp); + if (ret) + return ret; + + return lp872x_regulator_register(lp); +} + +static const struct of_device_id lp872x_dt_ids[] = { + { .compatible = "ti,lp8720", }, + { .compatible = "ti,lp8725", }, + { } +}; +MODULE_DEVICE_TABLE(of, lp872x_dt_ids); + +static const struct i2c_device_id lp872x_ids[] = { + {"lp8720", LP8720}, + {"lp8725", LP8725}, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp872x_ids); + +static struct i2c_driver lp872x_driver = { + .driver = { + .name = "lp872x", + .of_match_table = of_match_ptr(lp872x_dt_ids), + }, + .probe = lp872x_probe, + .id_table = lp872x_ids, +}; + +module_i2c_driver(lp872x_driver); + +MODULE_DESCRIPTION("TI/National Semiconductor LP872x PMU Regulator Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/lp873x-regulator.c b/drivers/regulator/lp873x-regulator.c new file mode 100644 index 000000000..70e3df653 --- /dev/null +++ b/drivers/regulator/lp873x-regulator.c @@ -0,0 +1,240 @@ +/* + * Regulator driver for LP873X PMIC + * + * Copyright (C) 2016 Texas Instruments 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 version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether expressed or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License version 2 for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <linux/mfd/lp873x.h> + +#define LP873X_REGULATOR(_name, _id, _of, _ops, _n, _vr, _vm, _er, _em, \ + _delay, _lr, _cr) \ + [_id] = { \ + .desc = { \ + .name = _name, \ + .supply_name = _of "-in", \ + .id = _id, \ + .of_match = of_match_ptr(_of), \ + .regulators_node = of_match_ptr("regulators"),\ + .ops = &_ops, \ + .n_voltages = _n, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .vsel_reg = _vr, \ + .vsel_mask = _vm, \ + .enable_reg = _er, \ + .enable_mask = _em, \ + .ramp_delay = _delay, \ + .linear_ranges = _lr, \ + .n_linear_ranges = ARRAY_SIZE(_lr), \ + }, \ + .ctrl2_reg = _cr, \ + } + +struct lp873x_regulator { + struct regulator_desc desc; + unsigned int ctrl2_reg; +}; + +static const struct lp873x_regulator regulators[]; + +static const struct regulator_linear_range buck0_buck1_ranges[] = { + REGULATOR_LINEAR_RANGE(0, 0x0, 0x13, 0), + REGULATOR_LINEAR_RANGE(700000, 0x14, 0x17, 10000), + REGULATOR_LINEAR_RANGE(735000, 0x18, 0x9d, 5000), + REGULATOR_LINEAR_RANGE(1420000, 0x9e, 0xff, 20000), +}; + +static const struct regulator_linear_range ldo0_ldo1_ranges[] = { + REGULATOR_LINEAR_RANGE(800000, 0x0, 0x19, 100000), +}; + +static unsigned int lp873x_buck_ramp_delay[] = { + 30000, 15000, 10000, 7500, 3800, 1900, 940, 470 +}; + +/* LP873X BUCK current limit */ +static const unsigned int lp873x_buck_uA[] = { + 1500000, 2000000, 2500000, 3000000, 3500000, 4000000, +}; + +static int lp873x_buck_set_ramp_delay(struct regulator_dev *rdev, + int ramp_delay) +{ + int id = rdev_get_id(rdev); + struct lp873x *lp873 = rdev_get_drvdata(rdev); + unsigned int reg; + int ret; + + if (ramp_delay <= 470) + reg = 7; + else if (ramp_delay <= 940) + reg = 6; + else if (ramp_delay <= 1900) + reg = 5; + else if (ramp_delay <= 3800) + reg = 4; + else if (ramp_delay <= 7500) + reg = 3; + else if (ramp_delay <= 10000) + reg = 2; + else if (ramp_delay <= 15000) + reg = 1; + else + reg = 0; + + ret = regmap_update_bits(lp873->regmap, regulators[id].ctrl2_reg, + LP873X_BUCK0_CTRL_2_BUCK0_SLEW_RATE, + reg << __ffs(LP873X_BUCK0_CTRL_2_BUCK0_SLEW_RATE)); + if (ret) { + dev_err(lp873->dev, "SLEW RATE write failed: %d\n", ret); + return ret; + } + + rdev->constraints->ramp_delay = lp873x_buck_ramp_delay[reg]; + + return 0; +} + +static int lp873x_buck_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + int id = rdev_get_id(rdev); + struct lp873x *lp873 = rdev_get_drvdata(rdev); + int i; + + for (i = ARRAY_SIZE(lp873x_buck_uA) - 1; i >= 0; i--) { + if (lp873x_buck_uA[i] >= min_uA && + lp873x_buck_uA[i] <= max_uA) + return regmap_update_bits(lp873->regmap, + regulators[id].ctrl2_reg, + LP873X_BUCK0_CTRL_2_BUCK0_ILIM, + i << __ffs(LP873X_BUCK0_CTRL_2_BUCK0_ILIM)); + } + + return -EINVAL; +} + +static int lp873x_buck_get_current_limit(struct regulator_dev *rdev) +{ + int id = rdev_get_id(rdev); + struct lp873x *lp873 = rdev_get_drvdata(rdev); + int ret; + unsigned int val; + + ret = regmap_read(lp873->regmap, regulators[id].ctrl2_reg, &val); + if (ret) + return ret; + + val = (val & LP873X_BUCK0_CTRL_2_BUCK0_ILIM) >> + __ffs(LP873X_BUCK0_CTRL_2_BUCK0_ILIM); + + return (val < ARRAY_SIZE(lp873x_buck_uA)) ? + lp873x_buck_uA[val] : -EINVAL; +} + +/* Operations permitted on BUCK0, BUCK1 */ +static struct regulator_ops lp873x_buck01_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = lp873x_buck_set_ramp_delay, + .set_current_limit = lp873x_buck_set_current_limit, + .get_current_limit = lp873x_buck_get_current_limit, +}; + +/* Operations permitted on LDO0 and LDO1 */ +static struct regulator_ops lp873x_ldo01_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, +}; + +static const struct lp873x_regulator regulators[] = { + LP873X_REGULATOR("BUCK0", LP873X_BUCK_0, "buck0", lp873x_buck01_ops, + 256, LP873X_REG_BUCK0_VOUT, + LP873X_BUCK0_VOUT_BUCK0_VSET, LP873X_REG_BUCK0_CTRL_1, + LP873X_BUCK0_CTRL_1_BUCK0_EN, 10000, + buck0_buck1_ranges, LP873X_REG_BUCK0_CTRL_2), + LP873X_REGULATOR("BUCK1", LP873X_BUCK_1, "buck1", lp873x_buck01_ops, + 256, LP873X_REG_BUCK1_VOUT, + LP873X_BUCK1_VOUT_BUCK1_VSET, LP873X_REG_BUCK1_CTRL_1, + LP873X_BUCK1_CTRL_1_BUCK1_EN, 10000, + buck0_buck1_ranges, LP873X_REG_BUCK1_CTRL_2), + LP873X_REGULATOR("LDO0", LP873X_LDO_0, "ldo0", lp873x_ldo01_ops, 26, + LP873X_REG_LDO0_VOUT, LP873X_LDO0_VOUT_LDO0_VSET, + LP873X_REG_LDO0_CTRL, + LP873X_LDO0_CTRL_LDO0_EN, 0, ldo0_ldo1_ranges, 0xFF), + LP873X_REGULATOR("LDO1", LP873X_LDO_1, "ldo1", lp873x_ldo01_ops, 26, + LP873X_REG_LDO1_VOUT, LP873X_LDO1_VOUT_LDO1_VSET, + LP873X_REG_LDO1_CTRL, + LP873X_LDO1_CTRL_LDO1_EN, 0, ldo0_ldo1_ranges, 0xFF), +}; + +static int lp873x_regulator_probe(struct platform_device *pdev) +{ + struct lp873x *lp873 = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = { }; + struct regulator_dev *rdev; + int i; + + platform_set_drvdata(pdev, lp873); + + config.dev = &pdev->dev; + config.dev->of_node = lp873->dev->of_node; + config.driver_data = lp873; + config.regmap = lp873->regmap; + + for (i = 0; i < ARRAY_SIZE(regulators); i++) { + rdev = devm_regulator_register(&pdev->dev, ®ulators[i].desc, + &config); + if (IS_ERR(rdev)) { + dev_err(lp873->dev, "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct platform_device_id lp873x_regulator_id_table[] = { + { "lp873x-regulator", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, lp873x_regulator_id_table); + +static struct platform_driver lp873x_regulator_driver = { + .driver = { + .name = "lp873x-pmic", + }, + .probe = lp873x_regulator_probe, + .id_table = lp873x_regulator_id_table, +}; +module_platform_driver(lp873x_regulator_driver); + +MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>"); +MODULE_DESCRIPTION("LP873X voltage regulator driver"); +MODULE_ALIAS("platform:lp873x-pmic"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/lp8755.c b/drivers/regulator/lp8755.c new file mode 100644 index 000000000..244822bb6 --- /dev/null +++ b/drivers/regulator/lp8755.c @@ -0,0 +1,548 @@ +/* + * LP8755 High Performance Power Management Unit : System Interface Driver + * (based on rev. 0.26) + * Copyright 2012 Texas Instruments + * + * Author: Daniel(Geon Si) Jeong <daniel.jeong@ti.com> + * + * 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/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/regmap.h> +#include <linux/uaccess.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/platform_data/lp8755.h> + +#define LP8755_REG_BUCK0 0x00 +#define LP8755_REG_BUCK1 0x03 +#define LP8755_REG_BUCK2 0x04 +#define LP8755_REG_BUCK3 0x01 +#define LP8755_REG_BUCK4 0x05 +#define LP8755_REG_BUCK5 0x02 +#define LP8755_REG_MAX 0xFF + +#define LP8755_BUCK_EN_M BIT(7) +#define LP8755_BUCK_LINEAR_OUT_MAX 0x76 +#define LP8755_BUCK_VOUT_M 0x7F + +struct lp8755_mphase { + int nreg; + int buck_num[LP8755_BUCK_MAX]; +}; + +struct lp8755_chip { + struct device *dev; + struct regmap *regmap; + struct lp8755_platform_data *pdata; + + int irq; + unsigned int irqmask; + + int mphase; + struct regulator_dev *rdev[LP8755_BUCK_MAX]; +}; + +/** + *lp8755_read : read a single register value from lp8755. + *@pchip : device to read from + *@reg : register to read from + *@val : pointer to store read value + */ +static int lp8755_read(struct lp8755_chip *pchip, unsigned int reg, + unsigned int *val) +{ + return regmap_read(pchip->regmap, reg, val); +} + +/** + *lp8755_write : write a single register value to lp8755. + *@pchip : device to write to + *@reg : register to write to + *@val : value to be written + */ +static int lp8755_write(struct lp8755_chip *pchip, unsigned int reg, + unsigned int val) +{ + return regmap_write(pchip->regmap, reg, val); +} + +/** + *lp8755_update_bits : set the values of bit fields in lp8755 register. + *@pchip : device to read from + *@reg : register to update + *@mask : bitmask to be changed + *@val : value for bitmask + */ +static int lp8755_update_bits(struct lp8755_chip *pchip, unsigned int reg, + unsigned int mask, unsigned int val) +{ + return regmap_update_bits(pchip->regmap, reg, mask, val); +} + +static int lp8755_buck_enable_time(struct regulator_dev *rdev) +{ + int ret; + unsigned int regval; + enum lp8755_bucks id = rdev_get_id(rdev); + struct lp8755_chip *pchip = rdev_get_drvdata(rdev); + + ret = lp8755_read(pchip, 0x12 + id, ®val); + if (ret < 0) { + dev_err(pchip->dev, "i2c access error %s\n", __func__); + return ret; + } + return (regval & 0xff) * 100; +} + +static int lp8755_buck_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + int ret; + unsigned int regbval = 0x0; + enum lp8755_bucks id = rdev_get_id(rdev); + struct lp8755_chip *pchip = rdev_get_drvdata(rdev); + + switch (mode) { + case REGULATOR_MODE_FAST: + /* forced pwm mode */ + regbval = (0x01 << id); + break; + case REGULATOR_MODE_NORMAL: + /* enable automatic pwm/pfm mode */ + ret = lp8755_update_bits(pchip, 0x08 + id, 0x20, 0x00); + if (ret < 0) + goto err_i2c; + break; + case REGULATOR_MODE_IDLE: + /* enable automatic pwm/pfm/lppfm mode */ + ret = lp8755_update_bits(pchip, 0x08 + id, 0x20, 0x20); + if (ret < 0) + goto err_i2c; + + ret = lp8755_update_bits(pchip, 0x10, 0x01, 0x01); + if (ret < 0) + goto err_i2c; + break; + default: + dev_err(pchip->dev, "Not supported buck mode %s\n", __func__); + /* forced pwm mode */ + regbval = (0x01 << id); + } + + ret = lp8755_update_bits(pchip, 0x06, 0x01 << id, regbval); + if (ret < 0) + goto err_i2c; + return ret; +err_i2c: + dev_err(pchip->dev, "i2c access error %s\n", __func__); + return ret; +} + +static unsigned int lp8755_buck_get_mode(struct regulator_dev *rdev) +{ + int ret; + unsigned int regval; + enum lp8755_bucks id = rdev_get_id(rdev); + struct lp8755_chip *pchip = rdev_get_drvdata(rdev); + + ret = lp8755_read(pchip, 0x06, ®val); + if (ret < 0) + goto err_i2c; + + /* mode fast means forced pwm mode */ + if (regval & (0x01 << id)) + return REGULATOR_MODE_FAST; + + ret = lp8755_read(pchip, 0x08 + id, ®val); + if (ret < 0) + goto err_i2c; + + /* mode idle means automatic pwm/pfm/lppfm mode */ + if (regval & 0x20) + return REGULATOR_MODE_IDLE; + + /* mode normal means automatic pwm/pfm mode */ + return REGULATOR_MODE_NORMAL; + +err_i2c: + dev_err(pchip->dev, "i2c access error %s\n", __func__); + return 0; +} + +static int lp8755_buck_set_ramp(struct regulator_dev *rdev, int ramp) +{ + int ret; + unsigned int regval = 0x00; + enum lp8755_bucks id = rdev_get_id(rdev); + struct lp8755_chip *pchip = rdev_get_drvdata(rdev); + + /* uV/us */ + switch (ramp) { + case 0 ... 230: + regval = 0x07; + break; + case 231 ... 470: + regval = 0x06; + break; + case 471 ... 940: + regval = 0x05; + break; + case 941 ... 1900: + regval = 0x04; + break; + case 1901 ... 3800: + regval = 0x03; + break; + case 3801 ... 7500: + regval = 0x02; + break; + case 7501 ... 15000: + regval = 0x01; + break; + case 15001 ... 30000: + regval = 0x00; + break; + default: + dev_err(pchip->dev, + "Not supported ramp value %d %s\n", ramp, __func__); + return -EINVAL; + } + + ret = lp8755_update_bits(pchip, 0x07 + id, 0x07, regval); + if (ret < 0) + goto err_i2c; + return ret; +err_i2c: + dev_err(pchip->dev, "i2c access error %s\n", __func__); + return ret; +} + +static const struct regulator_ops lp8755_buck_ops = { + .map_voltage = regulator_map_voltage_linear, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .enable_time = lp8755_buck_enable_time, + .set_mode = lp8755_buck_set_mode, + .get_mode = lp8755_buck_get_mode, + .set_ramp_delay = lp8755_buck_set_ramp, +}; + +#define lp8755_rail(_id) "lp8755_buck"#_id +#define lp8755_buck_init(_id)\ +{\ + .constraints = {\ + .name = lp8755_rail(_id),\ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE,\ + .min_uV = 500000,\ + .max_uV = 1675000,\ + },\ +} + +static struct regulator_init_data lp8755_reg_default[LP8755_BUCK_MAX] = { + [LP8755_BUCK0] = lp8755_buck_init(0), + [LP8755_BUCK1] = lp8755_buck_init(1), + [LP8755_BUCK2] = lp8755_buck_init(2), + [LP8755_BUCK3] = lp8755_buck_init(3), + [LP8755_BUCK4] = lp8755_buck_init(4), + [LP8755_BUCK5] = lp8755_buck_init(5), +}; + +static const struct lp8755_mphase mphase_buck[MPHASE_CONF_MAX] = { + { 3, { LP8755_BUCK0, LP8755_BUCK3, LP8755_BUCK5 } }, + { 6, { LP8755_BUCK0, LP8755_BUCK1, LP8755_BUCK2, LP8755_BUCK3, + LP8755_BUCK4, LP8755_BUCK5 } }, + { 5, { LP8755_BUCK0, LP8755_BUCK2, LP8755_BUCK3, LP8755_BUCK4, + LP8755_BUCK5} }, + { 4, { LP8755_BUCK0, LP8755_BUCK3, LP8755_BUCK4, LP8755_BUCK5} }, + { 3, { LP8755_BUCK0, LP8755_BUCK4, LP8755_BUCK5} }, + { 2, { LP8755_BUCK0, LP8755_BUCK5} }, + { 1, { LP8755_BUCK0} }, + { 2, { LP8755_BUCK0, LP8755_BUCK3} }, + { 4, { LP8755_BUCK0, LP8755_BUCK2, LP8755_BUCK3, LP8755_BUCK5} }, +}; + +static int lp8755_init_data(struct lp8755_chip *pchip) +{ + unsigned int regval; + int ret, icnt, buck_num; + struct lp8755_platform_data *pdata = pchip->pdata; + + /* read back muti-phase configuration */ + ret = lp8755_read(pchip, 0x3D, ®val); + if (ret < 0) + goto out_i2c_error; + pchip->mphase = regval & 0x0F; + + /* set default data based on multi-phase config */ + for (icnt = 0; icnt < mphase_buck[pchip->mphase].nreg; icnt++) { + buck_num = mphase_buck[pchip->mphase].buck_num[icnt]; + pdata->buck_data[buck_num] = &lp8755_reg_default[buck_num]; + } + return ret; + +out_i2c_error: + dev_err(pchip->dev, "i2c access error %s\n", __func__); + return ret; +} + +#define lp8755_buck_desc(_id)\ +{\ + .name = lp8755_rail(_id),\ + .id = LP8755_BUCK##_id,\ + .ops = &lp8755_buck_ops,\ + .n_voltages = LP8755_BUCK_LINEAR_OUT_MAX+1,\ + .uV_step = 10000,\ + .min_uV = 500000,\ + .type = REGULATOR_VOLTAGE,\ + .owner = THIS_MODULE,\ + .enable_reg = LP8755_REG_BUCK##_id,\ + .enable_mask = LP8755_BUCK_EN_M,\ + .vsel_reg = LP8755_REG_BUCK##_id,\ + .vsel_mask = LP8755_BUCK_VOUT_M,\ +} + +static struct regulator_desc lp8755_regulators[] = { + lp8755_buck_desc(0), + lp8755_buck_desc(1), + lp8755_buck_desc(2), + lp8755_buck_desc(3), + lp8755_buck_desc(4), + lp8755_buck_desc(5), +}; + +static int lp8755_regulator_init(struct lp8755_chip *pchip) +{ + int ret, icnt, buck_num; + struct lp8755_platform_data *pdata = pchip->pdata; + struct regulator_config rconfig = { }; + + rconfig.regmap = pchip->regmap; + rconfig.dev = pchip->dev; + rconfig.driver_data = pchip; + + for (icnt = 0; icnt < mphase_buck[pchip->mphase].nreg; icnt++) { + buck_num = mphase_buck[pchip->mphase].buck_num[icnt]; + rconfig.init_data = pdata->buck_data[buck_num]; + rconfig.of_node = pchip->dev->of_node; + pchip->rdev[buck_num] = + devm_regulator_register(pchip->dev, + &lp8755_regulators[buck_num], &rconfig); + if (IS_ERR(pchip->rdev[buck_num])) { + ret = PTR_ERR(pchip->rdev[buck_num]); + pchip->rdev[buck_num] = NULL; + dev_err(pchip->dev, "regulator init failed: buck %d\n", + buck_num); + return ret; + } + } + + return 0; +} + +static irqreturn_t lp8755_irq_handler(int irq, void *data) +{ + int ret, icnt; + unsigned int flag0, flag1; + struct lp8755_chip *pchip = data; + + /* read flag0 register */ + ret = lp8755_read(pchip, 0x0D, &flag0); + if (ret < 0) + goto err_i2c; + /* clear flag register to pull up int. pin */ + ret = lp8755_write(pchip, 0x0D, 0x00); + if (ret < 0) + goto err_i2c; + + /* sent power fault detection event to specific regulator */ + for (icnt = 0; icnt < LP8755_BUCK_MAX; icnt++) + if ((flag0 & (0x4 << icnt)) + && (pchip->irqmask & (0x04 << icnt)) + && (pchip->rdev[icnt] != NULL)) + regulator_notifier_call_chain(pchip->rdev[icnt], + LP8755_EVENT_PWR_FAULT, + NULL); + + /* read flag1 register */ + ret = lp8755_read(pchip, 0x0E, &flag1); + if (ret < 0) + goto err_i2c; + /* clear flag register to pull up int. pin */ + ret = lp8755_write(pchip, 0x0E, 0x00); + if (ret < 0) + goto err_i2c; + + /* send OCP event to all regualtor devices */ + if ((flag1 & 0x01) && (pchip->irqmask & 0x01)) + for (icnt = 0; icnt < LP8755_BUCK_MAX; icnt++) + if (pchip->rdev[icnt] != NULL) + regulator_notifier_call_chain(pchip->rdev[icnt], + LP8755_EVENT_OCP, + NULL); + + /* send OVP event to all regualtor devices */ + if ((flag1 & 0x02) && (pchip->irqmask & 0x02)) + for (icnt = 0; icnt < LP8755_BUCK_MAX; icnt++) + if (pchip->rdev[icnt] != NULL) + regulator_notifier_call_chain(pchip->rdev[icnt], + LP8755_EVENT_OVP, + NULL); + return IRQ_HANDLED; + +err_i2c: + dev_err(pchip->dev, "i2c access error %s\n", __func__); + return IRQ_NONE; +} + +static int lp8755_int_config(struct lp8755_chip *pchip) +{ + int ret; + unsigned int regval; + + if (pchip->irq == 0) { + dev_warn(pchip->dev, "not use interrupt : %s\n", __func__); + return 0; + } + + ret = lp8755_read(pchip, 0x0F, ®val); + if (ret < 0) { + dev_err(pchip->dev, "i2c access error %s\n", __func__); + return ret; + } + + pchip->irqmask = regval; + return devm_request_threaded_irq(pchip->dev, pchip->irq, NULL, + lp8755_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "lp8755-irq", pchip); +} + +static const struct regmap_config lp8755_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LP8755_REG_MAX, +}; + +static int lp8755_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret, icnt; + struct lp8755_chip *pchip; + struct lp8755_platform_data *pdata = dev_get_platdata(&client->dev); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "i2c functionality check fail.\n"); + return -EOPNOTSUPP; + } + + pchip = devm_kzalloc(&client->dev, + sizeof(struct lp8755_chip), GFP_KERNEL); + if (!pchip) + return -ENOMEM; + + pchip->dev = &client->dev; + pchip->regmap = devm_regmap_init_i2c(client, &lp8755_regmap); + if (IS_ERR(pchip->regmap)) { + ret = PTR_ERR(pchip->regmap); + dev_err(&client->dev, "fail to allocate regmap %d\n", ret); + return ret; + } + i2c_set_clientdata(client, pchip); + + if (pdata != NULL) { + pchip->pdata = pdata; + pchip->mphase = pdata->mphase; + } else { + pchip->pdata = devm_kzalloc(pchip->dev, + sizeof(struct lp8755_platform_data), + GFP_KERNEL); + if (!pchip->pdata) + return -ENOMEM; + ret = lp8755_init_data(pchip); + if (ret < 0) { + dev_err(&client->dev, "fail to initialize chip\n"); + return ret; + } + } + + ret = lp8755_regulator_init(pchip); + if (ret < 0) { + dev_err(&client->dev, "fail to initialize regulators\n"); + goto err; + } + + pchip->irq = client->irq; + ret = lp8755_int_config(pchip); + if (ret < 0) { + dev_err(&client->dev, "fail to irq config\n"); + goto err; + } + + return ret; + +err: + /* output disable */ + for (icnt = 0; icnt < LP8755_BUCK_MAX; icnt++) + lp8755_write(pchip, icnt, 0x00); + + return ret; +} + +static int lp8755_remove(struct i2c_client *client) +{ + int icnt; + struct lp8755_chip *pchip = i2c_get_clientdata(client); + + for (icnt = 0; icnt < LP8755_BUCK_MAX; icnt++) + lp8755_write(pchip, icnt, 0x00); + + return 0; +} + +static const struct i2c_device_id lp8755_id[] = { + {LP8755_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lp8755_id); + +static struct i2c_driver lp8755_i2c_driver = { + .driver = { + .name = LP8755_NAME, + }, + .probe = lp8755_probe, + .remove = lp8755_remove, + .id_table = lp8755_id, +}; + +static int __init lp8755_init(void) +{ + return i2c_add_driver(&lp8755_i2c_driver); +} + +subsys_initcall(lp8755_init); + +static void __exit lp8755_exit(void) +{ + i2c_del_driver(&lp8755_i2c_driver); +} + +module_exit(lp8755_exit); + +MODULE_DESCRIPTION("Texas Instruments lp8755 driver"); +MODULE_AUTHOR("Daniel Jeong <daniel.jeong@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/lp87565-regulator.c b/drivers/regulator/lp87565-regulator.c new file mode 100644 index 000000000..7cd686240 --- /dev/null +++ b/drivers/regulator/lp87565-regulator.c @@ -0,0 +1,241 @@ +/* + * Regulator driver for LP87565 PMIC + * + * Copyright (C) 2017 Texas Instruments 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. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <linux/mfd/lp87565.h> + +#define LP87565_REGULATOR(_name, _id, _of, _ops, _n, _vr, _vm, _er, _em, \ + _delay, _lr, _cr) \ + [_id] = { \ + .desc = { \ + .name = _name, \ + .supply_name = _of "-in", \ + .id = _id, \ + .of_match = of_match_ptr(_of), \ + .regulators_node = of_match_ptr("regulators"),\ + .ops = &_ops, \ + .n_voltages = _n, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .vsel_reg = _vr, \ + .vsel_mask = _vm, \ + .enable_reg = _er, \ + .enable_mask = _em, \ + .ramp_delay = _delay, \ + .linear_ranges = _lr, \ + .n_linear_ranges = ARRAY_SIZE(_lr), \ + }, \ + .ctrl2_reg = _cr, \ + } + +struct lp87565_regulator { + struct regulator_desc desc; + unsigned int ctrl2_reg; +}; + +static const struct lp87565_regulator regulators[]; + +static const struct regulator_linear_range buck0_1_2_3_ranges[] = { + REGULATOR_LINEAR_RANGE(600000, 0xA, 0x17, 10000), + REGULATOR_LINEAR_RANGE(735000, 0x18, 0x9d, 5000), + REGULATOR_LINEAR_RANGE(1420000, 0x9e, 0xff, 20000), +}; + +static unsigned int lp87565_buck_ramp_delay[] = { + 30000, 15000, 10000, 7500, 3800, 1900, 940, 470 +}; + +/* LP87565 BUCK current limit */ +static const unsigned int lp87565_buck_uA[] = { + 1500000, 2000000, 2500000, 3000000, 3500000, 4000000, 4500000, 5000000, +}; + +static int lp87565_buck_set_ramp_delay(struct regulator_dev *rdev, + int ramp_delay) +{ + int id = rdev_get_id(rdev); + struct lp87565 *lp87565 = rdev_get_drvdata(rdev); + unsigned int reg; + int ret; + + if (ramp_delay <= 470) + reg = 7; + else if (ramp_delay <= 940) + reg = 6; + else if (ramp_delay <= 1900) + reg = 5; + else if (ramp_delay <= 3800) + reg = 4; + else if (ramp_delay <= 7500) + reg = 3; + else if (ramp_delay <= 10000) + reg = 2; + else if (ramp_delay <= 15000) + reg = 1; + else + reg = 0; + + ret = regmap_update_bits(lp87565->regmap, regulators[id].ctrl2_reg, + LP87565_BUCK_CTRL_2_SLEW_RATE, + reg << __ffs(LP87565_BUCK_CTRL_2_SLEW_RATE)); + if (ret) { + dev_err(lp87565->dev, "SLEW RATE write failed: %d\n", ret); + return ret; + } + + rdev->constraints->ramp_delay = lp87565_buck_ramp_delay[reg]; + + /* Conservatively give a 15% margin */ + rdev->constraints->ramp_delay = + rdev->constraints->ramp_delay * 85 / 100; + + return 0; +} + +static int lp87565_buck_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + int id = rdev_get_id(rdev); + struct lp87565 *lp87565 = rdev_get_drvdata(rdev); + int i; + + for (i = ARRAY_SIZE(lp87565_buck_uA) - 1; i >= 0; i--) { + if (lp87565_buck_uA[i] >= min_uA && + lp87565_buck_uA[i] <= max_uA) + return regmap_update_bits(lp87565->regmap, + regulators[id].ctrl2_reg, + LP87565_BUCK_CTRL_2_ILIM, + i << __ffs(LP87565_BUCK_CTRL_2_ILIM)); + } + + return -EINVAL; +} + +static int lp87565_buck_get_current_limit(struct regulator_dev *rdev) +{ + int id = rdev_get_id(rdev); + struct lp87565 *lp87565 = rdev_get_drvdata(rdev); + int ret; + unsigned int val; + + ret = regmap_read(lp87565->regmap, regulators[id].ctrl2_reg, &val); + if (ret) + return ret; + + val = (val & LP87565_BUCK_CTRL_2_ILIM) >> + __ffs(LP87565_BUCK_CTRL_2_ILIM); + + return (val < ARRAY_SIZE(lp87565_buck_uA)) ? + lp87565_buck_uA[val] : -EINVAL; +} + +/* Operations permitted on BUCK0, BUCK1 */ +static struct regulator_ops lp87565_buck_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = lp87565_buck_set_ramp_delay, + .set_current_limit = lp87565_buck_set_current_limit, + .get_current_limit = lp87565_buck_get_current_limit, +}; + +static const struct lp87565_regulator regulators[] = { + LP87565_REGULATOR("BUCK0", LP87565_BUCK_0, "buck0", lp87565_buck_ops, + 256, LP87565_REG_BUCK0_VOUT, LP87565_BUCK_VSET, + LP87565_REG_BUCK0_CTRL_1, + LP87565_BUCK_CTRL_1_EN, 3230, + buck0_1_2_3_ranges, LP87565_REG_BUCK0_CTRL_2), + LP87565_REGULATOR("BUCK1", LP87565_BUCK_1, "buck1", lp87565_buck_ops, + 256, LP87565_REG_BUCK1_VOUT, LP87565_BUCK_VSET, + LP87565_REG_BUCK1_CTRL_1, + LP87565_BUCK_CTRL_1_EN, 3230, + buck0_1_2_3_ranges, LP87565_REG_BUCK1_CTRL_2), + LP87565_REGULATOR("BUCK2", LP87565_BUCK_2, "buck2", lp87565_buck_ops, + 256, LP87565_REG_BUCK2_VOUT, LP87565_BUCK_VSET, + LP87565_REG_BUCK2_CTRL_1, + LP87565_BUCK_CTRL_1_EN, 3230, + buck0_1_2_3_ranges, LP87565_REG_BUCK2_CTRL_2), + LP87565_REGULATOR("BUCK3", LP87565_BUCK_3, "buck3", lp87565_buck_ops, + 256, LP87565_REG_BUCK3_VOUT, LP87565_BUCK_VSET, + LP87565_REG_BUCK3_CTRL_1, + LP87565_BUCK_CTRL_1_EN, 3230, + buck0_1_2_3_ranges, LP87565_REG_BUCK3_CTRL_2), + LP87565_REGULATOR("BUCK10", LP87565_BUCK_10, "buck10", lp87565_buck_ops, + 256, LP87565_REG_BUCK0_VOUT, LP87565_BUCK_VSET, + LP87565_REG_BUCK0_CTRL_1, + LP87565_BUCK_CTRL_1_EN | + LP87565_BUCK_CTRL_1_FPWM_MP_0_2, 3230, + buck0_1_2_3_ranges, LP87565_REG_BUCK0_CTRL_2), + LP87565_REGULATOR("BUCK23", LP87565_BUCK_23, "buck23", lp87565_buck_ops, + 256, LP87565_REG_BUCK2_VOUT, LP87565_BUCK_VSET, + LP87565_REG_BUCK2_CTRL_1, + LP87565_BUCK_CTRL_1_EN, 3230, + buck0_1_2_3_ranges, LP87565_REG_BUCK2_CTRL_2), +}; + +static int lp87565_regulator_probe(struct platform_device *pdev) +{ + struct lp87565 *lp87565 = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = { }; + struct regulator_dev *rdev; + int i, min_idx = LP87565_BUCK_0, max_idx = LP87565_BUCK_3; + + platform_set_drvdata(pdev, lp87565); + + config.dev = &pdev->dev; + config.dev->of_node = lp87565->dev->of_node; + config.driver_data = lp87565; + config.regmap = lp87565->regmap; + + if (lp87565->dev_type == LP87565_DEVICE_TYPE_LP87565_Q1) { + min_idx = LP87565_BUCK_10; + max_idx = LP87565_BUCK_23; + } + + for (i = min_idx; i <= max_idx; i++) { + rdev = devm_regulator_register(&pdev->dev, ®ulators[i].desc, + &config); + if (IS_ERR(rdev)) { + dev_err(lp87565->dev, "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct platform_device_id lp87565_regulator_id_table[] = { + { "lp87565-regulator", }, + { "lp87565-q1-regulator", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, lp87565_regulator_id_table); + +static struct platform_driver lp87565_regulator_driver = { + .driver = { + .name = "lp87565-pmic", + }, + .probe = lp87565_regulator_probe, + .id_table = lp87565_regulator_id_table, +}; +module_platform_driver(lp87565_regulator_driver); + +MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>"); +MODULE_DESCRIPTION("LP87565 voltage regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/lp8788-buck.c b/drivers/regulator/lp8788-buck.c new file mode 100644 index 000000000..ec46290b6 --- /dev/null +++ b/drivers/regulator/lp8788-buck.c @@ -0,0 +1,554 @@ +/* + * TI LP8788 MFD - buck regulator driver + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * 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/module.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/lp8788.h> +#include <linux/gpio.h> + +/* register address */ +#define LP8788_EN_BUCK 0x0C +#define LP8788_BUCK_DVS_SEL 0x1D +#define LP8788_BUCK1_VOUT0 0x1E +#define LP8788_BUCK1_VOUT1 0x1F +#define LP8788_BUCK1_VOUT2 0x20 +#define LP8788_BUCK1_VOUT3 0x21 +#define LP8788_BUCK2_VOUT0 0x22 +#define LP8788_BUCK2_VOUT1 0x23 +#define LP8788_BUCK2_VOUT2 0x24 +#define LP8788_BUCK2_VOUT3 0x25 +#define LP8788_BUCK3_VOUT 0x26 +#define LP8788_BUCK4_VOUT 0x27 +#define LP8788_BUCK1_TIMESTEP 0x28 +#define LP8788_BUCK_PWM 0x2D + +/* mask/shift bits */ +#define LP8788_EN_BUCK1_M BIT(0) /* Addr 0Ch */ +#define LP8788_EN_BUCK2_M BIT(1) +#define LP8788_EN_BUCK3_M BIT(2) +#define LP8788_EN_BUCK4_M BIT(3) +#define LP8788_BUCK1_DVS_SEL_M 0x04 /* Addr 1Dh */ +#define LP8788_BUCK1_DVS_M 0x03 +#define LP8788_BUCK1_DVS_S 0 +#define LP8788_BUCK2_DVS_SEL_M 0x40 +#define LP8788_BUCK2_DVS_M 0x30 +#define LP8788_BUCK2_DVS_S 4 +#define LP8788_BUCK1_DVS_I2C BIT(2) +#define LP8788_BUCK2_DVS_I2C BIT(6) +#define LP8788_BUCK1_DVS_PIN (0 << 2) +#define LP8788_BUCK2_DVS_PIN (0 << 6) +#define LP8788_VOUT_M 0x1F /* Addr 1Eh ~ 27h */ +#define LP8788_STARTUP_TIME_M 0xF8 /* Addr 28h ~ 2Bh */ +#define LP8788_STARTUP_TIME_S 3 +#define LP8788_FPWM_BUCK1_M BIT(0) /* Addr 2Dh */ +#define LP8788_FPWM_BUCK1_S 0 +#define LP8788_FPWM_BUCK2_M BIT(1) +#define LP8788_FPWM_BUCK2_S 1 +#define LP8788_FPWM_BUCK3_M BIT(2) +#define LP8788_FPWM_BUCK3_S 2 +#define LP8788_FPWM_BUCK4_M BIT(3) +#define LP8788_FPWM_BUCK4_S 3 + +#define INVALID_ADDR 0xFF +#define LP8788_FORCE_PWM 1 +#define LP8788_AUTO_PWM 0 +#define PIN_LOW 0 +#define PIN_HIGH 1 +#define ENABLE_TIME_USEC 32 + +#define BUCK_FPWM_MASK(x) (1 << (x)) +#define BUCK_FPWM_SHIFT(x) (x) + +enum lp8788_dvs_state { + DVS_LOW = GPIOF_OUT_INIT_LOW, + DVS_HIGH = GPIOF_OUT_INIT_HIGH, +}; + +enum lp8788_dvs_mode { + REGISTER, + EXTPIN, +}; + +enum lp8788_buck_id { + BUCK1, + BUCK2, + BUCK3, + BUCK4, +}; + +struct lp8788_buck { + struct lp8788 *lp; + struct regulator_dev *regulator; + void *dvs; +}; + +/* BUCK 1 ~ 4 voltage table */ +static const int lp8788_buck_vtbl[] = { + 500000, 800000, 850000, 900000, 950000, 1000000, 1050000, 1100000, + 1150000, 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, + 1550000, 1600000, 1650000, 1700000, 1750000, 1800000, 1850000, 1900000, + 1950000, 2000000, +}; + +static void lp8788_buck1_set_dvs(struct lp8788_buck *buck) +{ + struct lp8788_buck1_dvs *dvs = (struct lp8788_buck1_dvs *)buck->dvs; + enum lp8788_dvs_state pinstate; + + if (!dvs) + return; + + pinstate = dvs->vsel == DVS_SEL_V0 ? DVS_LOW : DVS_HIGH; + if (gpio_is_valid(dvs->gpio)) + gpio_set_value(dvs->gpio, pinstate); +} + +static void lp8788_buck2_set_dvs(struct lp8788_buck *buck) +{ + struct lp8788_buck2_dvs *dvs = (struct lp8788_buck2_dvs *)buck->dvs; + enum lp8788_dvs_state pin1, pin2; + + if (!dvs) + return; + + switch (dvs->vsel) { + case DVS_SEL_V0: + pin1 = DVS_LOW; + pin2 = DVS_LOW; + break; + case DVS_SEL_V1: + pin1 = DVS_HIGH; + pin2 = DVS_LOW; + break; + case DVS_SEL_V2: + pin1 = DVS_LOW; + pin2 = DVS_HIGH; + break; + case DVS_SEL_V3: + pin1 = DVS_HIGH; + pin2 = DVS_HIGH; + break; + default: + return; + } + + if (gpio_is_valid(dvs->gpio[0])) + gpio_set_value(dvs->gpio[0], pin1); + + if (gpio_is_valid(dvs->gpio[1])) + gpio_set_value(dvs->gpio[1], pin2); +} + +static void lp8788_set_dvs(struct lp8788_buck *buck, enum lp8788_buck_id id) +{ + switch (id) { + case BUCK1: + lp8788_buck1_set_dvs(buck); + break; + case BUCK2: + lp8788_buck2_set_dvs(buck); + break; + default: + break; + } +} + +static enum lp8788_dvs_mode +lp8788_get_buck_dvs_ctrl_mode(struct lp8788_buck *buck, enum lp8788_buck_id id) +{ + u8 val, mask; + + switch (id) { + case BUCK1: + mask = LP8788_BUCK1_DVS_SEL_M; + break; + case BUCK2: + mask = LP8788_BUCK2_DVS_SEL_M; + break; + default: + return REGISTER; + } + + lp8788_read_byte(buck->lp, LP8788_BUCK_DVS_SEL, &val); + + return val & mask ? REGISTER : EXTPIN; +} + +static bool lp8788_is_valid_buck_addr(u8 addr) +{ + switch (addr) { + case LP8788_BUCK1_VOUT0: + case LP8788_BUCK1_VOUT1: + case LP8788_BUCK1_VOUT2: + case LP8788_BUCK1_VOUT3: + case LP8788_BUCK2_VOUT0: + case LP8788_BUCK2_VOUT1: + case LP8788_BUCK2_VOUT2: + case LP8788_BUCK2_VOUT3: + return true; + default: + return false; + } +} + +static u8 lp8788_select_buck_vout_addr(struct lp8788_buck *buck, + enum lp8788_buck_id id) +{ + enum lp8788_dvs_mode mode = lp8788_get_buck_dvs_ctrl_mode(buck, id); + struct lp8788_buck1_dvs *b1_dvs; + struct lp8788_buck2_dvs *b2_dvs; + u8 val, idx, addr; + int pin1, pin2; + + switch (id) { + case BUCK1: + if (mode == EXTPIN) { + b1_dvs = (struct lp8788_buck1_dvs *)buck->dvs; + if (!b1_dvs) + goto err; + + idx = gpio_get_value(b1_dvs->gpio) ? 1 : 0; + } else { + lp8788_read_byte(buck->lp, LP8788_BUCK_DVS_SEL, &val); + idx = (val & LP8788_BUCK1_DVS_M) >> LP8788_BUCK1_DVS_S; + } + addr = LP8788_BUCK1_VOUT0 + idx; + break; + case BUCK2: + if (mode == EXTPIN) { + b2_dvs = (struct lp8788_buck2_dvs *)buck->dvs; + if (!b2_dvs) + goto err; + + pin1 = gpio_get_value(b2_dvs->gpio[0]); + pin2 = gpio_get_value(b2_dvs->gpio[1]); + + if (pin1 == PIN_LOW && pin2 == PIN_LOW) + idx = 0; + else if (pin1 == PIN_LOW && pin2 == PIN_HIGH) + idx = 2; + else if (pin1 == PIN_HIGH && pin2 == PIN_LOW) + idx = 1; + else + idx = 3; + } else { + lp8788_read_byte(buck->lp, LP8788_BUCK_DVS_SEL, &val); + idx = (val & LP8788_BUCK2_DVS_M) >> LP8788_BUCK2_DVS_S; + } + addr = LP8788_BUCK2_VOUT0 + idx; + break; + default: + goto err; + } + + return addr; +err: + return INVALID_ADDR; +} + +static int lp8788_buck12_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct lp8788_buck *buck = rdev_get_drvdata(rdev); + enum lp8788_buck_id id = rdev_get_id(rdev); + u8 addr; + + if (buck->dvs) + lp8788_set_dvs(buck, id); + + addr = lp8788_select_buck_vout_addr(buck, id); + if (!lp8788_is_valid_buck_addr(addr)) + return -EINVAL; + + return lp8788_update_bits(buck->lp, addr, LP8788_VOUT_M, selector); +} + +static int lp8788_buck12_get_voltage_sel(struct regulator_dev *rdev) +{ + struct lp8788_buck *buck = rdev_get_drvdata(rdev); + enum lp8788_buck_id id = rdev_get_id(rdev); + int ret; + u8 val, addr; + + addr = lp8788_select_buck_vout_addr(buck, id); + if (!lp8788_is_valid_buck_addr(addr)) + return -EINVAL; + + ret = lp8788_read_byte(buck->lp, addr, &val); + if (ret) + return ret; + + return val & LP8788_VOUT_M; +} + +static int lp8788_buck_enable_time(struct regulator_dev *rdev) +{ + struct lp8788_buck *buck = rdev_get_drvdata(rdev); + enum lp8788_buck_id id = rdev_get_id(rdev); + u8 val, addr = LP8788_BUCK1_TIMESTEP + id; + + if (lp8788_read_byte(buck->lp, addr, &val)) + return -EINVAL; + + val = (val & LP8788_STARTUP_TIME_M) >> LP8788_STARTUP_TIME_S; + + return ENABLE_TIME_USEC * val; +} + +static int lp8788_buck_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct lp8788_buck *buck = rdev_get_drvdata(rdev); + enum lp8788_buck_id id = rdev_get_id(rdev); + u8 mask, val; + + mask = BUCK_FPWM_MASK(id); + switch (mode) { + case REGULATOR_MODE_FAST: + val = LP8788_FORCE_PWM << BUCK_FPWM_SHIFT(id); + break; + case REGULATOR_MODE_NORMAL: + val = LP8788_AUTO_PWM << BUCK_FPWM_SHIFT(id); + break; + default: + return -EINVAL; + } + + return lp8788_update_bits(buck->lp, LP8788_BUCK_PWM, mask, val); +} + +static unsigned int lp8788_buck_get_mode(struct regulator_dev *rdev) +{ + struct lp8788_buck *buck = rdev_get_drvdata(rdev); + enum lp8788_buck_id id = rdev_get_id(rdev); + u8 val; + int ret; + + ret = lp8788_read_byte(buck->lp, LP8788_BUCK_PWM, &val); + if (ret) + return ret; + + return val & BUCK_FPWM_MASK(id) ? + REGULATOR_MODE_FAST : REGULATOR_MODE_NORMAL; +} + +static const struct regulator_ops lp8788_buck12_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_voltage_sel = lp8788_buck12_set_voltage_sel, + .get_voltage_sel = lp8788_buck12_get_voltage_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .enable_time = lp8788_buck_enable_time, + .set_mode = lp8788_buck_set_mode, + .get_mode = lp8788_buck_get_mode, +}; + +static const struct regulator_ops lp8788_buck34_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .enable_time = lp8788_buck_enable_time, + .set_mode = lp8788_buck_set_mode, + .get_mode = lp8788_buck_get_mode, +}; + +static struct regulator_desc lp8788_buck_desc[] = { + { + .name = "buck1", + .id = BUCK1, + .ops = &lp8788_buck12_ops, + .n_voltages = ARRAY_SIZE(lp8788_buck_vtbl), + .volt_table = lp8788_buck_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8788_EN_BUCK, + .enable_mask = LP8788_EN_BUCK1_M, + }, + { + .name = "buck2", + .id = BUCK2, + .ops = &lp8788_buck12_ops, + .n_voltages = ARRAY_SIZE(lp8788_buck_vtbl), + .volt_table = lp8788_buck_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8788_EN_BUCK, + .enable_mask = LP8788_EN_BUCK2_M, + }, + { + .name = "buck3", + .id = BUCK3, + .ops = &lp8788_buck34_ops, + .n_voltages = ARRAY_SIZE(lp8788_buck_vtbl), + .volt_table = lp8788_buck_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_BUCK3_VOUT, + .vsel_mask = LP8788_VOUT_M, + .enable_reg = LP8788_EN_BUCK, + .enable_mask = LP8788_EN_BUCK3_M, + }, + { + .name = "buck4", + .id = BUCK4, + .ops = &lp8788_buck34_ops, + .n_voltages = ARRAY_SIZE(lp8788_buck_vtbl), + .volt_table = lp8788_buck_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_BUCK4_VOUT, + .vsel_mask = LP8788_VOUT_M, + .enable_reg = LP8788_EN_BUCK, + .enable_mask = LP8788_EN_BUCK4_M, + }, +}; + +static int lp8788_dvs_gpio_request(struct platform_device *pdev, + struct lp8788_buck *buck, + enum lp8788_buck_id id) +{ + struct lp8788_platform_data *pdata = buck->lp->pdata; + char *b1_name = "LP8788_B1_DVS"; + char *b2_name[] = { "LP8788_B2_DVS1", "LP8788_B2_DVS2" }; + int i, gpio, ret; + + switch (id) { + case BUCK1: + gpio = pdata->buck1_dvs->gpio; + ret = devm_gpio_request_one(&pdev->dev, gpio, DVS_LOW, + b1_name); + if (ret) + return ret; + + buck->dvs = pdata->buck1_dvs; + break; + case BUCK2: + for (i = 0; i < LP8788_NUM_BUCK2_DVS; i++) { + gpio = pdata->buck2_dvs->gpio[i]; + ret = devm_gpio_request_one(&pdev->dev, gpio, + DVS_LOW, b2_name[i]); + if (ret) + return ret; + } + buck->dvs = pdata->buck2_dvs; + break; + default: + break; + } + + return 0; +} + +static int lp8788_init_dvs(struct platform_device *pdev, + struct lp8788_buck *buck, enum lp8788_buck_id id) +{ + struct lp8788_platform_data *pdata = buck->lp->pdata; + u8 mask[] = { LP8788_BUCK1_DVS_SEL_M, LP8788_BUCK2_DVS_SEL_M }; + u8 val[] = { LP8788_BUCK1_DVS_PIN, LP8788_BUCK2_DVS_PIN }; + u8 default_dvs_mode[] = { LP8788_BUCK1_DVS_I2C, LP8788_BUCK2_DVS_I2C }; + + /* no dvs for buck3, 4 */ + if (id > BUCK2) + return 0; + + /* no dvs platform data, then dvs will be selected by I2C registers */ + if (!pdata) + goto set_default_dvs_mode; + + if ((id == BUCK1 && !pdata->buck1_dvs) || + (id == BUCK2 && !pdata->buck2_dvs)) + goto set_default_dvs_mode; + + if (lp8788_dvs_gpio_request(pdev, buck, id)) + goto set_default_dvs_mode; + + return lp8788_update_bits(buck->lp, LP8788_BUCK_DVS_SEL, mask[id], + val[id]); + +set_default_dvs_mode: + return lp8788_update_bits(buck->lp, LP8788_BUCK_DVS_SEL, mask[id], + default_dvs_mode[id]); +} + +static int lp8788_buck_probe(struct platform_device *pdev) +{ + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); + int id = pdev->id; + struct lp8788_buck *buck; + struct regulator_config cfg = { }; + struct regulator_dev *rdev; + int ret; + + if (id >= LP8788_NUM_BUCKS) + return -EINVAL; + + buck = devm_kzalloc(&pdev->dev, sizeof(struct lp8788_buck), GFP_KERNEL); + if (!buck) + return -ENOMEM; + + buck->lp = lp; + + ret = lp8788_init_dvs(pdev, buck, id); + if (ret) + return ret; + + cfg.dev = pdev->dev.parent; + cfg.init_data = lp->pdata ? lp->pdata->buck_data[id] : NULL; + cfg.driver_data = buck; + cfg.regmap = lp->regmap; + + rdev = devm_regulator_register(&pdev->dev, &lp8788_buck_desc[id], &cfg); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(&pdev->dev, "BUCK%d regulator register err = %d\n", + id + 1, ret); + return ret; + } + + buck->regulator = rdev; + platform_set_drvdata(pdev, buck); + + return 0; +} + +static struct platform_driver lp8788_buck_driver = { + .probe = lp8788_buck_probe, + .driver = { + .name = LP8788_DEV_BUCK, + }, +}; + +static int __init lp8788_buck_init(void) +{ + return platform_driver_register(&lp8788_buck_driver); +} +subsys_initcall(lp8788_buck_init); + +static void __exit lp8788_buck_exit(void) +{ + platform_driver_unregister(&lp8788_buck_driver); +} +module_exit(lp8788_buck_exit); + +MODULE_DESCRIPTION("TI LP8788 BUCK Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lp8788-buck"); diff --git a/drivers/regulator/lp8788-ldo.c b/drivers/regulator/lp8788-ldo.c new file mode 100644 index 000000000..f2347474a --- /dev/null +++ b/drivers/regulator/lp8788-ldo.c @@ -0,0 +1,637 @@ +/* + * TI LP8788 MFD - ldo regulator driver + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * 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/module.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/gpio/consumer.h> +#include <linux/mfd/lp8788.h> + +/* register address */ +#define LP8788_EN_LDO_A 0x0D /* DLDO 1 ~ 8 */ +#define LP8788_EN_LDO_B 0x0E /* DLDO 9 ~ 12, ALDO 1 ~ 4 */ +#define LP8788_EN_LDO_C 0x0F /* ALDO 5 ~ 10 */ +#define LP8788_EN_SEL 0x10 +#define LP8788_DLDO1_VOUT 0x2E +#define LP8788_DLDO2_VOUT 0x2F +#define LP8788_DLDO3_VOUT 0x30 +#define LP8788_DLDO4_VOUT 0x31 +#define LP8788_DLDO5_VOUT 0x32 +#define LP8788_DLDO6_VOUT 0x33 +#define LP8788_DLDO7_VOUT 0x34 +#define LP8788_DLDO8_VOUT 0x35 +#define LP8788_DLDO9_VOUT 0x36 +#define LP8788_DLDO10_VOUT 0x37 +#define LP8788_DLDO11_VOUT 0x38 +#define LP8788_DLDO12_VOUT 0x39 +#define LP8788_ALDO1_VOUT 0x3A +#define LP8788_ALDO2_VOUT 0x3B +#define LP8788_ALDO3_VOUT 0x3C +#define LP8788_ALDO4_VOUT 0x3D +#define LP8788_ALDO5_VOUT 0x3E +#define LP8788_ALDO6_VOUT 0x3F +#define LP8788_ALDO7_VOUT 0x40 +#define LP8788_ALDO8_VOUT 0x41 +#define LP8788_ALDO9_VOUT 0x42 +#define LP8788_ALDO10_VOUT 0x43 +#define LP8788_DLDO1_TIMESTEP 0x44 + +/* mask/shift bits */ +#define LP8788_EN_DLDO1_M BIT(0) /* Addr 0Dh ~ 0Fh */ +#define LP8788_EN_DLDO2_M BIT(1) +#define LP8788_EN_DLDO3_M BIT(2) +#define LP8788_EN_DLDO4_M BIT(3) +#define LP8788_EN_DLDO5_M BIT(4) +#define LP8788_EN_DLDO6_M BIT(5) +#define LP8788_EN_DLDO7_M BIT(6) +#define LP8788_EN_DLDO8_M BIT(7) +#define LP8788_EN_DLDO9_M BIT(0) +#define LP8788_EN_DLDO10_M BIT(1) +#define LP8788_EN_DLDO11_M BIT(2) +#define LP8788_EN_DLDO12_M BIT(3) +#define LP8788_EN_ALDO1_M BIT(4) +#define LP8788_EN_ALDO2_M BIT(5) +#define LP8788_EN_ALDO3_M BIT(6) +#define LP8788_EN_ALDO4_M BIT(7) +#define LP8788_EN_ALDO5_M BIT(0) +#define LP8788_EN_ALDO6_M BIT(1) +#define LP8788_EN_ALDO7_M BIT(2) +#define LP8788_EN_ALDO8_M BIT(3) +#define LP8788_EN_ALDO9_M BIT(4) +#define LP8788_EN_ALDO10_M BIT(5) +#define LP8788_EN_SEL_DLDO911_M BIT(0) /* Addr 10h */ +#define LP8788_EN_SEL_DLDO7_M BIT(1) +#define LP8788_EN_SEL_ALDO7_M BIT(2) +#define LP8788_EN_SEL_ALDO5_M BIT(3) +#define LP8788_EN_SEL_ALDO234_M BIT(4) +#define LP8788_EN_SEL_ALDO1_M BIT(5) +#define LP8788_VOUT_5BIT_M 0x1F /* Addr 2Eh ~ 43h */ +#define LP8788_VOUT_4BIT_M 0x0F +#define LP8788_VOUT_3BIT_M 0x07 +#define LP8788_VOUT_1BIT_M 0x01 +#define LP8788_STARTUP_TIME_M 0xF8 /* Addr 44h ~ 59h */ +#define LP8788_STARTUP_TIME_S 3 + +#define ENABLE_TIME_USEC 32 + +enum lp8788_ldo_id { + DLDO1, + DLDO2, + DLDO3, + DLDO4, + DLDO5, + DLDO6, + DLDO7, + DLDO8, + DLDO9, + DLDO10, + DLDO11, + DLDO12, + ALDO1, + ALDO2, + ALDO3, + ALDO4, + ALDO5, + ALDO6, + ALDO7, + ALDO8, + ALDO9, + ALDO10, +}; + +struct lp8788_ldo { + struct lp8788 *lp; + struct regulator_desc *desc; + struct regulator_dev *regulator; + struct gpio_desc *ena_gpiod; +}; + +/* DLDO 1, 2, 3, 9 voltage table */ +static const int lp8788_dldo1239_vtbl[] = { + 1800000, 1900000, 2000000, 2100000, 2200000, 2300000, 2400000, 2500000, + 2600000, 2700000, 2800000, 2900000, 3000000, 2850000, 2850000, 2850000, + 2850000, 2850000, 2850000, 2850000, 2850000, 2850000, 2850000, 2850000, + 2850000, 2850000, 2850000, 2850000, 2850000, 2850000, 2850000, 2850000, +}; + +/* DLDO 4 voltage table */ +static const int lp8788_dldo4_vtbl[] = { 1800000, 3000000 }; + +/* DLDO 5, 7, 8 and ALDO 6 voltage table */ +static const int lp8788_dldo578_aldo6_vtbl[] = { + 1800000, 1900000, 2000000, 2100000, 2200000, 2300000, 2400000, 2500000, + 2600000, 2700000, 2800000, 2900000, 3000000, 3000000, 3000000, 3000000, +}; + +/* DLDO 6 voltage table */ +static const int lp8788_dldo6_vtbl[] = { + 3000000, 3100000, 3200000, 3300000, 3400000, 3500000, 3600000, 3600000, +}; + +/* DLDO 10, 11 voltage table */ +static const int lp8788_dldo1011_vtbl[] = { + 1100000, 1150000, 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, + 1500000, 1500000, 1500000, 1500000, 1500000, 1500000, 1500000, 1500000, +}; + +/* ALDO 1 voltage table */ +static const int lp8788_aldo1_vtbl[] = { 1800000, 2850000 }; + +/* ALDO 7 voltage table */ +static const int lp8788_aldo7_vtbl[] = { + 1200000, 1300000, 1400000, 1500000, 1600000, 1700000, 1800000, 1800000, +}; + +static int lp8788_ldo_enable_time(struct regulator_dev *rdev) +{ + struct lp8788_ldo *ldo = rdev_get_drvdata(rdev); + enum lp8788_ldo_id id = rdev_get_id(rdev); + u8 val, addr = LP8788_DLDO1_TIMESTEP + id; + + if (lp8788_read_byte(ldo->lp, addr, &val)) + return -EINVAL; + + val = (val & LP8788_STARTUP_TIME_M) >> LP8788_STARTUP_TIME_S; + + return ENABLE_TIME_USEC * val; +} + +static const struct regulator_ops lp8788_ldo_voltage_table_ops = { + .list_voltage = regulator_list_voltage_table, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .enable_time = lp8788_ldo_enable_time, +}; + +static const struct regulator_ops lp8788_ldo_voltage_fixed_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .enable_time = lp8788_ldo_enable_time, +}; + +static struct regulator_desc lp8788_dldo_desc[] = { + { + .name = "dldo1", + .id = DLDO1, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo1239_vtbl), + .volt_table = lp8788_dldo1239_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO1_VOUT, + .vsel_mask = LP8788_VOUT_5BIT_M, + .enable_reg = LP8788_EN_LDO_A, + .enable_mask = LP8788_EN_DLDO1_M, + }, + { + .name = "dldo2", + .id = DLDO2, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo1239_vtbl), + .volt_table = lp8788_dldo1239_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO2_VOUT, + .vsel_mask = LP8788_VOUT_5BIT_M, + .enable_reg = LP8788_EN_LDO_A, + .enable_mask = LP8788_EN_DLDO2_M, + }, + { + .name = "dldo3", + .id = DLDO3, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo1239_vtbl), + .volt_table = lp8788_dldo1239_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO3_VOUT, + .vsel_mask = LP8788_VOUT_5BIT_M, + .enable_reg = LP8788_EN_LDO_A, + .enable_mask = LP8788_EN_DLDO3_M, + }, + { + .name = "dldo4", + .id = DLDO4, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo4_vtbl), + .volt_table = lp8788_dldo4_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO4_VOUT, + .vsel_mask = LP8788_VOUT_1BIT_M, + .enable_reg = LP8788_EN_LDO_A, + .enable_mask = LP8788_EN_DLDO4_M, + }, + { + .name = "dldo5", + .id = DLDO5, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo578_aldo6_vtbl), + .volt_table = lp8788_dldo578_aldo6_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO5_VOUT, + .vsel_mask = LP8788_VOUT_4BIT_M, + .enable_reg = LP8788_EN_LDO_A, + .enable_mask = LP8788_EN_DLDO5_M, + }, + { + .name = "dldo6", + .id = DLDO6, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo6_vtbl), + .volt_table = lp8788_dldo6_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO6_VOUT, + .vsel_mask = LP8788_VOUT_3BIT_M, + .enable_reg = LP8788_EN_LDO_A, + .enable_mask = LP8788_EN_DLDO6_M, + }, + { + .name = "dldo7", + .id = DLDO7, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo578_aldo6_vtbl), + .volt_table = lp8788_dldo578_aldo6_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO7_VOUT, + .vsel_mask = LP8788_VOUT_4BIT_M, + .enable_reg = LP8788_EN_LDO_A, + .enable_mask = LP8788_EN_DLDO7_M, + }, + { + .name = "dldo8", + .id = DLDO8, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo578_aldo6_vtbl), + .volt_table = lp8788_dldo578_aldo6_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO8_VOUT, + .vsel_mask = LP8788_VOUT_4BIT_M, + .enable_reg = LP8788_EN_LDO_A, + .enable_mask = LP8788_EN_DLDO8_M, + }, + { + .name = "dldo9", + .id = DLDO9, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo1239_vtbl), + .volt_table = lp8788_dldo1239_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO9_VOUT, + .vsel_mask = LP8788_VOUT_5BIT_M, + .enable_reg = LP8788_EN_LDO_B, + .enable_mask = LP8788_EN_DLDO9_M, + }, + { + .name = "dldo10", + .id = DLDO10, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo1011_vtbl), + .volt_table = lp8788_dldo1011_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO10_VOUT, + .vsel_mask = LP8788_VOUT_4BIT_M, + .enable_reg = LP8788_EN_LDO_B, + .enable_mask = LP8788_EN_DLDO10_M, + }, + { + .name = "dldo11", + .id = DLDO11, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo1011_vtbl), + .volt_table = lp8788_dldo1011_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_DLDO11_VOUT, + .vsel_mask = LP8788_VOUT_4BIT_M, + .enable_reg = LP8788_EN_LDO_B, + .enable_mask = LP8788_EN_DLDO11_M, + }, + { + .name = "dldo12", + .id = DLDO12, + .ops = &lp8788_ldo_voltage_fixed_ops, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8788_EN_LDO_B, + .enable_mask = LP8788_EN_DLDO12_M, + .min_uV = 2500000, + }, +}; + +static struct regulator_desc lp8788_aldo_desc[] = { + { + .name = "aldo1", + .id = ALDO1, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_aldo1_vtbl), + .volt_table = lp8788_aldo1_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_ALDO1_VOUT, + .vsel_mask = LP8788_VOUT_1BIT_M, + .enable_reg = LP8788_EN_LDO_B, + .enable_mask = LP8788_EN_ALDO1_M, + }, + { + .name = "aldo2", + .id = ALDO2, + .ops = &lp8788_ldo_voltage_fixed_ops, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8788_EN_LDO_B, + .enable_mask = LP8788_EN_ALDO2_M, + .min_uV = 2850000, + }, + { + .name = "aldo3", + .id = ALDO3, + .ops = &lp8788_ldo_voltage_fixed_ops, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8788_EN_LDO_B, + .enable_mask = LP8788_EN_ALDO3_M, + .min_uV = 2850000, + }, + { + .name = "aldo4", + .id = ALDO4, + .ops = &lp8788_ldo_voltage_fixed_ops, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8788_EN_LDO_B, + .enable_mask = LP8788_EN_ALDO4_M, + .min_uV = 2850000, + }, + { + .name = "aldo5", + .id = ALDO5, + .ops = &lp8788_ldo_voltage_fixed_ops, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8788_EN_LDO_C, + .enable_mask = LP8788_EN_ALDO5_M, + .min_uV = 2850000, + }, + { + .name = "aldo6", + .id = ALDO6, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_dldo578_aldo6_vtbl), + .volt_table = lp8788_dldo578_aldo6_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_ALDO6_VOUT, + .vsel_mask = LP8788_VOUT_4BIT_M, + .enable_reg = LP8788_EN_LDO_C, + .enable_mask = LP8788_EN_ALDO6_M, + }, + { + .name = "aldo7", + .id = ALDO7, + .ops = &lp8788_ldo_voltage_table_ops, + .n_voltages = ARRAY_SIZE(lp8788_aldo7_vtbl), + .volt_table = lp8788_aldo7_vtbl, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .vsel_reg = LP8788_ALDO7_VOUT, + .vsel_mask = LP8788_VOUT_3BIT_M, + .enable_reg = LP8788_EN_LDO_C, + .enable_mask = LP8788_EN_ALDO7_M, + }, + { + .name = "aldo8", + .id = ALDO8, + .ops = &lp8788_ldo_voltage_fixed_ops, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8788_EN_LDO_C, + .enable_mask = LP8788_EN_ALDO8_M, + .min_uV = 2500000, + }, + { + .name = "aldo9", + .id = ALDO9, + .ops = &lp8788_ldo_voltage_fixed_ops, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8788_EN_LDO_C, + .enable_mask = LP8788_EN_ALDO9_M, + .min_uV = 2500000, + }, + { + .name = "aldo10", + .id = ALDO10, + .ops = &lp8788_ldo_voltage_fixed_ops, + .n_voltages = 1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = LP8788_EN_LDO_C, + .enable_mask = LP8788_EN_ALDO10_M, + .min_uV = 1100000, + }, +}; + +static int lp8788_config_ldo_enable_mode(struct platform_device *pdev, + struct lp8788_ldo *ldo, + enum lp8788_ldo_id id) +{ + struct lp8788 *lp = ldo->lp; + enum lp8788_ext_ldo_en_id enable_id; + u8 en_mask[] = { + [EN_ALDO1] = LP8788_EN_SEL_ALDO1_M, + [EN_ALDO234] = LP8788_EN_SEL_ALDO234_M, + [EN_ALDO5] = LP8788_EN_SEL_ALDO5_M, + [EN_ALDO7] = LP8788_EN_SEL_ALDO7_M, + [EN_DLDO7] = LP8788_EN_SEL_DLDO7_M, + [EN_DLDO911] = LP8788_EN_SEL_DLDO911_M, + }; + + switch (id) { + case DLDO7: + enable_id = EN_DLDO7; + break; + case DLDO9: + case DLDO11: + enable_id = EN_DLDO911; + break; + case ALDO1: + enable_id = EN_ALDO1; + break; + case ALDO2 ... ALDO4: + enable_id = EN_ALDO234; + break; + case ALDO5: + enable_id = EN_ALDO5; + break; + case ALDO7: + enable_id = EN_ALDO7; + break; + default: + return 0; + } + + /* FIXME: check default mode for GPIO here: high or low? */ + ldo->ena_gpiod = devm_gpiod_get_index_optional(&pdev->dev, + "enable", + enable_id, + GPIOD_OUT_HIGH); + if (IS_ERR(ldo->ena_gpiod)) + return PTR_ERR(ldo->ena_gpiod); + + /* if no GPIO for ldo pin, then set default enable mode */ + if (!ldo->ena_gpiod) + goto set_default_ldo_enable_mode; + + return 0; + +set_default_ldo_enable_mode: + return lp8788_update_bits(lp, LP8788_EN_SEL, en_mask[enable_id], 0); +} + +static int lp8788_dldo_probe(struct platform_device *pdev) +{ + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); + int id = pdev->id; + struct lp8788_ldo *ldo; + struct regulator_config cfg = { }; + struct regulator_dev *rdev; + int ret; + + ldo = devm_kzalloc(&pdev->dev, sizeof(struct lp8788_ldo), GFP_KERNEL); + if (!ldo) + return -ENOMEM; + + ldo->lp = lp; + ret = lp8788_config_ldo_enable_mode(pdev, ldo, id); + if (ret) + return ret; + + if (ldo->ena_gpiod) + cfg.ena_gpiod = ldo->ena_gpiod; + + cfg.dev = pdev->dev.parent; + cfg.init_data = lp->pdata ? lp->pdata->dldo_data[id] : NULL; + cfg.driver_data = ldo; + cfg.regmap = lp->regmap; + + rdev = devm_regulator_register(&pdev->dev, &lp8788_dldo_desc[id], &cfg); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(&pdev->dev, "DLDO%d regulator register err = %d\n", + id + 1, ret); + return ret; + } + + ldo->regulator = rdev; + platform_set_drvdata(pdev, ldo); + + return 0; +} + +static struct platform_driver lp8788_dldo_driver = { + .probe = lp8788_dldo_probe, + .driver = { + .name = LP8788_DEV_DLDO, + }, +}; + +static int lp8788_aldo_probe(struct platform_device *pdev) +{ + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); + int id = pdev->id; + struct lp8788_ldo *ldo; + struct regulator_config cfg = { }; + struct regulator_dev *rdev; + int ret; + + ldo = devm_kzalloc(&pdev->dev, sizeof(struct lp8788_ldo), GFP_KERNEL); + if (!ldo) + return -ENOMEM; + + ldo->lp = lp; + ret = lp8788_config_ldo_enable_mode(pdev, ldo, id + ALDO1); + if (ret) + return ret; + + if (ldo->ena_gpiod) + cfg.ena_gpiod = ldo->ena_gpiod; + + cfg.dev = pdev->dev.parent; + cfg.init_data = lp->pdata ? lp->pdata->aldo_data[id] : NULL; + cfg.driver_data = ldo; + cfg.regmap = lp->regmap; + + rdev = devm_regulator_register(&pdev->dev, &lp8788_aldo_desc[id], &cfg); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(&pdev->dev, "ALDO%d regulator register err = %d\n", + id + 1, ret); + return ret; + } + + ldo->regulator = rdev; + platform_set_drvdata(pdev, ldo); + + return 0; +} + +static struct platform_driver lp8788_aldo_driver = { + .probe = lp8788_aldo_probe, + .driver = { + .name = LP8788_DEV_ALDO, + }, +}; + +static struct platform_driver * const drivers[] = { + &lp8788_dldo_driver, + &lp8788_aldo_driver, +}; + +static int __init lp8788_ldo_init(void) +{ + return platform_register_drivers(drivers, ARRAY_SIZE(drivers)); +} +subsys_initcall(lp8788_ldo_init); + +static void __exit lp8788_ldo_exit(void) +{ + platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); +} +module_exit(lp8788_ldo_exit); + +MODULE_DESCRIPTION("TI LP8788 LDO Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lp8788-dldo"); +MODULE_ALIAS("platform:lp8788-aldo"); diff --git a/drivers/regulator/ltc3589.c b/drivers/regulator/ltc3589.c new file mode 100644 index 000000000..18d5b01dd --- /dev/null +++ b/drivers/regulator/ltc3589.c @@ -0,0 +1,579 @@ +/* + * Linear Technology LTC3589,LTC3589-1 regulator support + * + * Copyright (c) 2014 Philipp Zabel <p.zabel@pengutronix.de>, Pengutronix + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +#define DRIVER_NAME "ltc3589" + +#define LTC3589_IRQSTAT 0x02 +#define LTC3589_SCR1 0x07 +#define LTC3589_OVEN 0x10 +#define LTC3589_SCR2 0x12 +#define LTC3589_PGSTAT 0x13 +#define LTC3589_VCCR 0x20 +#define LTC3589_CLIRQ 0x21 +#define LTC3589_B1DTV1 0x23 +#define LTC3589_B1DTV2 0x24 +#define LTC3589_VRRCR 0x25 +#define LTC3589_B2DTV1 0x26 +#define LTC3589_B2DTV2 0x27 +#define LTC3589_B3DTV1 0x29 +#define LTC3589_B3DTV2 0x2a +#define LTC3589_L2DTV1 0x32 +#define LTC3589_L2DTV2 0x33 + +#define LTC3589_IRQSTAT_PGOOD_TIMEOUT BIT(3) +#define LTC3589_IRQSTAT_UNDERVOLT_WARN BIT(4) +#define LTC3589_IRQSTAT_UNDERVOLT_FAULT BIT(5) +#define LTC3589_IRQSTAT_THERMAL_WARN BIT(6) +#define LTC3589_IRQSTAT_THERMAL_FAULT BIT(7) + +#define LTC3589_OVEN_SW1 BIT(0) +#define LTC3589_OVEN_SW2 BIT(1) +#define LTC3589_OVEN_SW3 BIT(2) +#define LTC3589_OVEN_BB_OUT BIT(3) +#define LTC3589_OVEN_LDO2 BIT(4) +#define LTC3589_OVEN_LDO3 BIT(5) +#define LTC3589_OVEN_LDO4 BIT(6) +#define LTC3589_OVEN_SW_CTRL BIT(7) + +#define LTC3589_VCCR_SW1_GO BIT(0) +#define LTC3589_VCCR_SW2_GO BIT(2) +#define LTC3589_VCCR_SW3_GO BIT(4) +#define LTC3589_VCCR_LDO2_GO BIT(6) + +enum ltc3589_variant { + LTC3589, + LTC3589_1, + LTC3589_2, +}; + +enum ltc3589_reg { + LTC3589_SW1, + LTC3589_SW2, + LTC3589_SW3, + LTC3589_BB_OUT, + LTC3589_LDO1, + LTC3589_LDO2, + LTC3589_LDO3, + LTC3589_LDO4, + LTC3589_NUM_REGULATORS, +}; + +struct ltc3589_regulator { + struct regulator_desc desc; + + /* External feedback voltage divider */ + unsigned int r1; + unsigned int r2; +}; + +struct ltc3589 { + struct regmap *regmap; + struct device *dev; + enum ltc3589_variant variant; + struct ltc3589_regulator regulator_descs[LTC3589_NUM_REGULATORS]; + struct regulator_dev *regulators[LTC3589_NUM_REGULATORS]; +}; + +static const int ltc3589_ldo4[] = { + 2800000, 2500000, 1800000, 3300000, +}; + +static const int ltc3589_12_ldo4[] = { + 1200000, 1800000, 2500000, 3200000, +}; + +static int ltc3589_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay) +{ + struct ltc3589 *ltc3589 = rdev_get_drvdata(rdev); + int sel, shift; + + if (unlikely(ramp_delay <= 0)) + return -EINVAL; + + /* VRRCR slew rate offsets are the same as VCCR go bit offsets */ + shift = ffs(rdev->desc->apply_bit) - 1; + + /* The slew rate can be set to 0.88, 1.75, 3.5, or 7 mV/uS */ + for (sel = 0; sel < 4; sel++) { + if ((880 << sel) >= ramp_delay) { + return regmap_update_bits(ltc3589->regmap, + LTC3589_VRRCR, + 0x3 << shift, sel << shift); + } + } + return -EINVAL; +} + +static int ltc3589_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + struct ltc3589 *ltc3589 = rdev_get_drvdata(rdev); + int sel; + + sel = regulator_map_voltage_linear(rdev, uV, uV); + if (sel < 0) + return sel; + + /* DTV2 register follows right after the corresponding DTV1 register */ + return regmap_update_bits(ltc3589->regmap, rdev->desc->vsel_reg + 1, + rdev->desc->vsel_mask, sel); +} + +static int ltc3589_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct ltc3589 *ltc3589 = rdev_get_drvdata(rdev); + int mask, bit = 0; + + /* VCCR reference selects are right next to the VCCR go bits */ + mask = rdev->desc->apply_bit << 1; + + if (mode == REGULATOR_MODE_STANDBY) + bit = mask; /* Select DTV2 */ + + mask |= rdev->desc->apply_bit; + bit |= rdev->desc->apply_bit; + return regmap_update_bits(ltc3589->regmap, LTC3589_VCCR, mask, bit); +} + +/* SW1, SW2, SW3, LDO2 */ +static const struct regulator_ops ltc3589_linear_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_ramp_delay = ltc3589_set_ramp_delay, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_voltage = ltc3589_set_suspend_voltage, + .set_suspend_mode = ltc3589_set_suspend_mode, +}; + +/* BB_OUT, LDO3 */ +static const struct regulator_ops ltc3589_fixed_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +/* LDO1 */ +static const struct regulator_ops ltc3589_fixed_standby_regulator_ops = { +}; + +/* LDO4 */ +static const struct regulator_ops ltc3589_table_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_table, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + + +#define LTC3589_REG(_name, _ops, en_bit, dtv1_reg, dtv_mask, go_bit) \ + [LTC3589_ ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = (dtv_mask) + 1, \ + .min_uV = (go_bit) ? 362500 : 0, \ + .uV_step = (go_bit) ? 12500 : 0, \ + .ramp_delay = (go_bit) ? 1750 : 0, \ + .fixed_uV = (dtv_mask) ? 0 : 800000, \ + .ops = <c3589_ ## _ops ## _regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = LTC3589_ ## _name, \ + .owner = THIS_MODULE, \ + .vsel_reg = (dtv1_reg), \ + .vsel_mask = (dtv_mask), \ + .apply_reg = (go_bit) ? LTC3589_VCCR : 0, \ + .apply_bit = (go_bit), \ + .enable_reg = (en_bit) ? LTC3589_OVEN : 0, \ + .enable_mask = (en_bit), \ + }, \ + } + +#define LTC3589_LINEAR_REG(_name, _dtv1) \ + LTC3589_REG(_name, linear, LTC3589_OVEN_ ## _name, \ + LTC3589_ ## _dtv1, 0x1f, \ + LTC3589_VCCR_ ## _name ## _GO) + +#define LTC3589_FIXED_REG(_name) \ + LTC3589_REG(_name, fixed, LTC3589_OVEN_ ## _name, 0, 0, 0) + +static struct ltc3589_regulator ltc3589_regulators[LTC3589_NUM_REGULATORS] = { + LTC3589_LINEAR_REG(SW1, B1DTV1), + LTC3589_LINEAR_REG(SW2, B2DTV1), + LTC3589_LINEAR_REG(SW3, B3DTV1), + LTC3589_FIXED_REG(BB_OUT), + LTC3589_REG(LDO1, fixed_standby, 0, 0, 0, 0), + LTC3589_LINEAR_REG(LDO2, L2DTV1), + LTC3589_FIXED_REG(LDO3), + LTC3589_REG(LDO4, table, LTC3589_OVEN_LDO4, LTC3589_L2DTV2, 0x60, 0), +}; + +#ifdef CONFIG_OF +static struct of_regulator_match ltc3589_matches[LTC3589_NUM_REGULATORS] = { + { .name = "sw1", }, + { .name = "sw2", }, + { .name = "sw3", }, + { .name = "bb-out", }, + { .name = "ldo1", }, /* standby */ + { .name = "ldo2", }, + { .name = "ldo3", }, + { .name = "ldo4", }, +}; + +static int ltc3589_parse_regulators_dt(struct ltc3589 *ltc3589) +{ + struct device *dev = ltc3589->dev; + struct device_node *node; + int i, ret; + + node = of_get_child_by_name(dev->of_node, "regulators"); + if (!node) { + dev_err(dev, "regulators node not found\n"); + return -EINVAL; + } + + ret = of_regulator_match(dev, node, ltc3589_matches, + ARRAY_SIZE(ltc3589_matches)); + of_node_put(node); + if (ret < 0) { + dev_err(dev, "Error parsing regulator init data: %d\n", ret); + return ret; + } + if (ret != LTC3589_NUM_REGULATORS) { + dev_err(dev, "Only %d regulators described in device tree\n", + ret); + return -EINVAL; + } + + /* Parse feedback voltage dividers. LDO3 and LDO4 don't have them */ + for (i = 0; i < LTC3589_LDO3; i++) { + struct ltc3589_regulator *desc = <c3589->regulator_descs[i]; + struct device_node *np = ltc3589_matches[i].of_node; + u32 vdiv[2]; + + ret = of_property_read_u32_array(np, "lltc,fb-voltage-divider", + vdiv, 2); + if (ret) { + dev_err(dev, "Failed to parse voltage divider: %d\n", + ret); + return ret; + } + + desc->r1 = vdiv[0]; + desc->r2 = vdiv[1]; + } + + return 0; +} + +static inline struct regulator_init_data *match_init_data(int index) +{ + return ltc3589_matches[index].init_data; +} + +static inline struct device_node *match_of_node(int index) +{ + return ltc3589_matches[index].of_node; +} +#else +static inline int ltc3589_parse_regulators_dt(struct ltc3589 *ltc3589) +{ + return 0; +} + +static inline struct regulator_init_data *match_init_data(int index) +{ + return NULL; +} + +static inline struct device_node *match_of_node(int index) +{ + return NULL; +} +#endif + +static bool ltc3589_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTC3589_IRQSTAT: + case LTC3589_SCR1: + case LTC3589_OVEN: + case LTC3589_SCR2: + case LTC3589_VCCR: + case LTC3589_CLIRQ: + case LTC3589_B1DTV1: + case LTC3589_B1DTV2: + case LTC3589_VRRCR: + case LTC3589_B2DTV1: + case LTC3589_B2DTV2: + case LTC3589_B3DTV1: + case LTC3589_B3DTV2: + case LTC3589_L2DTV1: + case LTC3589_L2DTV2: + return true; + } + return false; +} + +static bool ltc3589_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTC3589_IRQSTAT: + case LTC3589_SCR1: + case LTC3589_OVEN: + case LTC3589_SCR2: + case LTC3589_PGSTAT: + case LTC3589_VCCR: + case LTC3589_B1DTV1: + case LTC3589_B1DTV2: + case LTC3589_VRRCR: + case LTC3589_B2DTV1: + case LTC3589_B2DTV2: + case LTC3589_B3DTV1: + case LTC3589_B3DTV2: + case LTC3589_L2DTV1: + case LTC3589_L2DTV2: + return true; + } + return false; +} + +static bool ltc3589_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTC3589_IRQSTAT: + case LTC3589_PGSTAT: + case LTC3589_VCCR: + return true; + } + return false; +} + +static const struct reg_default ltc3589_reg_defaults[] = { + { LTC3589_SCR1, 0x00 }, + { LTC3589_OVEN, 0x00 }, + { LTC3589_SCR2, 0x00 }, + { LTC3589_VCCR, 0x00 }, + { LTC3589_B1DTV1, 0x19 }, + { LTC3589_B1DTV2, 0x19 }, + { LTC3589_VRRCR, 0xff }, + { LTC3589_B2DTV1, 0x19 }, + { LTC3589_B2DTV2, 0x19 }, + { LTC3589_B3DTV1, 0x19 }, + { LTC3589_B3DTV2, 0x19 }, + { LTC3589_L2DTV1, 0x19 }, + { LTC3589_L2DTV2, 0x19 }, +}; + +static const struct regmap_config ltc3589_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = ltc3589_writeable_reg, + .readable_reg = ltc3589_readable_reg, + .volatile_reg = ltc3589_volatile_reg, + .max_register = LTC3589_L2DTV2, + .reg_defaults = ltc3589_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ltc3589_reg_defaults), + .use_single_rw = true, + .cache_type = REGCACHE_RBTREE, +}; + + +static irqreturn_t ltc3589_isr(int irq, void *dev_id) +{ + struct ltc3589 *ltc3589 = dev_id; + unsigned int i, irqstat, event; + + regmap_read(ltc3589->regmap, LTC3589_IRQSTAT, &irqstat); + + if (irqstat & LTC3589_IRQSTAT_THERMAL_WARN) { + event = REGULATOR_EVENT_OVER_TEMP; + for (i = 0; i < LTC3589_NUM_REGULATORS; i++) + regulator_notifier_call_chain(ltc3589->regulators[i], + event, NULL); + } + + if (irqstat & LTC3589_IRQSTAT_UNDERVOLT_WARN) { + event = REGULATOR_EVENT_UNDER_VOLTAGE; + for (i = 0; i < LTC3589_NUM_REGULATORS; i++) + regulator_notifier_call_chain(ltc3589->regulators[i], + event, NULL); + } + + /* Clear warning condition */ + regmap_write(ltc3589->regmap, LTC3589_CLIRQ, 0); + + return IRQ_HANDLED; +} + +static inline unsigned int ltc3589_scale(unsigned int uV, u32 r1, u32 r2) +{ + uint64_t tmp; + if (uV == 0) + return 0; + tmp = (uint64_t)uV * r1; + do_div(tmp, r2); + return uV + (unsigned int)tmp; +} + +static void ltc3589_apply_fb_voltage_divider(struct ltc3589_regulator *rdesc) +{ + struct regulator_desc *desc = &rdesc->desc; + + if (!rdesc->r1 || !rdesc->r2) + return; + + desc->min_uV = ltc3589_scale(desc->min_uV, rdesc->r1, rdesc->r2); + desc->uV_step = ltc3589_scale(desc->uV_step, rdesc->r1, rdesc->r2); + desc->fixed_uV = ltc3589_scale(desc->fixed_uV, rdesc->r1, rdesc->r2); +} + +static int ltc3589_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ltc3589_regulator *descs; + struct ltc3589 *ltc3589; + int i, ret; + + ltc3589 = devm_kzalloc(dev, sizeof(*ltc3589), GFP_KERNEL); + if (!ltc3589) + return -ENOMEM; + + i2c_set_clientdata(client, ltc3589); + if (client->dev.of_node) + ltc3589->variant = (enum ltc3589_variant) + of_device_get_match_data(&client->dev); + else + ltc3589->variant = id->driver_data; + ltc3589->dev = dev; + + descs = ltc3589->regulator_descs; + memcpy(descs, ltc3589_regulators, sizeof(ltc3589_regulators)); + if (ltc3589->variant == LTC3589) { + descs[LTC3589_LDO3].desc.fixed_uV = 1800000; + descs[LTC3589_LDO4].desc.volt_table = ltc3589_ldo4; + } else { + descs[LTC3589_LDO3].desc.fixed_uV = 2800000; + descs[LTC3589_LDO4].desc.volt_table = ltc3589_12_ldo4; + } + + ltc3589->regmap = devm_regmap_init_i2c(client, <c3589_regmap_config); + if (IS_ERR(ltc3589->regmap)) { + ret = PTR_ERR(ltc3589->regmap); + dev_err(dev, "failed to initialize regmap: %d\n", ret); + return ret; + } + + ret = ltc3589_parse_regulators_dt(ltc3589); + if (ret) + return ret; + + for (i = 0; i < LTC3589_NUM_REGULATORS; i++) { + struct ltc3589_regulator *rdesc = <c3589->regulator_descs[i]; + struct regulator_desc *desc = &rdesc->desc; + struct regulator_init_data *init_data; + struct regulator_config config = { }; + + init_data = match_init_data(i); + + if (i < LTC3589_LDO3) + ltc3589_apply_fb_voltage_divider(rdesc); + + config.dev = dev; + config.init_data = init_data; + config.driver_data = ltc3589; + config.of_node = match_of_node(i); + + ltc3589->regulators[i] = devm_regulator_register(dev, desc, + &config); + if (IS_ERR(ltc3589->regulators[i])) { + ret = PTR_ERR(ltc3589->regulators[i]); + dev_err(dev, "failed to register regulator %s: %d\n", + desc->name, ret); + return ret; + } + } + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + ltc3589_isr, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + client->name, ltc3589); + if (ret) { + dev_err(dev, "Failed to request IRQ: %d\n", ret); + return ret; + } + } + + return 0; +} + +static const struct i2c_device_id ltc3589_i2c_id[] = { + { "ltc3589", LTC3589 }, + { "ltc3589-1", LTC3589_1 }, + { "ltc3589-2", LTC3589_2 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc3589_i2c_id); + +static const struct of_device_id ltc3589_of_match[] = { + { + .compatible = "lltc,ltc3589", + .data = (void *)LTC3589, + }, + { + .compatible = "lltc,ltc3589-1", + .data = (void *)LTC3589_1, + }, + { + .compatible = "lltc,ltc3589-2", + .data = (void *)LTC3589_2, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ltc3589_of_match); + +static struct i2c_driver ltc3589_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(ltc3589_of_match), + }, + .probe = ltc3589_probe, + .id_table = ltc3589_i2c_id, +}; +module_i2c_driver(ltc3589_driver); + +MODULE_AUTHOR("Philipp Zabel <p.zabel@pengutronix.de>"); +MODULE_DESCRIPTION("Regulator driver for Linear Technology LTC3589(-1,2)"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/ltc3676.c b/drivers/regulator/ltc3676.c new file mode 100644 index 000000000..9dec1609f --- /dev/null +++ b/drivers/regulator/ltc3676.c @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2016 Gateworks Corporation, Inc. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> + +#define DRIVER_NAME "ltc3676" + +/* LTC3676 Registers */ +#define LTC3676_BUCK1 0x01 +#define LTC3676_BUCK2 0x02 +#define LTC3676_BUCK3 0x03 +#define LTC3676_BUCK4 0x04 +#define LTC3676_LDOA 0x05 +#define LTC3676_LDOB 0x06 +#define LTC3676_SQD1 0x07 +#define LTC3676_SQD2 0x08 +#define LTC3676_CNTRL 0x09 +#define LTC3676_DVB1A 0x0A +#define LTC3676_DVB1B 0x0B +#define LTC3676_DVB2A 0x0C +#define LTC3676_DVB2B 0x0D +#define LTC3676_DVB3A 0x0E +#define LTC3676_DVB3B 0x0F +#define LTC3676_DVB4A 0x10 +#define LTC3676_DVB4B 0x11 +#define LTC3676_MSKIRQ 0x12 +#define LTC3676_MSKPG 0x13 +#define LTC3676_USER 0x14 +#define LTC3676_IRQSTAT 0x15 +#define LTC3676_PGSTATL 0x16 +#define LTC3676_PGSTATRT 0x17 +#define LTC3676_HRST 0x1E +#define LTC3676_CLIRQ 0x1F + +#define LTC3676_DVBxA_REF_SELECT BIT(5) +#define LTC3676_DVBxB_PGOOD_MASK BIT(5) + +#define LTC3676_IRQSTAT_PGOOD_TIMEOUT BIT(3) +#define LTC3676_IRQSTAT_UNDERVOLT_WARN BIT(4) +#define LTC3676_IRQSTAT_UNDERVOLT_FAULT BIT(5) +#define LTC3676_IRQSTAT_THERMAL_WARN BIT(6) +#define LTC3676_IRQSTAT_THERMAL_FAULT BIT(7) + +enum ltc3676_reg { + LTC3676_SW1, + LTC3676_SW2, + LTC3676_SW3, + LTC3676_SW4, + LTC3676_LDO1, + LTC3676_LDO2, + LTC3676_LDO3, + LTC3676_LDO4, + LTC3676_NUM_REGULATORS, +}; + +struct ltc3676 { + struct regmap *regmap; + struct device *dev; + struct regulator_desc regulator_descs[LTC3676_NUM_REGULATORS]; + struct regulator_dev *regulators[LTC3676_NUM_REGULATORS]; +}; + +static int ltc3676_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + struct ltc3676 *ltc3676 = rdev_get_drvdata(rdev); + struct device *dev = ltc3676->dev; + int dcdc = rdev_get_id(rdev); + int sel; + + dev_dbg(dev, "%s id=%d uV=%d\n", __func__, dcdc, uV); + sel = regulator_map_voltage_linear(rdev, uV, uV); + if (sel < 0) + return sel; + + /* DVBB register follows right after the corresponding DVBA register */ + return regmap_update_bits(ltc3676->regmap, rdev->desc->vsel_reg + 1, + rdev->desc->vsel_mask, sel); +} + +static int ltc3676_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct ltc3676 *ltc3676= rdev_get_drvdata(rdev); + struct device *dev = ltc3676->dev; + int mask, val; + int dcdc = rdev_get_id(rdev); + + dev_dbg(dev, "%s id=%d mode=%d\n", __func__, dcdc, mode); + + mask = LTC3676_DVBxA_REF_SELECT; + switch (mode) { + case REGULATOR_MODE_STANDBY: + val = 0; /* select DVBxA */ + break; + case REGULATOR_MODE_NORMAL: + val = LTC3676_DVBxA_REF_SELECT; /* select DVBxB */ + break; + default: + dev_warn(&rdev->dev, "%s: regulator mode: 0x%x not supported\n", + rdev->desc->name, mode); + return -EINVAL; + } + + return regmap_update_bits(ltc3676->regmap, rdev->desc->vsel_reg, + mask, val); +} + +static int ltc3676_set_voltage_sel(struct regulator_dev *rdev, unsigned selector) +{ + struct ltc3676 *ltc3676 = rdev_get_drvdata(rdev); + struct device *dev = ltc3676->dev; + int ret, dcdc = rdev_get_id(rdev); + + dev_dbg(dev, "%s id=%d selector=%d\n", __func__, dcdc, selector); + + ret = regmap_update_bits(ltc3676->regmap, rdev->desc->vsel_reg + 1, + LTC3676_DVBxB_PGOOD_MASK, + LTC3676_DVBxB_PGOOD_MASK); + if (ret) + return ret; + + return regulator_set_voltage_sel_regmap(rdev, selector); +} + +static inline unsigned int ltc3676_scale(unsigned int uV, u32 r1, u32 r2) +{ + uint64_t tmp; + if (uV == 0) + return 0; + tmp = (uint64_t)uV * r1; + do_div(tmp, r2); + return uV + (unsigned int)tmp; +} + +static int ltc3676_of_parse_cb(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct ltc3676 *ltc3676 = config->driver_data; + struct regulator_desc *rdesc = <c3676->regulator_descs[desc->id]; + u32 r[2]; + int ret; + + /* LDO3 has a fixed output */ + if (desc->id == LTC3676_LDO3) + return 0; + + ret = of_property_read_u32_array(np, "lltc,fb-voltage-divider", r, 2); + if (ret) { + dev_err(ltc3676->dev, "Failed to parse voltage divider: %d\n", + ret); + return ret; + } + + rdesc->min_uV = ltc3676_scale(desc->min_uV, r[0], r[1]); + rdesc->uV_step = ltc3676_scale(desc->uV_step, r[0], r[1]); + rdesc->fixed_uV = ltc3676_scale(desc->fixed_uV, r[0], r[1]); + + return 0; +} + +/* SW1, SW2, SW3, SW4 linear 0.8V-3.3V with scalar via R1/R2 feeback res */ +static const struct regulator_ops ltc3676_linear_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = ltc3676_set_voltage_sel, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_suspend_voltage = ltc3676_set_suspend_voltage, + .set_suspend_mode = ltc3676_set_suspend_mode, +}; + +/* LDO1 always on fixed 0.8V-3.3V via scalar via R1/R2 feeback res */ +static const struct regulator_ops ltc3676_fixed_standby_regulator_ops = { +}; + +/* LDO2, LDO3 fixed (LDO2 has external scalar via R1/R2 feedback res) */ +static const struct regulator_ops ltc3676_fixed_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +#define LTC3676_REG(_id, _name, _ops, en_reg, en_bit, dvba_reg, dvb_mask) \ + [LTC3676_ ## _id] = { \ + .name = #_name, \ + .of_match = of_match_ptr(#_name), \ + .regulators_node = of_match_ptr("regulators"), \ + .of_parse_cb = ltc3676_of_parse_cb, \ + .n_voltages = (dvb_mask) + 1, \ + .min_uV = (dvba_reg) ? 412500 : 0, \ + .uV_step = (dvba_reg) ? 12500 : 0, \ + .ramp_delay = (dvba_reg) ? 800 : 0, \ + .fixed_uV = (dvb_mask) ? 0 : 725000, \ + .ops = <c3676_ ## _ops ## _regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = LTC3676_ ## _id, \ + .owner = THIS_MODULE, \ + .vsel_reg = (dvba_reg), \ + .vsel_mask = (dvb_mask), \ + .enable_reg = (en_reg), \ + .enable_mask = (1 << en_bit), \ + } + +#define LTC3676_LINEAR_REG(_id, _name, _en, _dvba) \ + LTC3676_REG(_id, _name, linear, \ + LTC3676_ ## _en, 7, \ + LTC3676_ ## _dvba, 0x1f) + +#define LTC3676_FIXED_REG(_id, _name, _en_reg, _en_bit) \ + LTC3676_REG(_id, _name, fixed, LTC3676_ ## _en_reg, _en_bit, 0, 0) + +static struct regulator_desc ltc3676_regulators[LTC3676_NUM_REGULATORS] = { + LTC3676_LINEAR_REG(SW1, sw1, BUCK1, DVB1A), + LTC3676_LINEAR_REG(SW2, sw2, BUCK2, DVB2A), + LTC3676_LINEAR_REG(SW3, sw3, BUCK3, DVB3A), + LTC3676_LINEAR_REG(SW4, sw4, BUCK4, DVB4A), + LTC3676_REG(LDO1, ldo1, fixed_standby, 0, 0, 0, 0), + LTC3676_FIXED_REG(LDO2, ldo2, LDOA, 2), + LTC3676_FIXED_REG(LDO3, ldo3, LDOA, 5), + LTC3676_FIXED_REG(LDO4, ldo4, LDOB, 2), +}; + +static bool ltc3676_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTC3676_IRQSTAT: + case LTC3676_BUCK1: + case LTC3676_BUCK2: + case LTC3676_BUCK3: + case LTC3676_BUCK4: + case LTC3676_LDOA: + case LTC3676_LDOB: + case LTC3676_SQD1: + case LTC3676_SQD2: + case LTC3676_CNTRL: + case LTC3676_DVB1A: + case LTC3676_DVB1B: + case LTC3676_DVB2A: + case LTC3676_DVB2B: + case LTC3676_DVB3A: + case LTC3676_DVB3B: + case LTC3676_DVB4A: + case LTC3676_DVB4B: + case LTC3676_MSKIRQ: + case LTC3676_MSKPG: + case LTC3676_USER: + case LTC3676_HRST: + case LTC3676_CLIRQ: + return true; + } + return false; +} + +static bool ltc3676_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTC3676_IRQSTAT: + case LTC3676_BUCK1: + case LTC3676_BUCK2: + case LTC3676_BUCK3: + case LTC3676_BUCK4: + case LTC3676_LDOA: + case LTC3676_LDOB: + case LTC3676_SQD1: + case LTC3676_SQD2: + case LTC3676_CNTRL: + case LTC3676_DVB1A: + case LTC3676_DVB1B: + case LTC3676_DVB2A: + case LTC3676_DVB2B: + case LTC3676_DVB3A: + case LTC3676_DVB3B: + case LTC3676_DVB4A: + case LTC3676_DVB4B: + case LTC3676_MSKIRQ: + case LTC3676_MSKPG: + case LTC3676_USER: + case LTC3676_HRST: + case LTC3676_CLIRQ: + return true; + } + return false; +} + +static bool ltc3676_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTC3676_IRQSTAT: + case LTC3676_PGSTATL: + case LTC3676_PGSTATRT: + return true; + } + return false; +} + +static const struct regmap_config ltc3676_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = ltc3676_writeable_reg, + .readable_reg = ltc3676_readable_reg, + .volatile_reg = ltc3676_volatile_reg, + .max_register = LTC3676_CLIRQ, + .use_single_rw = true, + .cache_type = REGCACHE_RBTREE, +}; + +static irqreturn_t ltc3676_isr(int irq, void *dev_id) +{ + struct ltc3676 *ltc3676 = dev_id; + struct device *dev = ltc3676->dev; + unsigned int i, irqstat, event; + + regmap_read(ltc3676->regmap, LTC3676_IRQSTAT, &irqstat); + + dev_dbg(dev, "irq%d irqstat=0x%02x\n", irq, irqstat); + if (irqstat & LTC3676_IRQSTAT_THERMAL_WARN) { + dev_warn(dev, "Over-temperature Warning\n"); + event = REGULATOR_EVENT_OVER_TEMP; + for (i = 0; i < LTC3676_NUM_REGULATORS; i++) + regulator_notifier_call_chain(ltc3676->regulators[i], + event, NULL); + } + + if (irqstat & LTC3676_IRQSTAT_UNDERVOLT_WARN) { + dev_info(dev, "Undervoltage Warning\n"); + event = REGULATOR_EVENT_UNDER_VOLTAGE; + for (i = 0; i < LTC3676_NUM_REGULATORS; i++) + regulator_notifier_call_chain(ltc3676->regulators[i], + event, NULL); + } + + /* Clear warning condition */ + regmap_write(ltc3676->regmap, LTC3676_CLIRQ, 0); + + return IRQ_HANDLED; +} + +static int ltc3676_regulator_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct regulator_init_data *init_data = dev_get_platdata(dev); + struct regulator_desc *descs; + struct ltc3676 *ltc3676; + int i, ret; + + ltc3676 = devm_kzalloc(dev, sizeof(*ltc3676), GFP_KERNEL); + if (!ltc3676) + return -ENOMEM; + + i2c_set_clientdata(client, ltc3676); + ltc3676->dev = dev; + + descs = ltc3676->regulator_descs; + memcpy(descs, ltc3676_regulators, sizeof(ltc3676_regulators)); + descs[LTC3676_LDO3].fixed_uV = 1800000; /* LDO3 is fixed 1.8V */ + + ltc3676->regmap = devm_regmap_init_i2c(client, <c3676_regmap_config); + if (IS_ERR(ltc3676->regmap)) { + ret = PTR_ERR(ltc3676->regmap); + dev_err(dev, "failed to initialize regmap: %d\n", ret); + return ret; + } + + for (i = 0; i < LTC3676_NUM_REGULATORS; i++) { + struct regulator_desc *desc = <c3676->regulator_descs[i]; + struct regulator_config config = { }; + + if (init_data) + config.init_data = &init_data[i]; + + config.dev = dev; + config.driver_data = ltc3676; + + ltc3676->regulators[i] = devm_regulator_register(dev, desc, + &config); + if (IS_ERR(ltc3676->regulators[i])) { + ret = PTR_ERR(ltc3676->regulators[i]); + dev_err(dev, "failed to register regulator %s: %d\n", + desc->name, ret); + return ret; + } + } + + regmap_write(ltc3676->regmap, LTC3676_CLIRQ, 0); + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + ltc3676_isr, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + client->name, ltc3676); + if (ret) { + dev_err(dev, "Failed to request IRQ: %d\n", ret); + return ret; + } + } + + return 0; +} + +static const struct i2c_device_id ltc3676_i2c_id[] = { + { "ltc3676" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc3676_i2c_id); + +static const struct of_device_id ltc3676_of_match[] = { + { .compatible = "lltc,ltc3676" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ltc3676_of_match); + +static struct i2c_driver ltc3676_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(ltc3676_of_match), + }, + .probe = ltc3676_regulator_probe, + .id_table = ltc3676_i2c_id, +}; +module_i2c_driver(ltc3676_driver); + +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>"); +MODULE_DESCRIPTION("Regulator driver for Linear Technology LTC1376"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/max14577-regulator.c b/drivers/regulator/max14577-regulator.c new file mode 100644 index 000000000..bc7f4751b --- /dev/null +++ b/drivers/regulator/max14577-regulator.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// max14577.c - Regulator driver for the Maxim 14577/77836 +// +// Copyright (C) 2013,2014 Samsung Electronics +// Krzysztof Kozlowski <krzk@kernel.org> + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/max14577.h> +#include <linux/mfd/max14577-private.h> +#include <linux/regulator/of_regulator.h> + +static int max14577_reg_is_enabled(struct regulator_dev *rdev) +{ + int rid = rdev_get_id(rdev); + struct regmap *rmap = rdev->regmap; + u8 reg_data; + + switch (rid) { + case MAX14577_CHARGER: + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data); + if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0) + return 0; + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); + if ((reg_data & STATUS3_CGMBC_MASK) == 0) + return 0; + /* MBCHOSTEN and CGMBC are on */ + return 1; + default: + return -EINVAL; + } +} + +static int max14577_reg_get_current_limit(struct regulator_dev *rdev) +{ + u8 reg_data; + struct regmap *rmap = rdev->regmap; + struct max14577 *max14577 = rdev_get_drvdata(rdev); + const struct maxim_charger_current *limits = + &maxim_charger_currents[max14577->dev_type]; + + if (rdev_get_id(rdev) != MAX14577_CHARGER) + return -EINVAL; + + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL4, ®_data); + + if ((reg_data & CHGCTRL4_MBCICHWRCL_MASK) == 0) + return limits->min; + + reg_data = ((reg_data & CHGCTRL4_MBCICHWRCH_MASK) >> + CHGCTRL4_MBCICHWRCH_SHIFT); + return limits->high_start + reg_data * limits->high_step; +} + +static int max14577_reg_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + u8 reg_data; + int ret; + struct max14577 *max14577 = rdev_get_drvdata(rdev); + const struct maxim_charger_current *limits = + &maxim_charger_currents[max14577->dev_type]; + + if (rdev_get_id(rdev) != MAX14577_CHARGER) + return -EINVAL; + + ret = maxim_charger_calc_reg_current(limits, min_uA, max_uA, ®_data); + if (ret) + return ret; + + return max14577_update_reg(rdev->regmap, MAX14577_CHG_REG_CHG_CTRL4, + CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK, + reg_data); +} + +static const struct regulator_ops max14577_safeout_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +static const struct regulator_ops max14577_charger_ops = { + .is_enabled = max14577_reg_is_enabled, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_current_limit = max14577_reg_get_current_limit, + .set_current_limit = max14577_reg_set_current_limit, +}; + +#define MAX14577_SAFEOUT_REG { \ + .name = "SAFEOUT", \ + .of_match = of_match_ptr("SAFEOUT"), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = MAX14577_SAFEOUT, \ + .ops = &max14577_safeout_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .n_voltages = 1, \ + .min_uV = MAX14577_REGULATOR_SAFEOUT_VOLTAGE, \ + .enable_reg = MAX14577_REG_CONTROL2, \ + .enable_mask = CTRL2_SFOUTORD_MASK, \ +} +#define MAX14577_CHARGER_REG { \ + .name = "CHARGER", \ + .of_match = of_match_ptr("CHARGER"), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = MAX14577_CHARGER, \ + .ops = &max14577_charger_ops, \ + .type = REGULATOR_CURRENT, \ + .owner = THIS_MODULE, \ + .enable_reg = MAX14577_CHG_REG_CHG_CTRL2, \ + .enable_mask = CHGCTRL2_MBCHOSTEN_MASK, \ +} + +static const struct regulator_desc max14577_supported_regulators[] = { + [MAX14577_SAFEOUT] = MAX14577_SAFEOUT_REG, + [MAX14577_CHARGER] = MAX14577_CHARGER_REG, +}; + +static const struct regulator_ops max77836_ldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + /* TODO: add .set_suspend_mode */ +}; + +#define MAX77836_LDO_REG(num) { \ + .name = "LDO" # num, \ + .of_match = of_match_ptr("LDO" # num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = MAX77836_LDO ## num, \ + .ops = &max77836_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .n_voltages = MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM, \ + .min_uV = MAX77836_REGULATOR_LDO_VOLTAGE_MIN, \ + .uV_step = MAX77836_REGULATOR_LDO_VOLTAGE_STEP, \ + .enable_reg = MAX77836_LDO_REG_CNFG1_LDO ## num, \ + .enable_mask = MAX77836_CNFG1_LDO_PWRMD_MASK, \ + .vsel_reg = MAX77836_LDO_REG_CNFG1_LDO ## num, \ + .vsel_mask = MAX77836_CNFG1_LDO_TV_MASK, \ +} + +static const struct regulator_desc max77836_supported_regulators[] = { + [MAX14577_SAFEOUT] = MAX14577_SAFEOUT_REG, + [MAX14577_CHARGER] = MAX14577_CHARGER_REG, + [MAX77836_LDO1] = MAX77836_LDO_REG(1), + [MAX77836_LDO2] = MAX77836_LDO_REG(2), +}; + +#ifdef CONFIG_OF +static struct of_regulator_match max14577_regulator_matches[] = { + { .name = "SAFEOUT", }, + { .name = "CHARGER", }, +}; + +static struct of_regulator_match max77836_regulator_matches[] = { + { .name = "SAFEOUT", }, + { .name = "CHARGER", }, + { .name = "LDO1", }, + { .name = "LDO2", }, +}; + +static inline struct regulator_init_data *match_init_data(int index, + enum maxim_device_type dev_type) +{ + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + return max77836_regulator_matches[index].init_data; + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + return max14577_regulator_matches[index].init_data; + } +} + +static inline struct device_node *match_of_node(int index, + enum maxim_device_type dev_type) +{ + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + return max77836_regulator_matches[index].of_node; + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + return max14577_regulator_matches[index].of_node; + } +} +#else /* CONFIG_OF */ +static inline struct regulator_init_data *match_init_data(int index, + enum maxim_device_type dev_type) +{ + return NULL; +} + +static inline struct device_node *match_of_node(int index, + enum maxim_device_type dev_type) +{ + return NULL; +} +#endif /* CONFIG_OF */ + +/** + * Registers for regulators of max77836 use different I2C slave addresses so + * different regmaps must be used for them. + * + * Returns proper regmap for accessing regulator passed by id. + */ +static struct regmap *max14577_get_regmap(struct max14577 *max14577, + int reg_id) +{ + switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + switch (reg_id) { + case MAX77836_SAFEOUT ... MAX77836_CHARGER: + return max14577->regmap; + default: + /* MAX77836_LDO1 ... MAX77836_LDO2 */ + return max14577->regmap_pmic; + } + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + return max14577->regmap; + } +} + +static int max14577_regulator_probe(struct platform_device *pdev) +{ + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent); + struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev); + int i, ret = 0; + struct regulator_config config = {}; + const struct regulator_desc *supported_regulators; + unsigned int supported_regulators_size; + enum maxim_device_type dev_type = max14577->dev_type; + + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + supported_regulators = max77836_supported_regulators; + supported_regulators_size = ARRAY_SIZE(max77836_supported_regulators); + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + supported_regulators = max14577_supported_regulators; + supported_regulators_size = ARRAY_SIZE(max14577_supported_regulators); + } + + config.dev = max14577->dev; + config.driver_data = max14577; + + for (i = 0; i < supported_regulators_size; i++) { + struct regulator_dev *regulator; + /* + * Index of supported_regulators[] is also the id and must + * match index of pdata->regulators[]. + */ + if (pdata && pdata->regulators) { + config.init_data = pdata->regulators[i].initdata; + config.of_node = pdata->regulators[i].of_node; + } else { + config.init_data = match_init_data(i, dev_type); + config.of_node = match_of_node(i, dev_type); + } + config.regmap = max14577_get_regmap(max14577, + supported_regulators[i].id); + + regulator = devm_regulator_register(&pdev->dev, + &supported_regulators[i], &config); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + dev_err(&pdev->dev, + "Regulator init failed for %d/%s with error: %d\n", + i, supported_regulators[i].name, ret); + return ret; + } + } + + return ret; +} + +static const struct platform_device_id max14577_regulator_id[] = { + { "max14577-regulator", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836-regulator", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max14577_regulator_id); + +static struct platform_driver max14577_regulator_driver = { + .driver = { + .name = "max14577-regulator", + }, + .probe = max14577_regulator_probe, + .id_table = max14577_regulator_id, +}; + +static int __init max14577_regulator_init(void) +{ + BUILD_BUG_ON(ARRAY_SIZE(max14577_supported_regulators) != MAX14577_REGULATOR_NUM); + BUILD_BUG_ON(ARRAY_SIZE(max77836_supported_regulators) != MAX77836_REGULATOR_NUM); + + BUILD_BUG_ON(MAX77836_REGULATOR_LDO_VOLTAGE_MIN + + (MAX77836_REGULATOR_LDO_VOLTAGE_STEP * + (MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM - 1)) != + MAX77836_REGULATOR_LDO_VOLTAGE_MAX); + + return platform_driver_register(&max14577_regulator_driver); +} +subsys_initcall(max14577_regulator_init); + +static void __exit max14577_regulator_exit(void) +{ + platform_driver_unregister(&max14577_regulator_driver); +} +module_exit(max14577_regulator_exit); + +MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>"); +MODULE_DESCRIPTION("Maxim 14577/77836 regulator driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:max14577-regulator"); diff --git a/drivers/regulator/max1586.c b/drivers/regulator/max1586.c new file mode 100644 index 000000000..cc52779b5 --- /dev/null +++ b/drivers/regulator/max1586.c @@ -0,0 +1,329 @@ +/* + * max1586.c -- Voltage and current regulation for the Maxim 1586 + * + * Copyright (C) 2008 Robert Jarzmik + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/module.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/slab.h> +#include <linux/regulator/max1586.h> +#include <linux/of_device.h> +#include <linux/regulator/of_regulator.h> + +#define MAX1586_V3_MAX_VSEL 31 +#define MAX1586_V6_MAX_VSEL 3 + +#define MAX1586_V3_MIN_UV 700000 +#define MAX1586_V3_MAX_UV 1475000 + +#define MAX1586_V6_MIN_UV 0 +#define MAX1586_V6_MAX_UV 3000000 + +#define I2C_V3_SELECT (0 << 5) +#define I2C_V6_SELECT (1 << 5) + +struct max1586_data { + struct i2c_client *client; + + /* min/max V3 voltage */ + unsigned int min_uV; + unsigned int max_uV; + + unsigned int v3_curr_sel; + unsigned int v6_curr_sel; +}; + +/* + * V6 voltage + * On I2C bus, sending a "x" byte to the max1586 means : + * set V6 to either 0V, 1.8V, 2.5V, 3V depending on (x & 0x3) + * As regulator framework doesn't accept voltages to be 0V, we use 1uV. + */ +static const unsigned int v6_voltages_uv[] = { 1, 1800000, 2500000, 3000000 }; + +/* + * V3 voltage + * On I2C bus, sending a "x" byte to the max1586 means : + * set V3 to 0.700V + (x & 0x1f) * 0.025V + * This voltage can be increased by external resistors + * R24 and R25=100kOhm as described in the data sheet. + * The gain is approximately: 1 + R24/R25 + R24/185.5kOhm + */ +static int max1586_v3_get_voltage_sel(struct regulator_dev *rdev) +{ + struct max1586_data *max1586 = rdev_get_drvdata(rdev); + + return max1586->v3_curr_sel; +} + +static int max1586_v3_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct max1586_data *max1586 = rdev_get_drvdata(rdev); + struct i2c_client *client = max1586->client; + int ret; + u8 v3_prog; + + dev_dbg(&client->dev, "changing voltage v3 to %dmv\n", + regulator_list_voltage_linear(rdev, selector) / 1000); + + v3_prog = I2C_V3_SELECT | (u8) selector; + ret = i2c_smbus_write_byte(client, v3_prog); + if (ret) + return ret; + + max1586->v3_curr_sel = selector; + + return 0; +} + +static int max1586_v6_get_voltage_sel(struct regulator_dev *rdev) +{ + struct max1586_data *max1586 = rdev_get_drvdata(rdev); + + return max1586->v6_curr_sel; +} + +static int max1586_v6_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct max1586_data *max1586 = rdev_get_drvdata(rdev); + struct i2c_client *client = max1586->client; + u8 v6_prog; + int ret; + + dev_dbg(&client->dev, "changing voltage v6 to %dmv\n", + rdev->desc->volt_table[selector] / 1000); + + v6_prog = I2C_V6_SELECT | (u8) selector; + ret = i2c_smbus_write_byte(client, v6_prog); + if (ret) + return ret; + + max1586->v6_curr_sel = selector; + + return 0; +} + +/* + * The Maxim 1586 controls V3 and V6 voltages, but offers no way of reading back + * the set up value. + */ +static const struct regulator_ops max1586_v3_ops = { + .get_voltage_sel = max1586_v3_get_voltage_sel, + .set_voltage_sel = max1586_v3_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, +}; + +static const struct regulator_ops max1586_v6_ops = { + .get_voltage_sel = max1586_v6_get_voltage_sel, + .set_voltage_sel = max1586_v6_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, +}; + +static struct regulator_desc max1586_reg[] = { + { + .name = "Output_V3", + .id = MAX1586_V3, + .ops = &max1586_v3_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = MAX1586_V3_MAX_VSEL + 1, + .owner = THIS_MODULE, + }, + { + .name = "Output_V6", + .id = MAX1586_V6, + .ops = &max1586_v6_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = MAX1586_V6_MAX_VSEL + 1, + .volt_table = v6_voltages_uv, + .owner = THIS_MODULE, + }, +}; + +static int of_get_max1586_platform_data(struct device *dev, + struct max1586_platform_data *pdata) +{ + struct max1586_subdev_data *sub; + struct of_regulator_match rmatch[ARRAY_SIZE(max1586_reg)] = { }; + struct device_node *np = dev->of_node; + int i, matched; + + if (of_property_read_u32(np, "v3-gain", + &pdata->v3_gain) < 0) { + dev_err(dev, "%pOF has no 'v3-gain' property\n", np); + return -EINVAL; + } + + np = of_get_child_by_name(np, "regulators"); + if (!np) { + dev_err(dev, "missing 'regulators' subnode in DT\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(rmatch); i++) + rmatch[i].name = max1586_reg[i].name; + + matched = of_regulator_match(dev, np, rmatch, ARRAY_SIZE(rmatch)); + of_node_put(np); + /* + * If matched is 0, ie. neither Output_V3 nor Output_V6 have been found, + * return 0, which signals the normal situation where no subregulator is + * available. This is normal because the max1586 doesn't provide any + * readback support, so the subregulators can't report any status + * anyway. If matched < 0, return the error. + */ + if (matched <= 0) + return matched; + + pdata->subdevs = devm_kcalloc(dev, + matched, + sizeof(struct max1586_subdev_data), + GFP_KERNEL); + if (!pdata->subdevs) + return -ENOMEM; + + pdata->num_subdevs = matched; + sub = pdata->subdevs; + + for (i = 0; i < matched; i++) { + sub->id = i; + sub->name = rmatch[i].of_node->name; + sub->platform_data = rmatch[i].init_data; + sub++; + } + + return 0; +} + +static const struct of_device_id max1586_of_match[] = { + { .compatible = "maxim,max1586", }, + {}, +}; +MODULE_DEVICE_TABLE(of, max1586_of_match); + +static int max1586_pmic_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct max1586_platform_data *pdata, pdata_of; + struct regulator_config config = { }; + struct max1586_data *max1586; + int i, id, ret; + const struct of_device_id *match; + + pdata = dev_get_platdata(&client->dev); + if (client->dev.of_node && !pdata) { + match = of_match_device(of_match_ptr(max1586_of_match), + &client->dev); + if (!match) { + dev_err(&client->dev, "Error: No device match found\n"); + return -ENODEV; + } + ret = of_get_max1586_platform_data(&client->dev, &pdata_of); + if (ret < 0) + return ret; + pdata = &pdata_of; + } + + max1586 = devm_kzalloc(&client->dev, sizeof(struct max1586_data), + GFP_KERNEL); + if (!max1586) + return -ENOMEM; + + max1586->client = client; + + if (!pdata->v3_gain) + return -EINVAL; + + max1586->min_uV = MAX1586_V3_MIN_UV / 1000 * pdata->v3_gain / 1000; + max1586->max_uV = MAX1586_V3_MAX_UV / 1000 * pdata->v3_gain / 1000; + + /* Set curr_sel to default voltage on power-up */ + max1586->v3_curr_sel = 24; /* 1.3V */ + max1586->v6_curr_sel = 0; + + for (i = 0; i < pdata->num_subdevs && i <= MAX1586_V6; i++) { + struct regulator_dev *rdev; + + id = pdata->subdevs[i].id; + if (!pdata->subdevs[i].platform_data) + continue; + if (id < MAX1586_V3 || id > MAX1586_V6) { + dev_err(&client->dev, "invalid regulator id %d\n", id); + return -EINVAL; + } + + if (id == MAX1586_V3) { + max1586_reg[id].min_uV = max1586->min_uV; + max1586_reg[id].uV_step = + (max1586->max_uV - max1586->min_uV) / + MAX1586_V3_MAX_VSEL; + } + + config.dev = &client->dev; + config.init_data = pdata->subdevs[i].platform_data; + config.driver_data = max1586; + + rdev = devm_regulator_register(&client->dev, + &max1586_reg[id], &config); + if (IS_ERR(rdev)) { + dev_err(&client->dev, "failed to register %s\n", + max1586_reg[id].name); + return PTR_ERR(rdev); + } + } + + i2c_set_clientdata(client, max1586); + dev_info(&client->dev, "Maxim 1586 regulator driver loaded\n"); + return 0; +} + +static const struct i2c_device_id max1586_id[] = { + { "max1586", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max1586_id); + +static struct i2c_driver max1586_pmic_driver = { + .probe = max1586_pmic_probe, + .driver = { + .name = "max1586", + .of_match_table = of_match_ptr(max1586_of_match), + }, + .id_table = max1586_id, +}; + +static int __init max1586_pmic_init(void) +{ + return i2c_add_driver(&max1586_pmic_driver); +} +subsys_initcall(max1586_pmic_init); + +static void __exit max1586_pmic_exit(void) +{ + i2c_del_driver(&max1586_pmic_driver); +} +module_exit(max1586_pmic_exit); + +/* Module information */ +MODULE_DESCRIPTION("MAXIM 1586 voltage regulator driver"); +MODULE_AUTHOR("Robert Jarzmik"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/max77620-regulator.c b/drivers/regulator/max77620-regulator.c new file mode 100644 index 000000000..28baeac3a --- /dev/null +++ b/drivers/regulator/max77620-regulator.c @@ -0,0 +1,907 @@ +/* + * Maxim MAX77620 Regulator driver + * + * Copyright (c) 2016-2018, NVIDIA CORPORATION. All rights reserved. + * + * Author: Mallikarjun Kasoju <mkasoju@nvidia.com> + * Laxman Dewangan <ldewangan@nvidia.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/mfd/max77620.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> + +#define max77620_rails(_name) "max77620-"#_name + +/* Power Mode */ +#define MAX77620_POWER_MODE_NORMAL 3 +#define MAX77620_POWER_MODE_LPM 2 +#define MAX77620_POWER_MODE_GLPM 1 +#define MAX77620_POWER_MODE_DISABLE 0 + +/* SD Slew Rate */ +#define MAX77620_SD_SR_13_75 0 +#define MAX77620_SD_SR_27_5 1 +#define MAX77620_SD_SR_55 2 +#define MAX77620_SD_SR_100 3 + +enum max77620_regulators { + MAX77620_REGULATOR_ID_SD0, + MAX77620_REGULATOR_ID_SD1, + MAX77620_REGULATOR_ID_SD2, + MAX77620_REGULATOR_ID_SD3, + MAX77620_REGULATOR_ID_SD4, + MAX77620_REGULATOR_ID_LDO0, + MAX77620_REGULATOR_ID_LDO1, + MAX77620_REGULATOR_ID_LDO2, + MAX77620_REGULATOR_ID_LDO3, + MAX77620_REGULATOR_ID_LDO4, + MAX77620_REGULATOR_ID_LDO5, + MAX77620_REGULATOR_ID_LDO6, + MAX77620_REGULATOR_ID_LDO7, + MAX77620_REGULATOR_ID_LDO8, + MAX77620_NUM_REGS, +}; + +/* Regulator types */ +enum max77620_regulator_type { + MAX77620_REGULATOR_TYPE_SD, + MAX77620_REGULATOR_TYPE_LDO_N, + MAX77620_REGULATOR_TYPE_LDO_P, +}; + +struct max77620_regulator_info { + u8 type; + u8 fps_addr; + u8 volt_addr; + u8 cfg_addr; + u8 power_mode_mask; + u8 power_mode_shift; + u8 remote_sense_addr; + u8 remote_sense_mask; + struct regulator_desc desc; +}; + +struct max77620_regulator_pdata { + int active_fps_src; + int active_fps_pd_slot; + int active_fps_pu_slot; + int suspend_fps_src; + int suspend_fps_pd_slot; + int suspend_fps_pu_slot; + int current_mode; + int power_ok; + int ramp_rate_setting; +}; + +struct max77620_regulator { + struct device *dev; + struct regmap *rmap; + struct max77620_regulator_info *rinfo[MAX77620_NUM_REGS]; + struct max77620_regulator_pdata reg_pdata[MAX77620_NUM_REGS]; + int enable_power_mode[MAX77620_NUM_REGS]; + int current_power_mode[MAX77620_NUM_REGS]; + int active_fps_src[MAX77620_NUM_REGS]; +}; + +#define fps_src_name(fps_src) \ + (fps_src == MAX77620_FPS_SRC_0 ? "FPS_SRC_0" : \ + fps_src == MAX77620_FPS_SRC_1 ? "FPS_SRC_1" : \ + fps_src == MAX77620_FPS_SRC_2 ? "FPS_SRC_2" : "FPS_SRC_NONE") + +static int max77620_regulator_get_fps_src(struct max77620_regulator *pmic, + int id) +{ + struct max77620_regulator_info *rinfo = pmic->rinfo[id]; + unsigned int val; + int ret; + + ret = regmap_read(pmic->rmap, rinfo->fps_addr, &val); + if (ret < 0) { + dev_err(pmic->dev, "Reg 0x%02x read failed %d\n", + rinfo->fps_addr, ret); + return ret; + } + + return (val & MAX77620_FPS_SRC_MASK) >> MAX77620_FPS_SRC_SHIFT; +} + +static int max77620_regulator_set_fps_src(struct max77620_regulator *pmic, + int fps_src, int id) +{ + struct max77620_regulator_info *rinfo = pmic->rinfo[id]; + unsigned int val; + int ret; + + if (!rinfo) + return 0; + + switch (fps_src) { + case MAX77620_FPS_SRC_0: + case MAX77620_FPS_SRC_1: + case MAX77620_FPS_SRC_2: + case MAX77620_FPS_SRC_NONE: + break; + + case MAX77620_FPS_SRC_DEF: + ret = regmap_read(pmic->rmap, rinfo->fps_addr, &val); + if (ret < 0) { + dev_err(pmic->dev, "Reg 0x%02x read failed %d\n", + rinfo->fps_addr, ret); + return ret; + } + ret = (val & MAX77620_FPS_SRC_MASK) >> MAX77620_FPS_SRC_SHIFT; + pmic->active_fps_src[id] = ret; + return 0; + + default: + dev_err(pmic->dev, "Invalid FPS %d for regulator %d\n", + fps_src, id); + return -EINVAL; + } + + ret = regmap_update_bits(pmic->rmap, rinfo->fps_addr, + MAX77620_FPS_SRC_MASK, + fps_src << MAX77620_FPS_SRC_SHIFT); + if (ret < 0) { + dev_err(pmic->dev, "Reg 0x%02x update failed %d\n", + rinfo->fps_addr, ret); + return ret; + } + pmic->active_fps_src[id] = fps_src; + + return 0; +} + +static int max77620_regulator_set_fps_slots(struct max77620_regulator *pmic, + int id, bool is_suspend) +{ + struct max77620_regulator_pdata *rpdata = &pmic->reg_pdata[id]; + struct max77620_regulator_info *rinfo = pmic->rinfo[id]; + unsigned int val = 0; + unsigned int mask = 0; + int pu = rpdata->active_fps_pu_slot; + int pd = rpdata->active_fps_pd_slot; + int ret = 0; + + if (!rinfo) + return 0; + + if (is_suspend) { + pu = rpdata->suspend_fps_pu_slot; + pd = rpdata->suspend_fps_pd_slot; + } + + /* FPS power up period setting */ + if (pu >= 0) { + val |= (pu << MAX77620_FPS_PU_PERIOD_SHIFT); + mask |= MAX77620_FPS_PU_PERIOD_MASK; + } + + /* FPS power down period setting */ + if (pd >= 0) { + val |= (pd << MAX77620_FPS_PD_PERIOD_SHIFT); + mask |= MAX77620_FPS_PD_PERIOD_MASK; + } + + if (mask) { + ret = regmap_update_bits(pmic->rmap, rinfo->fps_addr, + mask, val); + if (ret < 0) { + dev_err(pmic->dev, "Reg 0x%02x update failed: %d\n", + rinfo->fps_addr, ret); + return ret; + } + } + + return ret; +} + +static int max77620_regulator_set_power_mode(struct max77620_regulator *pmic, + int power_mode, int id) +{ + struct max77620_regulator_info *rinfo = pmic->rinfo[id]; + u8 mask = rinfo->power_mode_mask; + u8 shift = rinfo->power_mode_shift; + u8 addr; + int ret; + + switch (rinfo->type) { + case MAX77620_REGULATOR_TYPE_SD: + addr = rinfo->cfg_addr; + break; + default: + addr = rinfo->volt_addr; + break; + } + + ret = regmap_update_bits(pmic->rmap, addr, mask, power_mode << shift); + if (ret < 0) { + dev_err(pmic->dev, "Regulator %d mode set failed: %d\n", + id, ret); + return ret; + } + pmic->current_power_mode[id] = power_mode; + + return ret; +} + +static int max77620_regulator_get_power_mode(struct max77620_regulator *pmic, + int id) +{ + struct max77620_regulator_info *rinfo = pmic->rinfo[id]; + unsigned int val, addr; + u8 mask = rinfo->power_mode_mask; + u8 shift = rinfo->power_mode_shift; + int ret; + + switch (rinfo->type) { + case MAX77620_REGULATOR_TYPE_SD: + addr = rinfo->cfg_addr; + break; + default: + addr = rinfo->volt_addr; + break; + } + + ret = regmap_read(pmic->rmap, addr, &val); + if (ret < 0) { + dev_err(pmic->dev, "Regulator %d: Reg 0x%02x read failed: %d\n", + id, addr, ret); + return ret; + } + + return (val & mask) >> shift; +} + +static int max77620_read_slew_rate(struct max77620_regulator *pmic, int id) +{ + struct max77620_regulator_info *rinfo = pmic->rinfo[id]; + unsigned int rval; + int slew_rate; + int ret; + + ret = regmap_read(pmic->rmap, rinfo->cfg_addr, &rval); + if (ret < 0) { + dev_err(pmic->dev, "Register 0x%02x read failed: %d\n", + rinfo->cfg_addr, ret); + return ret; + } + + switch (rinfo->type) { + case MAX77620_REGULATOR_TYPE_SD: + slew_rate = (rval >> MAX77620_SD_SR_SHIFT) & 0x3; + switch (slew_rate) { + case 0: + slew_rate = 13750; + break; + case 1: + slew_rate = 27500; + break; + case 2: + slew_rate = 55000; + break; + case 3: + slew_rate = 100000; + break; + } + rinfo->desc.ramp_delay = slew_rate; + break; + default: + slew_rate = rval & 0x1; + switch (slew_rate) { + case 0: + slew_rate = 100000; + break; + case 1: + slew_rate = 5000; + break; + } + rinfo->desc.ramp_delay = slew_rate; + break; + } + + return 0; +} + +static int max77620_set_slew_rate(struct max77620_regulator *pmic, int id, + int slew_rate) +{ + struct max77620_regulator_info *rinfo = pmic->rinfo[id]; + unsigned int val; + int ret; + u8 mask; + + if (rinfo->type == MAX77620_REGULATOR_TYPE_SD) { + if (slew_rate <= 13750) + val = 0; + else if (slew_rate <= 27500) + val = 1; + else if (slew_rate <= 55000) + val = 2; + else + val = 3; + val <<= MAX77620_SD_SR_SHIFT; + mask = MAX77620_SD_SR_MASK; + } else { + if (slew_rate <= 5000) + val = 1; + else + val = 0; + mask = MAX77620_LDO_SLEW_RATE_MASK; + } + + ret = regmap_update_bits(pmic->rmap, rinfo->cfg_addr, mask, val); + if (ret < 0) { + dev_err(pmic->dev, "Regulator %d slew rate set failed: %d\n", + id, ret); + return ret; + } + + return 0; +} + +static int max77620_config_power_ok(struct max77620_regulator *pmic, int id) +{ + struct max77620_regulator_pdata *rpdata = &pmic->reg_pdata[id]; + struct max77620_regulator_info *rinfo = pmic->rinfo[id]; + struct max77620_chip *chip = dev_get_drvdata(pmic->dev->parent); + u8 val, mask; + int ret; + + switch (chip->chip_id) { + case MAX20024: + if (rpdata->power_ok >= 0) { + if (rinfo->type == MAX77620_REGULATOR_TYPE_SD) + mask = MAX20024_SD_CFG1_MPOK_MASK; + else + mask = MAX20024_LDO_CFG2_MPOK_MASK; + + val = rpdata->power_ok ? mask : 0; + + ret = regmap_update_bits(pmic->rmap, rinfo->cfg_addr, + mask, val); + if (ret < 0) { + dev_err(pmic->dev, "Reg 0x%02x update failed %d\n", + rinfo->cfg_addr, ret); + return ret; + } + } + break; + + default: + break; + } + + return 0; +} + +static int max77620_init_pmic(struct max77620_regulator *pmic, int id) +{ + struct max77620_regulator_pdata *rpdata = &pmic->reg_pdata[id]; + int ret; + + max77620_config_power_ok(pmic, id); + + /* Update power mode */ + ret = max77620_regulator_get_power_mode(pmic, id); + if (ret < 0) + return ret; + + pmic->current_power_mode[id] = ret; + pmic->enable_power_mode[id] = MAX77620_POWER_MODE_NORMAL; + + if (rpdata->active_fps_src == MAX77620_FPS_SRC_DEF) { + ret = max77620_regulator_get_fps_src(pmic, id); + if (ret < 0) + return ret; + rpdata->active_fps_src = ret; + } + + /* If rails are externally control of FPS then enable it always. */ + if (rpdata->active_fps_src == MAX77620_FPS_SRC_NONE) { + ret = max77620_regulator_set_power_mode(pmic, + pmic->enable_power_mode[id], id); + if (ret < 0) + return ret; + } else { + if (pmic->current_power_mode[id] != + pmic->enable_power_mode[id]) { + ret = max77620_regulator_set_power_mode(pmic, + pmic->enable_power_mode[id], id); + if (ret < 0) + return ret; + } + } + + ret = max77620_regulator_set_fps_src(pmic, rpdata->active_fps_src, id); + if (ret < 0) + return ret; + + ret = max77620_regulator_set_fps_slots(pmic, id, false); + if (ret < 0) + return ret; + + if (rpdata->ramp_rate_setting) { + ret = max77620_set_slew_rate(pmic, id, + rpdata->ramp_rate_setting); + if (ret < 0) + return ret; + } + + return 0; +} + +static int max77620_regulator_enable(struct regulator_dev *rdev) +{ + struct max77620_regulator *pmic = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + + if (pmic->active_fps_src[id] != MAX77620_FPS_SRC_NONE) + return 0; + + return max77620_regulator_set_power_mode(pmic, + pmic->enable_power_mode[id], id); +} + +static int max77620_regulator_disable(struct regulator_dev *rdev) +{ + struct max77620_regulator *pmic = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + + if (pmic->active_fps_src[id] != MAX77620_FPS_SRC_NONE) + return 0; + + return max77620_regulator_set_power_mode(pmic, + MAX77620_POWER_MODE_DISABLE, id); +} + +static int max77620_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct max77620_regulator *pmic = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + int ret = 1; + + if (pmic->active_fps_src[id] != MAX77620_FPS_SRC_NONE) + return 1; + + ret = max77620_regulator_get_power_mode(pmic, id); + if (ret < 0) + return ret; + + if (ret != MAX77620_POWER_MODE_DISABLE) + return 1; + + return 0; +} + +static int max77620_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct max77620_regulator *pmic = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + struct max77620_regulator_info *rinfo = pmic->rinfo[id]; + struct max77620_regulator_pdata *rpdata = &pmic->reg_pdata[id]; + bool fpwm = false; + int power_mode; + int ret; + u8 val; + + switch (mode) { + case REGULATOR_MODE_FAST: + fpwm = true; + power_mode = MAX77620_POWER_MODE_NORMAL; + break; + + case REGULATOR_MODE_NORMAL: + power_mode = MAX77620_POWER_MODE_NORMAL; + break; + + case REGULATOR_MODE_IDLE: + power_mode = MAX77620_POWER_MODE_LPM; + break; + + default: + dev_err(pmic->dev, "Regulator %d mode %d is invalid\n", + id, mode); + return -EINVAL; + } + + if (rinfo->type != MAX77620_REGULATOR_TYPE_SD) + goto skip_fpwm; + + val = (fpwm) ? MAX77620_SD_FPWM_MASK : 0; + ret = regmap_update_bits(pmic->rmap, rinfo->cfg_addr, + MAX77620_SD_FPWM_MASK, val); + if (ret < 0) { + dev_err(pmic->dev, "Reg 0x%02x update failed: %d\n", + rinfo->cfg_addr, ret); + return ret; + } + rpdata->current_mode = mode; + +skip_fpwm: + ret = max77620_regulator_set_power_mode(pmic, power_mode, id); + if (ret < 0) + return ret; + + pmic->enable_power_mode[id] = power_mode; + + return 0; +} + +static unsigned int max77620_regulator_get_mode(struct regulator_dev *rdev) +{ + struct max77620_regulator *pmic = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + struct max77620_regulator_info *rinfo = pmic->rinfo[id]; + int fpwm = 0; + int ret; + int pm_mode, reg_mode; + unsigned int val; + + ret = max77620_regulator_get_power_mode(pmic, id); + if (ret < 0) + return 0; + + pm_mode = ret; + + if (rinfo->type == MAX77620_REGULATOR_TYPE_SD) { + ret = regmap_read(pmic->rmap, rinfo->cfg_addr, &val); + if (ret < 0) { + dev_err(pmic->dev, "Reg 0x%02x read failed: %d\n", + rinfo->cfg_addr, ret); + return ret; + } + fpwm = !!(val & MAX77620_SD_FPWM_MASK); + } + + switch (pm_mode) { + case MAX77620_POWER_MODE_NORMAL: + case MAX77620_POWER_MODE_DISABLE: + if (fpwm) + reg_mode = REGULATOR_MODE_FAST; + else + reg_mode = REGULATOR_MODE_NORMAL; + break; + case MAX77620_POWER_MODE_LPM: + case MAX77620_POWER_MODE_GLPM: + reg_mode = REGULATOR_MODE_IDLE; + break; + default: + return 0; + } + + return reg_mode; +} + +static int max77620_regulator_set_ramp_delay(struct regulator_dev *rdev, + int ramp_delay) +{ + struct max77620_regulator *pmic = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + struct max77620_regulator_pdata *rpdata = &pmic->reg_pdata[id]; + + /* Device specific ramp rate setting tells that platform has + * different ramp rate from advertised value. In this case, + * do not configure anything and just return success. + */ + if (rpdata->ramp_rate_setting) + return 0; + + return max77620_set_slew_rate(pmic, id, ramp_delay); +} + +static int max77620_of_parse_cb(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct max77620_regulator *pmic = config->driver_data; + struct max77620_regulator_pdata *rpdata = &pmic->reg_pdata[desc->id]; + u32 pval; + int ret; + + ret = of_property_read_u32(np, "maxim,active-fps-source", &pval); + rpdata->active_fps_src = (!ret) ? pval : MAX77620_FPS_SRC_DEF; + + ret = of_property_read_u32(np, "maxim,active-fps-power-up-slot", &pval); + rpdata->active_fps_pu_slot = (!ret) ? pval : -1; + + ret = of_property_read_u32( + np, "maxim,active-fps-power-down-slot", &pval); + rpdata->active_fps_pd_slot = (!ret) ? pval : -1; + + ret = of_property_read_u32(np, "maxim,suspend-fps-source", &pval); + rpdata->suspend_fps_src = (!ret) ? pval : -1; + + ret = of_property_read_u32( + np, "maxim,suspend-fps-power-up-slot", &pval); + rpdata->suspend_fps_pu_slot = (!ret) ? pval : -1; + + ret = of_property_read_u32( + np, "maxim,suspend-fps-power-down-slot", &pval); + rpdata->suspend_fps_pd_slot = (!ret) ? pval : -1; + + ret = of_property_read_u32(np, "maxim,power-ok-control", &pval); + if (!ret) + rpdata->power_ok = pval; + else + rpdata->power_ok = -1; + + ret = of_property_read_u32(np, "maxim,ramp-rate-setting", &pval); + rpdata->ramp_rate_setting = (!ret) ? pval : 0; + + return max77620_init_pmic(pmic, desc->id); +} + +static const struct regulator_ops max77620_regulator_ops = { + .is_enabled = max77620_regulator_is_enabled, + .enable = max77620_regulator_enable, + .disable = max77620_regulator_disable, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_mode = max77620_regulator_set_mode, + .get_mode = max77620_regulator_get_mode, + .set_ramp_delay = max77620_regulator_set_ramp_delay, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_active_discharge = regulator_set_active_discharge_regmap, +}; + +#define MAX77620_SD_CNF2_ROVS_EN_NONE 0 +#define RAIL_SD(_id, _name, _sname, _volt_mask, _min_uV, _max_uV, \ + _step_uV, _rs_add, _rs_mask) \ + [MAX77620_REGULATOR_ID_##_id] = { \ + .type = MAX77620_REGULATOR_TYPE_SD, \ + .volt_addr = MAX77620_REG_##_id, \ + .cfg_addr = MAX77620_REG_##_id##_CFG, \ + .fps_addr = MAX77620_REG_FPS_##_id, \ + .remote_sense_addr = _rs_add, \ + .remote_sense_mask = MAX77620_SD_CNF2_ROVS_EN_##_rs_mask, \ + .power_mode_mask = MAX77620_SD_POWER_MODE_MASK, \ + .power_mode_shift = MAX77620_SD_POWER_MODE_SHIFT, \ + .desc = { \ + .name = max77620_rails(_name), \ + .of_match = of_match_ptr(#_name), \ + .regulators_node = of_match_ptr("regulators"), \ + .of_parse_cb = max77620_of_parse_cb, \ + .supply_name = _sname, \ + .id = MAX77620_REGULATOR_ID_##_id, \ + .ops = &max77620_regulator_ops, \ + .n_voltages = ((_max_uV - _min_uV) / _step_uV) + 1, \ + .min_uV = _min_uV, \ + .uV_step = _step_uV, \ + .enable_time = 500, \ + .vsel_mask = MAX77620_##_volt_mask##_VOLT_MASK, \ + .vsel_reg = MAX77620_REG_##_id, \ + .active_discharge_off = 0, \ + .active_discharge_on = MAX77620_SD_CFG1_ADE_ENABLE, \ + .active_discharge_mask = MAX77620_SD_CFG1_ADE_MASK, \ + .active_discharge_reg = MAX77620_REG_##_id##_CFG, \ + .type = REGULATOR_VOLTAGE, \ + }, \ + } + +#define RAIL_LDO(_id, _name, _sname, _type, _min_uV, _max_uV, _step_uV) \ + [MAX77620_REGULATOR_ID_##_id] = { \ + .type = MAX77620_REGULATOR_TYPE_LDO_##_type, \ + .volt_addr = MAX77620_REG_##_id##_CFG, \ + .cfg_addr = MAX77620_REG_##_id##_CFG2, \ + .fps_addr = MAX77620_REG_FPS_##_id, \ + .remote_sense_addr = 0xFF, \ + .power_mode_mask = MAX77620_LDO_POWER_MODE_MASK, \ + .power_mode_shift = MAX77620_LDO_POWER_MODE_SHIFT, \ + .desc = { \ + .name = max77620_rails(_name), \ + .of_match = of_match_ptr(#_name), \ + .regulators_node = of_match_ptr("regulators"), \ + .of_parse_cb = max77620_of_parse_cb, \ + .supply_name = _sname, \ + .id = MAX77620_REGULATOR_ID_##_id, \ + .ops = &max77620_regulator_ops, \ + .n_voltages = ((_max_uV - _min_uV) / _step_uV) + 1, \ + .min_uV = _min_uV, \ + .uV_step = _step_uV, \ + .enable_time = 500, \ + .vsel_mask = MAX77620_LDO_VOLT_MASK, \ + .vsel_reg = MAX77620_REG_##_id##_CFG, \ + .active_discharge_off = 0, \ + .active_discharge_on = MAX77620_LDO_CFG2_ADE_ENABLE, \ + .active_discharge_mask = MAX77620_LDO_CFG2_ADE_MASK, \ + .active_discharge_reg = MAX77620_REG_##_id##_CFG2, \ + .type = REGULATOR_VOLTAGE, \ + }, \ + } + +static struct max77620_regulator_info max77620_regs_info[MAX77620_NUM_REGS] = { + RAIL_SD(SD0, sd0, "in-sd0", SD0, 600000, 1400000, 12500, 0x22, SD0), + RAIL_SD(SD1, sd1, "in-sd1", SD1, 600000, 1550000, 12500, 0x22, SD1), + RAIL_SD(SD2, sd2, "in-sd2", SDX, 600000, 3787500, 12500, 0xFF, NONE), + RAIL_SD(SD3, sd3, "in-sd3", SDX, 600000, 3787500, 12500, 0xFF, NONE), + + RAIL_LDO(LDO0, ldo0, "in-ldo0-1", N, 800000, 2375000, 25000), + RAIL_LDO(LDO1, ldo1, "in-ldo0-1", N, 800000, 2375000, 25000), + RAIL_LDO(LDO2, ldo2, "in-ldo2", P, 800000, 3950000, 50000), + RAIL_LDO(LDO3, ldo3, "in-ldo3-5", P, 800000, 3950000, 50000), + RAIL_LDO(LDO4, ldo4, "in-ldo4-6", P, 800000, 1587500, 12500), + RAIL_LDO(LDO5, ldo5, "in-ldo3-5", P, 800000, 3950000, 50000), + RAIL_LDO(LDO6, ldo6, "in-ldo4-6", P, 800000, 3950000, 50000), + RAIL_LDO(LDO7, ldo7, "in-ldo7-8", N, 800000, 3950000, 50000), + RAIL_LDO(LDO8, ldo8, "in-ldo7-8", N, 800000, 3950000, 50000), +}; + +static struct max77620_regulator_info max20024_regs_info[MAX77620_NUM_REGS] = { + RAIL_SD(SD0, sd0, "in-sd0", SD0, 800000, 1587500, 12500, 0x22, SD0), + RAIL_SD(SD1, sd1, "in-sd1", SD1, 600000, 3387500, 12500, 0x22, SD1), + RAIL_SD(SD2, sd2, "in-sd2", SDX, 600000, 3787500, 12500, 0xFF, NONE), + RAIL_SD(SD3, sd3, "in-sd3", SDX, 600000, 3787500, 12500, 0xFF, NONE), + RAIL_SD(SD4, sd4, "in-sd4", SDX, 600000, 3787500, 12500, 0xFF, NONE), + + RAIL_LDO(LDO0, ldo0, "in-ldo0-1", N, 800000, 2375000, 25000), + RAIL_LDO(LDO1, ldo1, "in-ldo0-1", N, 800000, 2375000, 25000), + RAIL_LDO(LDO2, ldo2, "in-ldo2", P, 800000, 3950000, 50000), + RAIL_LDO(LDO3, ldo3, "in-ldo3-5", P, 800000, 3950000, 50000), + RAIL_LDO(LDO4, ldo4, "in-ldo4-6", P, 800000, 1587500, 12500), + RAIL_LDO(LDO5, ldo5, "in-ldo3-5", P, 800000, 3950000, 50000), + RAIL_LDO(LDO6, ldo6, "in-ldo4-6", P, 800000, 3950000, 50000), + RAIL_LDO(LDO7, ldo7, "in-ldo7-8", N, 800000, 3950000, 50000), + RAIL_LDO(LDO8, ldo8, "in-ldo7-8", N, 800000, 3950000, 50000), +}; + +static int max77620_regulator_probe(struct platform_device *pdev) +{ + struct max77620_chip *max77620_chip = dev_get_drvdata(pdev->dev.parent); + struct max77620_regulator_info *rinfo; + struct device *dev = &pdev->dev; + struct regulator_config config = { }; + struct max77620_regulator *pmic; + int ret = 0; + int id; + + pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + platform_set_drvdata(pdev, pmic); + pmic->dev = dev; + pmic->rmap = max77620_chip->rmap; + if (!dev->of_node) + dev->of_node = pdev->dev.parent->of_node; + + switch (max77620_chip->chip_id) { + case MAX77620: + rinfo = max77620_regs_info; + break; + default: + rinfo = max20024_regs_info; + break; + } + + config.regmap = pmic->rmap; + config.dev = dev; + config.driver_data = pmic; + + /* + * Set of_node_reuse flag to prevent driver core from attempting to + * claim any pinmux resources already claimed by the parent device. + * Otherwise PMIC driver will fail to re-probe. + */ + device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent); + + for (id = 0; id < MAX77620_NUM_REGS; id++) { + struct regulator_dev *rdev; + struct regulator_desc *rdesc; + + if ((max77620_chip->chip_id == MAX77620) && + (id == MAX77620_REGULATOR_ID_SD4)) + continue; + + rdesc = &rinfo[id].desc; + pmic->rinfo[id] = &max77620_regs_info[id]; + pmic->enable_power_mode[id] = MAX77620_POWER_MODE_NORMAL; + pmic->reg_pdata[id].active_fps_src = -1; + pmic->reg_pdata[id].active_fps_pd_slot = -1; + pmic->reg_pdata[id].active_fps_pu_slot = -1; + pmic->reg_pdata[id].suspend_fps_src = -1; + pmic->reg_pdata[id].suspend_fps_pd_slot = -1; + pmic->reg_pdata[id].suspend_fps_pu_slot = -1; + pmic->reg_pdata[id].power_ok = -1; + pmic->reg_pdata[id].ramp_rate_setting = -1; + + ret = max77620_read_slew_rate(pmic, id); + if (ret < 0) + return ret; + + rdev = devm_regulator_register(dev, rdesc, &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(dev, "Regulator registration %s failed: %d\n", + rdesc->name, ret); + return ret; + } + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max77620_regulator_suspend(struct device *dev) +{ + struct max77620_regulator *pmic = dev_get_drvdata(dev); + struct max77620_regulator_pdata *reg_pdata; + int id; + + for (id = 0; id < MAX77620_NUM_REGS; id++) { + reg_pdata = &pmic->reg_pdata[id]; + + max77620_regulator_set_fps_slots(pmic, id, true); + if (reg_pdata->suspend_fps_src < 0) + continue; + + max77620_regulator_set_fps_src(pmic, reg_pdata->suspend_fps_src, + id); + } + + return 0; +} + +static int max77620_regulator_resume(struct device *dev) +{ + struct max77620_regulator *pmic = dev_get_drvdata(dev); + struct max77620_regulator_pdata *reg_pdata; + int id; + + for (id = 0; id < MAX77620_NUM_REGS; id++) { + reg_pdata = &pmic->reg_pdata[id]; + + max77620_config_power_ok(pmic, id); + + max77620_regulator_set_fps_slots(pmic, id, false); + if (reg_pdata->active_fps_src < 0) + continue; + max77620_regulator_set_fps_src(pmic, reg_pdata->active_fps_src, + id); + } + + return 0; +} +#endif + +static const struct dev_pm_ops max77620_regulator_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(max77620_regulator_suspend, + max77620_regulator_resume) +}; + +static const struct platform_device_id max77620_regulator_devtype[] = { + { .name = "max77620-pmic", }, + { .name = "max20024-pmic", }, + {}, +}; +MODULE_DEVICE_TABLE(platform, max77620_regulator_devtype); + +static struct platform_driver max77620_regulator_driver = { + .probe = max77620_regulator_probe, + .id_table = max77620_regulator_devtype, + .driver = { + .name = "max77620-pmic", + .pm = &max77620_regulator_pm_ops, + }, +}; + +module_platform_driver(max77620_regulator_driver); + +MODULE_DESCRIPTION("MAX77620/MAX20024 regulator driver"); +MODULE_AUTHOR("Mallikarjun Kasoju <mkasoju@nvidia.com>"); +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/max77686-regulator.c b/drivers/regulator/max77686-regulator.c new file mode 100644 index 000000000..bee060937 --- /dev/null +++ b/drivers/regulator/max77686-regulator.c @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// max77686.c - Regulator driver for the Maxim 77686 +// +// Copyright (C) 2012 Samsung Electronics +// Chiwoong Byun <woong.byun@samsung.com> +// Jonghwa Lee <jonghwa3.lee@samsung.com> +// +// This driver is based on max8997.c + +#include <linux/kernel.h> +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/max77686.h> +#include <linux/mfd/max77686-private.h> + +#define MAX77686_LDO_MINUV 800000 +#define MAX77686_LDO_UVSTEP 50000 +#define MAX77686_LDO_LOW_MINUV 800000 +#define MAX77686_LDO_LOW_UVSTEP 25000 +#define MAX77686_BUCK_MINUV 750000 +#define MAX77686_BUCK_UVSTEP 50000 +#define MAX77686_BUCK_ENABLE_TIME 40 /* us */ +#define MAX77686_DVS_ENABLE_TIME 22 /* us */ +#define MAX77686_RAMP_DELAY 100000 /* uV/us */ +#define MAX77686_DVS_RAMP_DELAY 27500 /* uV/us */ +#define MAX77686_DVS_MINUV 600000 +#define MAX77686_DVS_UVSTEP 12500 + +/* + * Value for configuring buck[89] and LDO{20,21,22} as GPIO control. + * It is the same as 'off' for other regulators. + */ +#define MAX77686_GPIO_CONTROL 0x0 +/* + * Values used for configuring LDOs and bucks. + * Forcing low power mode: LDO1, 3-5, 9, 13, 17-26 + */ +#define MAX77686_LDO_LOWPOWER 0x1 +/* + * On/off controlled by PWRREQ: + * - LDO2, 6-8, 10-12, 14-16 + * - buck[1234] + */ +#define MAX77686_OFF_PWRREQ 0x1 +/* Low power mode controlled by PWRREQ: All LDOs */ +#define MAX77686_LDO_LOWPOWER_PWRREQ 0x2 +/* Forcing low power mode: buck[234] */ +#define MAX77686_BUCK_LOWPOWER 0x2 +#define MAX77686_NORMAL 0x3 + +#define MAX77686_OPMODE_SHIFT 6 +#define MAX77686_OPMODE_BUCK234_SHIFT 4 +#define MAX77686_OPMODE_MASK 0x3 + +#define MAX77686_VSEL_MASK 0x3F +#define MAX77686_DVS_VSEL_MASK 0xFF + +#define MAX77686_RAMP_RATE_MASK 0xC0 + +#define MAX77686_REGULATORS MAX77686_REG_MAX +#define MAX77686_LDOS 26 + +enum max77686_ramp_rate { + RAMP_RATE_13P75MV, + RAMP_RATE_27P5MV, + RAMP_RATE_55MV, + RAMP_RATE_NO_CTRL, /* 100mV/us */ +}; + +struct max77686_data { + DECLARE_BITMAP(gpio_enabled, MAX77686_REGULATORS); + + /* Array indexed by regulator id */ + unsigned int opmode[MAX77686_REGULATORS]; +}; + +static unsigned int max77686_get_opmode_shift(int id) +{ + switch (id) { + case MAX77686_BUCK1: + case MAX77686_BUCK5 ... MAX77686_BUCK9: + return 0; + case MAX77686_BUCK2 ... MAX77686_BUCK4: + return MAX77686_OPMODE_BUCK234_SHIFT; + default: + /* all LDOs */ + return MAX77686_OPMODE_SHIFT; + } +} + +/* + * When regulator is configured for GPIO control then it + * replaces "normal" mode. Any change from low power mode to normal + * should actually change to GPIO control. + * Map normal mode to proper value for such regulators. + */ +static unsigned int max77686_map_normal_mode(struct max77686_data *max77686, + int id) +{ + switch (id) { + case MAX77686_BUCK8: + case MAX77686_BUCK9: + case MAX77686_LDO20 ... MAX77686_LDO22: + if (test_bit(id, max77686->gpio_enabled)) + return MAX77686_GPIO_CONTROL; + } + + return MAX77686_NORMAL; +} + +/* Some BUCKs and LDOs supports Normal[ON/OFF] mode during suspend */ +static int max77686_set_suspend_disable(struct regulator_dev *rdev) +{ + unsigned int val, shift; + struct max77686_data *max77686 = rdev_get_drvdata(rdev); + int ret, id = rdev_get_id(rdev); + + shift = max77686_get_opmode_shift(id); + val = MAX77686_OFF_PWRREQ; + + ret = regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, val << shift); + if (ret) + return ret; + + max77686->opmode[id] = val; + return 0; +} + +/* Some LDOs supports [LPM/Normal]ON mode during suspend state */ +static int max77686_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct max77686_data *max77686 = rdev_get_drvdata(rdev); + unsigned int val; + int ret, id = rdev_get_id(rdev); + + /* BUCK[5-9] doesn't support this feature */ + if (id >= MAX77686_BUCK5) + return 0; + + switch (mode) { + case REGULATOR_MODE_IDLE: /* ON in LP Mode */ + val = MAX77686_LDO_LOWPOWER_PWRREQ; + break; + case REGULATOR_MODE_NORMAL: /* ON in Normal Mode */ + val = max77686_map_normal_mode(max77686, id); + break; + default: + pr_warn("%s: regulator_suspend_mode : 0x%x not supported\n", + rdev->desc->name, mode); + return -EINVAL; + } + + ret = regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, + val << MAX77686_OPMODE_SHIFT); + if (ret) + return ret; + + max77686->opmode[id] = val; + return 0; +} + +/* Some LDOs supports LPM-ON/OFF/Normal-ON mode during suspend state */ +static int max77686_ldo_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + unsigned int val; + struct max77686_data *max77686 = rdev_get_drvdata(rdev); + int ret, id = rdev_get_id(rdev); + + switch (mode) { + case REGULATOR_MODE_STANDBY: /* switch off */ + val = MAX77686_OFF_PWRREQ; + break; + case REGULATOR_MODE_IDLE: /* ON in LP Mode */ + val = MAX77686_LDO_LOWPOWER_PWRREQ; + break; + case REGULATOR_MODE_NORMAL: /* ON in Normal Mode */ + val = max77686_map_normal_mode(max77686, id); + break; + default: + pr_warn("%s: regulator_suspend_mode : 0x%x not supported\n", + rdev->desc->name, mode); + return -EINVAL; + } + + ret = regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, + val << MAX77686_OPMODE_SHIFT); + if (ret) + return ret; + + max77686->opmode[id] = val; + return 0; +} + +static int max77686_enable(struct regulator_dev *rdev) +{ + struct max77686_data *max77686 = rdev_get_drvdata(rdev); + unsigned int shift; + int id = rdev_get_id(rdev); + + shift = max77686_get_opmode_shift(id); + + if (max77686->opmode[id] == MAX77686_OFF_PWRREQ) + max77686->opmode[id] = max77686_map_normal_mode(max77686, id); + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, + max77686->opmode[id] << shift); +} + +static int max77686_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay) +{ + unsigned int ramp_value = RAMP_RATE_NO_CTRL; + + switch (ramp_delay) { + case 1 ... 13750: + ramp_value = RAMP_RATE_13P75MV; + break; + case 13751 ... 27500: + ramp_value = RAMP_RATE_27P5MV; + break; + case 27501 ... 55000: + ramp_value = RAMP_RATE_55MV; + break; + case 55001 ... 100000: + break; + default: + pr_warn("%s: ramp_delay: %d not supported, setting 100000\n", + rdev->desc->name, ramp_delay); + } + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + MAX77686_RAMP_RATE_MASK, ramp_value << 6); +} + +static int max77686_of_parse_cb(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct max77686_data *max77686 = config->driver_data; + + switch (desc->id) { + case MAX77686_BUCK8: + case MAX77686_BUCK9: + case MAX77686_LDO20 ... MAX77686_LDO22: + config->ena_gpio = of_get_named_gpio(np, + "maxim,ena-gpios", 0); + config->ena_gpio_flags = GPIOF_OUT_INIT_HIGH; + config->ena_gpio_initialized = true; + break; + default: + return 0; + } + + if (gpio_is_valid(config->ena_gpio)) { + set_bit(desc->id, max77686->gpio_enabled); + + return regmap_update_bits(config->regmap, desc->enable_reg, + desc->enable_mask, + MAX77686_GPIO_CONTROL); + } + + return 0; +} + +static const struct regulator_ops max77686_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77686_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_mode = max77686_set_suspend_mode, +}; + +static const struct regulator_ops max77686_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77686_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_mode = max77686_ldo_set_suspend_mode, + .set_suspend_disable = max77686_set_suspend_disable, +}; + +static const struct regulator_ops max77686_buck1_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77686_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_disable = max77686_set_suspend_disable, +}; + +static const struct regulator_ops max77686_buck_dvs_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77686_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = max77686_set_ramp_delay, + .set_suspend_disable = max77686_set_suspend_disable, +}; + +#define regulator_desc_ldo(num) { \ + .name = "LDO"#num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("voltage-regulators"), \ + .of_parse_cb = max77686_of_parse_cb, \ + .id = MAX77686_LDO##num, \ + .ops = &max77686_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MAX77686_LDO_MINUV, \ + .uV_step = MAX77686_LDO_UVSTEP, \ + .ramp_delay = MAX77686_RAMP_DELAY, \ + .n_voltages = MAX77686_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77686_VSEL_MASK, \ + .enable_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77686_OPMODE_MASK \ + << MAX77686_OPMODE_SHIFT, \ +} +#define regulator_desc_lpm_ldo(num) { \ + .name = "LDO"#num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("voltage-regulators"), \ + .id = MAX77686_LDO##num, \ + .ops = &max77686_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MAX77686_LDO_MINUV, \ + .uV_step = MAX77686_LDO_UVSTEP, \ + .ramp_delay = MAX77686_RAMP_DELAY, \ + .n_voltages = MAX77686_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77686_VSEL_MASK, \ + .enable_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77686_OPMODE_MASK \ + << MAX77686_OPMODE_SHIFT, \ +} +#define regulator_desc_ldo_low(num) { \ + .name = "LDO"#num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("voltage-regulators"), \ + .id = MAX77686_LDO##num, \ + .ops = &max77686_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MAX77686_LDO_LOW_MINUV, \ + .uV_step = MAX77686_LDO_LOW_UVSTEP, \ + .ramp_delay = MAX77686_RAMP_DELAY, \ + .n_voltages = MAX77686_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77686_VSEL_MASK, \ + .enable_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77686_OPMODE_MASK \ + << MAX77686_OPMODE_SHIFT, \ +} +#define regulator_desc_ldo1_low(num) { \ + .name = "LDO"#num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("voltage-regulators"), \ + .id = MAX77686_LDO##num, \ + .ops = &max77686_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MAX77686_LDO_LOW_MINUV, \ + .uV_step = MAX77686_LDO_LOW_UVSTEP, \ + .ramp_delay = MAX77686_RAMP_DELAY, \ + .n_voltages = MAX77686_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77686_VSEL_MASK, \ + .enable_reg = MAX77686_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77686_OPMODE_MASK \ + << MAX77686_OPMODE_SHIFT, \ +} +#define regulator_desc_buck(num) { \ + .name = "BUCK"#num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("voltage-regulators"), \ + .of_parse_cb = max77686_of_parse_cb, \ + .id = MAX77686_BUCK##num, \ + .ops = &max77686_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MAX77686_BUCK_MINUV, \ + .uV_step = MAX77686_BUCK_UVSTEP, \ + .ramp_delay = MAX77686_RAMP_DELAY, \ + .enable_time = MAX77686_BUCK_ENABLE_TIME, \ + .n_voltages = MAX77686_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_BUCK5OUT + (num - 5) * 2, \ + .vsel_mask = MAX77686_VSEL_MASK, \ + .enable_reg = MAX77686_REG_BUCK5CTRL + (num - 5) * 2, \ + .enable_mask = MAX77686_OPMODE_MASK, \ +} +#define regulator_desc_buck1(num) { \ + .name = "BUCK"#num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("voltage-regulators"), \ + .id = MAX77686_BUCK##num, \ + .ops = &max77686_buck1_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MAX77686_BUCK_MINUV, \ + .uV_step = MAX77686_BUCK_UVSTEP, \ + .ramp_delay = MAX77686_RAMP_DELAY, \ + .enable_time = MAX77686_BUCK_ENABLE_TIME, \ + .n_voltages = MAX77686_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_BUCK1OUT, \ + .vsel_mask = MAX77686_VSEL_MASK, \ + .enable_reg = MAX77686_REG_BUCK1CTRL, \ + .enable_mask = MAX77686_OPMODE_MASK, \ +} +#define regulator_desc_buck_dvs(num) { \ + .name = "BUCK"#num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("voltage-regulators"), \ + .id = MAX77686_BUCK##num, \ + .ops = &max77686_buck_dvs_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MAX77686_DVS_MINUV, \ + .uV_step = MAX77686_DVS_UVSTEP, \ + .ramp_delay = MAX77686_DVS_RAMP_DELAY, \ + .enable_time = MAX77686_DVS_ENABLE_TIME, \ + .n_voltages = MAX77686_DVS_VSEL_MASK + 1, \ + .vsel_reg = MAX77686_REG_BUCK2DVS1 + (num - 2) * 10, \ + .vsel_mask = MAX77686_DVS_VSEL_MASK, \ + .enable_reg = MAX77686_REG_BUCK2CTRL1 + (num - 2) * 10, \ + .enable_mask = MAX77686_OPMODE_MASK \ + << MAX77686_OPMODE_BUCK234_SHIFT, \ +} + +static const struct regulator_desc regulators[] = { + regulator_desc_ldo1_low(1), + regulator_desc_ldo_low(2), + regulator_desc_ldo(3), + regulator_desc_ldo(4), + regulator_desc_ldo(5), + regulator_desc_ldo_low(6), + regulator_desc_ldo_low(7), + regulator_desc_ldo_low(8), + regulator_desc_ldo(9), + regulator_desc_lpm_ldo(10), + regulator_desc_lpm_ldo(11), + regulator_desc_lpm_ldo(12), + regulator_desc_ldo(13), + regulator_desc_lpm_ldo(14), + regulator_desc_ldo_low(15), + regulator_desc_lpm_ldo(16), + regulator_desc_ldo(17), + regulator_desc_ldo(18), + regulator_desc_ldo(19), + regulator_desc_ldo(20), + regulator_desc_ldo(21), + regulator_desc_ldo(22), + regulator_desc_ldo(23), + regulator_desc_ldo(24), + regulator_desc_ldo(25), + regulator_desc_ldo(26), + regulator_desc_buck1(1), + regulator_desc_buck_dvs(2), + regulator_desc_buck_dvs(3), + regulator_desc_buck_dvs(4), + regulator_desc_buck(5), + regulator_desc_buck(6), + regulator_desc_buck(7), + regulator_desc_buck(8), + regulator_desc_buck(9), +}; + +static int max77686_pmic_probe(struct platform_device *pdev) +{ + struct max77686_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max77686_data *max77686; + int i; + struct regulator_config config = { }; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + max77686 = devm_kzalloc(&pdev->dev, sizeof(struct max77686_data), + GFP_KERNEL); + if (!max77686) + return -ENOMEM; + + config.dev = iodev->dev; + config.regmap = iodev->regmap; + config.driver_data = max77686; + platform_set_drvdata(pdev, max77686); + + for (i = 0; i < MAX77686_REGULATORS; i++) { + struct regulator_dev *rdev; + int id = regulators[i].id; + + max77686->opmode[id] = MAX77686_NORMAL; + rdev = devm_regulator_register(&pdev->dev, + ®ulators[i], &config); + if (IS_ERR(rdev)) { + int ret = PTR_ERR(rdev); + dev_err(&pdev->dev, + "regulator init failed for %d: %d\n", i, ret); + return ret; + } + } + + return 0; +} + +static const struct platform_device_id max77686_pmic_id[] = { + {"max77686-pmic", 0}, + { }, +}; +MODULE_DEVICE_TABLE(platform, max77686_pmic_id); + +static struct platform_driver max77686_pmic_driver = { + .driver = { + .name = "max77686-pmic", + }, + .probe = max77686_pmic_probe, + .id_table = max77686_pmic_id, +}; + +module_platform_driver(max77686_pmic_driver); + +MODULE_DESCRIPTION("MAXIM 77686 Regulator Driver"); +MODULE_AUTHOR("Chiwoong Byun <woong.byun@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/max77693-regulator.c b/drivers/regulator/max77693-regulator.c new file mode 100644 index 000000000..077ecbbfd --- /dev/null +++ b/drivers/regulator/max77693-regulator.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// max77693.c - Regulator driver for the Maxim 77693 and 77843 +// +// Copyright (C) 2013-2015 Samsung Electronics +// Jonghwa Lee <jonghwa3.lee@samsung.com> +// Krzysztof Kozlowski <krzk@kernel.org> +// +// This driver is based on max77686.c + +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/export.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/max77693.h> +#include <linux/mfd/max77693-common.h> +#include <linux/mfd/max77693-private.h> +#include <linux/mfd/max77843-private.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regmap.h> + +/* + * ID for MAX77843 regulators. + * There is no need for such for MAX77693. + */ +enum max77843_regulator_type { + MAX77843_SAFEOUT1 = 0, + MAX77843_SAFEOUT2, + MAX77843_CHARGER, + + MAX77843_NUM, +}; + +/* Register differences between chargers: MAX77693 and MAX77843 */ +struct chg_reg_data { + unsigned int linear_reg; + unsigned int linear_mask; + unsigned int uA_step; + unsigned int min_sel; +}; + +/* + * MAX77693 CHARGER regulator - Min : 20mA, Max : 2580mA, step : 20mA + * 0x00, 0x01, 0x2, 0x03 = 60 mA + * 0x04 ~ 0x7E = (60 + (X - 3) * 20) mA + * Actually for MAX77693 the driver manipulates the maximum input current, + * not the fast charge current (output). This should be fixed. + * + * On MAX77843 the calculation formula is the same (except values). + * Fortunately it properly manipulates the fast charge current. + */ +static int max77693_chg_get_current_limit(struct regulator_dev *rdev) +{ + const struct chg_reg_data *reg_data = rdev_get_drvdata(rdev); + unsigned int chg_min_uA = rdev->constraints->min_uA; + unsigned int chg_max_uA = rdev->constraints->max_uA; + unsigned int reg, sel; + unsigned int val; + int ret; + + ret = regmap_read(rdev->regmap, reg_data->linear_reg, ®); + if (ret < 0) + return ret; + + sel = reg & reg_data->linear_mask; + + /* the first four codes for charger current are all 60mA */ + if (sel <= reg_data->min_sel) + sel = 0; + else + sel -= reg_data->min_sel; + + val = chg_min_uA + reg_data->uA_step * sel; + if (val > chg_max_uA) + return -EINVAL; + + return val; +} + +static int max77693_chg_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + const struct chg_reg_data *reg_data = rdev_get_drvdata(rdev); + unsigned int chg_min_uA = rdev->constraints->min_uA; + int sel = 0; + + while (chg_min_uA + reg_data->uA_step * sel < min_uA) + sel++; + + if (chg_min_uA + reg_data->uA_step * sel > max_uA) + return -EINVAL; + + /* the first four codes for charger current are all 60mA */ + sel += reg_data->min_sel; + + return regmap_write(rdev->regmap, reg_data->linear_reg, sel); +} +/* end of CHARGER regulator ops */ + +/* Returns regmap suitable for given regulator on chosen device */ +static struct regmap *max77693_get_regmap(enum max77693_types type, + struct max77693_dev *max77693, + int reg_id) +{ + if (type == TYPE_MAX77693) + return max77693->regmap; + + /* Else: TYPE_MAX77843 */ + switch (reg_id) { + case MAX77843_SAFEOUT1: + case MAX77843_SAFEOUT2: + return max77693->regmap; + case MAX77843_CHARGER: + return max77693->regmap_chg; + default: + return max77693->regmap; + } +} + +static const unsigned int max77693_safeout_table[] = { + 4850000, + 4900000, + 4950000, + 3300000, +}; + +static const struct regulator_ops max77693_safeout_ops = { + .list_voltage = regulator_list_voltage_table, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_ops max77693_charger_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_current_limit = max77693_chg_get_current_limit, + .set_current_limit = max77693_chg_set_current_limit, +}; + +#define max77693_regulator_desc_esafeout(_num) { \ + .name = "ESAFEOUT"#_num, \ + .id = MAX77693_ESAFEOUT##_num, \ + .of_match = of_match_ptr("ESAFEOUT"#_num), \ + .regulators_node = of_match_ptr("regulators"), \ + .n_voltages = 4, \ + .ops = &max77693_safeout_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .volt_table = max77693_safeout_table, \ + .vsel_reg = MAX77693_CHG_REG_SAFEOUT_CTRL, \ + .vsel_mask = SAFEOUT_CTRL_SAFEOUT##_num##_MASK, \ + .enable_reg = MAX77693_CHG_REG_SAFEOUT_CTRL, \ + .enable_mask = SAFEOUT_CTRL_ENSAFEOUT##_num##_MASK , \ +} + +static const struct regulator_desc max77693_supported_regulators[] = { + max77693_regulator_desc_esafeout(1), + max77693_regulator_desc_esafeout(2), + { + .name = "CHARGER", + .id = MAX77693_CHARGER, + .of_match = of_match_ptr("CHARGER"), + .regulators_node = of_match_ptr("regulators"), + .ops = &max77693_charger_ops, + .type = REGULATOR_CURRENT, + .owner = THIS_MODULE, + .enable_reg = MAX77693_CHG_REG_CHG_CNFG_00, + .enable_mask = CHG_CNFG_00_CHG_MASK | + CHG_CNFG_00_BUCK_MASK, + .enable_val = CHG_CNFG_00_CHG_MASK | CHG_CNFG_00_BUCK_MASK, + }, +}; + +static const struct chg_reg_data max77693_chg_reg_data = { + .linear_reg = MAX77693_CHG_REG_CHG_CNFG_09, + .linear_mask = CHG_CNFG_09_CHGIN_ILIM_MASK, + .uA_step = 20000, + .min_sel = 3, +}; + +#define max77843_regulator_desc_esafeout(num) { \ + .name = "SAFEOUT" # num, \ + .id = MAX77843_SAFEOUT ## num, \ + .ops = &max77693_safeout_ops, \ + .of_match = of_match_ptr("SAFEOUT" # num), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(max77693_safeout_table), \ + .volt_table = max77693_safeout_table, \ + .enable_reg = MAX77843_SYS_REG_SAFEOUTCTRL, \ + .enable_mask = MAX77843_REG_SAFEOUTCTRL_ENSAFEOUT ## num, \ + .vsel_reg = MAX77843_SYS_REG_SAFEOUTCTRL, \ + .vsel_mask = MAX77843_REG_SAFEOUTCTRL_SAFEOUT ## num ## _MASK, \ +} + +static const struct regulator_desc max77843_supported_regulators[] = { + [MAX77843_SAFEOUT1] = max77843_regulator_desc_esafeout(1), + [MAX77843_SAFEOUT2] = max77843_regulator_desc_esafeout(2), + [MAX77843_CHARGER] = { + .name = "CHARGER", + .id = MAX77843_CHARGER, + .ops = &max77693_charger_ops, + .of_match = of_match_ptr("CHARGER"), + .regulators_node = of_match_ptr("regulators"), + .type = REGULATOR_CURRENT, + .owner = THIS_MODULE, + .enable_reg = MAX77843_CHG_REG_CHG_CNFG_00, + .enable_mask = MAX77843_CHG_MASK, + .enable_val = MAX77843_CHG_MASK, + }, +}; + +static const struct chg_reg_data max77843_chg_reg_data = { + .linear_reg = MAX77843_CHG_REG_CHG_CNFG_02, + .linear_mask = MAX77843_CHG_FAST_CHG_CURRENT_MASK, + .uA_step = MAX77843_CHG_FAST_CHG_CURRENT_STEP, + .min_sel = 2, +}; + +static int max77693_pmic_probe(struct platform_device *pdev) +{ + enum max77693_types type = platform_get_device_id(pdev)->driver_data; + struct max77693_dev *iodev = dev_get_drvdata(pdev->dev.parent); + const struct regulator_desc *regulators; + unsigned int regulators_size; + int i; + struct regulator_config config = { }; + + config.dev = iodev->dev; + + switch (type) { + case TYPE_MAX77693: + regulators = max77693_supported_regulators; + regulators_size = ARRAY_SIZE(max77693_supported_regulators); + config.driver_data = (void *)&max77693_chg_reg_data; + break; + case TYPE_MAX77843: + regulators = max77843_supported_regulators; + regulators_size = ARRAY_SIZE(max77843_supported_regulators); + config.driver_data = (void *)&max77843_chg_reg_data; + break; + default: + dev_err(&pdev->dev, "Unsupported device type: %u\n", type); + return -ENODEV; + } + + for (i = 0; i < regulators_size; i++) { + struct regulator_dev *rdev; + + config.regmap = max77693_get_regmap(type, iodev, + regulators[i].id); + + rdev = devm_regulator_register(&pdev->dev, + ®ulators[i], &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, + "Failed to initialize regulator-%d\n", i); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct platform_device_id max77693_pmic_id[] = { + { "max77693-pmic", TYPE_MAX77693 }, + { "max77843-regulator", TYPE_MAX77843 }, + {}, +}; + +MODULE_DEVICE_TABLE(platform, max77693_pmic_id); + +static struct platform_driver max77693_pmic_driver = { + .driver = { + .name = "max77693-pmic", + }, + .probe = max77693_pmic_probe, + .id_table = max77693_pmic_id, +}; + +static int __init max77693_pmic_init(void) +{ + return platform_driver_register(&max77693_pmic_driver); +} +subsys_initcall(max77693_pmic_init); + +static void __exit max77693_pmic_cleanup(void) +{ + platform_driver_unregister(&max77693_pmic_driver); +} +module_exit(max77693_pmic_cleanup); + +MODULE_DESCRIPTION("MAXIM 77693/77843 regulator driver"); +MODULE_AUTHOR("Jonghwa Lee <jonghwa3.lee@samsung.com>"); +MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/max77802-regulator.c b/drivers/regulator/max77802-regulator.c new file mode 100644 index 000000000..c30cf5c9f --- /dev/null +++ b/drivers/regulator/max77802-regulator.c @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// max77802.c - Regulator driver for the Maxim 77802 +// +// Copyright (C) 2013-2014 Google, Inc +// Simon Glass <sjg@chromium.org> +// +// Copyright (C) 2012 Samsung Electronics +// Chiwoong Byun <woong.byun@samsung.com> +// Jonghwa Lee <jonghwa3.lee@samsung.com> +// +// This driver is based on max8997.c + +#include <linux/kernel.h> +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/max77686.h> +#include <linux/mfd/max77686-private.h> +#include <dt-bindings/regulator/maxim,max77802.h> + +/* Default ramp delay in case it is not manually set */ +#define MAX77802_RAMP_DELAY 100000 /* uV/us */ + +#define MAX77802_OPMODE_SHIFT_LDO 6 +#define MAX77802_OPMODE_BUCK234_SHIFT 4 +#define MAX77802_OPMODE_MASK 0x3 + +#define MAX77802_VSEL_MASK 0x3F +#define MAX77802_DVS_VSEL_MASK 0xFF + +#define MAX77802_RAMP_RATE_MASK_2BIT 0xC0 +#define MAX77802_RAMP_RATE_SHIFT_2BIT 6 +#define MAX77802_RAMP_RATE_MASK_4BIT 0xF0 +#define MAX77802_RAMP_RATE_SHIFT_4BIT 4 + +#define MAX77802_STATUS_OFF 0x0 +#define MAX77802_OFF_PWRREQ 0x1 +#define MAX77802_LP_PWRREQ 0x2 + +/* MAX77802 has two register formats: 2-bit and 4-bit */ +static const unsigned int ramp_table_77802_2bit[] = { + 12500, + 25000, + 50000, + 100000, +}; + +static unsigned int ramp_table_77802_4bit[] = { + 1000, 2000, 3030, 4000, + 5000, 5880, 7140, 8330, + 9090, 10000, 11110, 12500, + 16670, 25000, 50000, 100000, +}; + +struct max77802_regulator_prv { + /* Array indexed by regulator id */ + unsigned int opmode[MAX77802_REG_MAX]; +}; + +static inline unsigned int max77802_map_mode(unsigned int mode) +{ + return mode == MAX77802_OPMODE_NORMAL ? + REGULATOR_MODE_NORMAL : REGULATOR_MODE_STANDBY; +} + +static int max77802_get_opmode_shift(int id) +{ + if (id == MAX77802_BUCK1 || (id >= MAX77802_BUCK5 && + id <= MAX77802_BUCK10)) + return 0; + + if (id >= MAX77802_BUCK2 && id <= MAX77802_BUCK4) + return MAX77802_OPMODE_BUCK234_SHIFT; + + if (id >= MAX77802_LDO1 && id <= MAX77802_LDO35) + return MAX77802_OPMODE_SHIFT_LDO; + + return -EINVAL; +} + +/** + * max77802_set_suspend_disable - Disable the regulator during system suspend + * @rdev: regulator to mark as disabled + * + * All regulators expect LDO 1, 3, 20 and 21 support OFF by PWRREQ. + * Configure the regulator so the PMIC will turn it OFF during system suspend. + */ +static int max77802_set_suspend_disable(struct regulator_dev *rdev) +{ + unsigned int val = MAX77802_OFF_PWRREQ; + struct max77802_regulator_prv *max77802 = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + int shift = max77802_get_opmode_shift(id); + + max77802->opmode[id] = val; + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, val << shift); +} + +/* + * Some LDOs support Low Power Mode while the system is running. + * + * LDOs 1, 3, 20, 21. + */ +static int max77802_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct max77802_regulator_prv *max77802 = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + unsigned int val; + int shift = max77802_get_opmode_shift(id); + + switch (mode) { + case REGULATOR_MODE_STANDBY: + val = MAX77802_OPMODE_LP; /* ON in Low Power Mode */ + break; + case REGULATOR_MODE_NORMAL: + val = MAX77802_OPMODE_NORMAL; /* ON in Normal Mode */ + break; + default: + dev_warn(&rdev->dev, "%s: regulator mode: 0x%x not supported\n", + rdev->desc->name, mode); + return -EINVAL; + } + + max77802->opmode[id] = val; + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, val << shift); +} + +static unsigned max77802_get_mode(struct regulator_dev *rdev) +{ + struct max77802_regulator_prv *max77802 = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + + return max77802_map_mode(max77802->opmode[id]); +} + +/** + * max77802_set_suspend_mode - set regulator opmode when the system is suspended + * @rdev: regulator to change mode + * @mode: operating mode to be set + * + * Will set the operating mode for the regulators during system suspend. + * This function is valid for the three different enable control logics: + * + * Enable Control Logic1 by PWRREQ (BUCK 2-4 and LDOs 2, 4-19, 22-35) + * Enable Control Logic2 by PWRREQ (LDOs 1, 20, 21) + * Enable Control Logic3 by PWRREQ (LDO 3) + * + * If setting the regulator mode fails, the function only warns but does + * not return an error code to avoid the regulator core to stop setting + * the operating mode for the remaining regulators. + */ +static int max77802_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct max77802_regulator_prv *max77802 = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + unsigned int val; + int shift = max77802_get_opmode_shift(id); + + /* + * If the regulator has been disabled for suspend + * then is invalid to try setting a suspend mode. + */ + if (max77802->opmode[id] == MAX77802_OFF_PWRREQ) { + dev_warn(&rdev->dev, "%s: is disabled, mode: 0x%x not set\n", + rdev->desc->name, mode); + return 0; + } + + switch (mode) { + case REGULATOR_MODE_STANDBY: + /* + * If the regulator opmode is normal then enable + * ON in Low Power Mode by PWRREQ. If the mode is + * already Low Power then no action is required. + */ + if (max77802->opmode[id] == MAX77802_OPMODE_NORMAL) + val = MAX77802_LP_PWRREQ; + else + return 0; + break; + case REGULATOR_MODE_NORMAL: + /* + * If the regulator operating mode is Low Power then + * normal is not a valid opmode in suspend. If the + * mode is already normal then no action is required. + */ + if (max77802->opmode[id] == MAX77802_OPMODE_LP) + dev_warn(&rdev->dev, "%s: in Low Power: 0x%x invalid\n", + rdev->desc->name, mode); + return 0; + default: + dev_warn(&rdev->dev, "%s: regulator mode: 0x%x not supported\n", + rdev->desc->name, mode); + return -EINVAL; + } + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, val << shift); +} + +static int max77802_enable(struct regulator_dev *rdev) +{ + struct max77802_regulator_prv *max77802 = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + int shift = max77802_get_opmode_shift(id); + + if (max77802->opmode[id] == MAX77802_OFF_PWRREQ) + max77802->opmode[id] = MAX77802_OPMODE_NORMAL; + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, + max77802->opmode[id] << shift); +} + +static int max77802_find_ramp_value(struct regulator_dev *rdev, + const unsigned int limits[], int size, + unsigned int ramp_delay) +{ + int i; + + for (i = 0; i < size; i++) { + if (ramp_delay <= limits[i]) + return i; + } + + /* Use maximum value for no ramp control */ + dev_warn(&rdev->dev, "%s: ramp_delay: %d not supported, setting 100000\n", + rdev->desc->name, ramp_delay); + return size - 1; +} + +/* Used for BUCKs 2-4 */ +static int max77802_set_ramp_delay_2bit(struct regulator_dev *rdev, + int ramp_delay) +{ + int id = rdev_get_id(rdev); + unsigned int ramp_value; + + if (id > MAX77802_BUCK4) { + dev_warn(&rdev->dev, + "%s: regulator: ramp delay not supported\n", + rdev->desc->name); + return -EINVAL; + } + ramp_value = max77802_find_ramp_value(rdev, ramp_table_77802_2bit, + ARRAY_SIZE(ramp_table_77802_2bit), ramp_delay); + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + MAX77802_RAMP_RATE_MASK_2BIT, + ramp_value << MAX77802_RAMP_RATE_SHIFT_2BIT); +} + +/* For BUCK1, 6 */ +static int max77802_set_ramp_delay_4bit(struct regulator_dev *rdev, + int ramp_delay) +{ + unsigned int ramp_value; + + ramp_value = max77802_find_ramp_value(rdev, ramp_table_77802_4bit, + ARRAY_SIZE(ramp_table_77802_4bit), ramp_delay); + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + MAX77802_RAMP_RATE_MASK_4BIT, + ramp_value << MAX77802_RAMP_RATE_SHIFT_4BIT); +} + +/* + * LDOs 2, 4-19, 22-35 + */ +static const struct regulator_ops max77802_ldo_ops_logic1 = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77802_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_disable = max77802_set_suspend_disable, + .set_suspend_mode = max77802_set_suspend_mode, +}; + +/* + * LDOs 1, 20, 21, 3 + */ +static const struct regulator_ops max77802_ldo_ops_logic2 = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77802_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_mode = max77802_set_mode, + .get_mode = max77802_get_mode, + .set_suspend_mode = max77802_set_suspend_mode, +}; + +/* BUCKS 1, 6 */ +static const struct regulator_ops max77802_buck_16_dvs_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77802_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = max77802_set_ramp_delay_4bit, + .set_suspend_disable = max77802_set_suspend_disable, +}; + +/* BUCKs 2-4 */ +static const struct regulator_ops max77802_buck_234_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77802_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = max77802_set_ramp_delay_2bit, + .set_suspend_disable = max77802_set_suspend_disable, + .set_suspend_mode = max77802_set_suspend_mode, +}; + +/* BUCKs 5, 7-10 */ +static const struct regulator_ops max77802_buck_dvs_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = max77802_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = max77802_set_ramp_delay_2bit, + .set_suspend_disable = max77802_set_suspend_disable, +}; + +/* LDOs 3-7, 9-14, 18-26, 28, 29, 32-34 */ +#define regulator_77802_desc_p_ldo(num, supply, log) { \ + .name = "LDO"#num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = MAX77802_LDO##num, \ + .supply_name = "inl"#supply, \ + .ops = &max77802_ldo_ops_logic##log, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 800000, \ + .uV_step = 50000, \ + .ramp_delay = MAX77802_RAMP_DELAY, \ + .n_voltages = 1 << 6, \ + .vsel_reg = MAX77802_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77802_VSEL_MASK, \ + .enable_reg = MAX77802_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77802_OPMODE_MASK << MAX77802_OPMODE_SHIFT_LDO, \ + .of_map_mode = max77802_map_mode, \ +} + +/* LDOs 1, 2, 8, 15, 17, 27, 30, 35 */ +#define regulator_77802_desc_n_ldo(num, supply, log) { \ + .name = "LDO"#num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = MAX77802_LDO##num, \ + .supply_name = "inl"#supply, \ + .ops = &max77802_ldo_ops_logic##log, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 800000, \ + .uV_step = 25000, \ + .ramp_delay = MAX77802_RAMP_DELAY, \ + .n_voltages = 1 << 6, \ + .vsel_reg = MAX77802_REG_LDO1CTRL1 + num - 1, \ + .vsel_mask = MAX77802_VSEL_MASK, \ + .enable_reg = MAX77802_REG_LDO1CTRL1 + num - 1, \ + .enable_mask = MAX77802_OPMODE_MASK << MAX77802_OPMODE_SHIFT_LDO, \ + .of_map_mode = max77802_map_mode, \ +} + +/* BUCKs 1, 6 */ +#define regulator_77802_desc_16_buck(num) { \ + .name = "BUCK"#num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = MAX77802_BUCK##num, \ + .supply_name = "inb"#num, \ + .ops = &max77802_buck_16_dvs_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 612500, \ + .uV_step = 6250, \ + .ramp_delay = MAX77802_RAMP_DELAY, \ + .n_voltages = 1 << 8, \ + .vsel_reg = MAX77802_REG_BUCK ## num ## DVS1, \ + .vsel_mask = MAX77802_DVS_VSEL_MASK, \ + .enable_reg = MAX77802_REG_BUCK ## num ## CTRL, \ + .enable_mask = MAX77802_OPMODE_MASK, \ + .of_map_mode = max77802_map_mode, \ +} + +/* BUCKS 2-4 */ +#define regulator_77802_desc_234_buck(num) { \ + .name = "BUCK"#num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = MAX77802_BUCK##num, \ + .supply_name = "inb"#num, \ + .ops = &max77802_buck_234_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 600000, \ + .uV_step = 6250, \ + .ramp_delay = MAX77802_RAMP_DELAY, \ + .n_voltages = 0x91, \ + .vsel_reg = MAX77802_REG_BUCK ## num ## DVS1, \ + .vsel_mask = MAX77802_DVS_VSEL_MASK, \ + .enable_reg = MAX77802_REG_BUCK ## num ## CTRL1, \ + .enable_mask = MAX77802_OPMODE_MASK << \ + MAX77802_OPMODE_BUCK234_SHIFT, \ + .of_map_mode = max77802_map_mode, \ +} + +/* BUCK 5 */ +#define regulator_77802_desc_buck5(num) { \ + .name = "BUCK"#num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = MAX77802_BUCK##num, \ + .supply_name = "inb"#num, \ + .ops = &max77802_buck_dvs_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 750000, \ + .uV_step = 50000, \ + .ramp_delay = MAX77802_RAMP_DELAY, \ + .n_voltages = 1 << 6, \ + .vsel_reg = MAX77802_REG_BUCK5OUT, \ + .vsel_mask = MAX77802_VSEL_MASK, \ + .enable_reg = MAX77802_REG_BUCK5CTRL, \ + .enable_mask = MAX77802_OPMODE_MASK, \ + .of_map_mode = max77802_map_mode, \ +} + +/* BUCKs 7-10 */ +#define regulator_77802_desc_buck7_10(num) { \ + .name = "BUCK"#num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = MAX77802_BUCK##num, \ + .supply_name = "inb"#num, \ + .ops = &max77802_buck_dvs_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = 750000, \ + .uV_step = 50000, \ + .ramp_delay = MAX77802_RAMP_DELAY, \ + .n_voltages = 1 << 6, \ + .vsel_reg = MAX77802_REG_BUCK7OUT + (num - 7) * 3, \ + .vsel_mask = MAX77802_VSEL_MASK, \ + .enable_reg = MAX77802_REG_BUCK7CTRL + (num - 7) * 3, \ + .enable_mask = MAX77802_OPMODE_MASK, \ + .of_map_mode = max77802_map_mode, \ +} + +static const struct regulator_desc regulators[] = { + regulator_77802_desc_16_buck(1), + regulator_77802_desc_234_buck(2), + regulator_77802_desc_234_buck(3), + regulator_77802_desc_234_buck(4), + regulator_77802_desc_buck5(5), + regulator_77802_desc_16_buck(6), + regulator_77802_desc_buck7_10(7), + regulator_77802_desc_buck7_10(8), + regulator_77802_desc_buck7_10(9), + regulator_77802_desc_buck7_10(10), + regulator_77802_desc_n_ldo(1, 10, 2), + regulator_77802_desc_n_ldo(2, 10, 1), + regulator_77802_desc_p_ldo(3, 3, 2), + regulator_77802_desc_p_ldo(4, 6, 1), + regulator_77802_desc_p_ldo(5, 3, 1), + regulator_77802_desc_p_ldo(6, 3, 1), + regulator_77802_desc_p_ldo(7, 3, 1), + regulator_77802_desc_n_ldo(8, 1, 1), + regulator_77802_desc_p_ldo(9, 5, 1), + regulator_77802_desc_p_ldo(10, 4, 1), + regulator_77802_desc_p_ldo(11, 4, 1), + regulator_77802_desc_p_ldo(12, 9, 1), + regulator_77802_desc_p_ldo(13, 4, 1), + regulator_77802_desc_p_ldo(14, 4, 1), + regulator_77802_desc_n_ldo(15, 1, 1), + regulator_77802_desc_n_ldo(17, 2, 1), + regulator_77802_desc_p_ldo(18, 7, 1), + regulator_77802_desc_p_ldo(19, 5, 1), + regulator_77802_desc_p_ldo(20, 7, 2), + regulator_77802_desc_p_ldo(21, 6, 2), + regulator_77802_desc_p_ldo(23, 9, 1), + regulator_77802_desc_p_ldo(24, 6, 1), + regulator_77802_desc_p_ldo(25, 9, 1), + regulator_77802_desc_p_ldo(26, 9, 1), + regulator_77802_desc_n_ldo(27, 2, 1), + regulator_77802_desc_p_ldo(28, 7, 1), + regulator_77802_desc_p_ldo(29, 7, 1), + regulator_77802_desc_n_ldo(30, 2, 1), + regulator_77802_desc_p_ldo(32, 9, 1), + regulator_77802_desc_p_ldo(33, 6, 1), + regulator_77802_desc_p_ldo(34, 9, 1), + regulator_77802_desc_n_ldo(35, 2, 1), +}; + +static int max77802_pmic_probe(struct platform_device *pdev) +{ + struct max77686_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max77802_regulator_prv *max77802; + int i, val; + struct regulator_config config = { }; + + max77802 = devm_kzalloc(&pdev->dev, + sizeof(struct max77802_regulator_prv), + GFP_KERNEL); + if (!max77802) + return -ENOMEM; + + config.dev = iodev->dev; + config.regmap = iodev->regmap; + config.driver_data = max77802; + platform_set_drvdata(pdev, max77802); + + for (i = 0; i < MAX77802_REG_MAX; i++) { + struct regulator_dev *rdev; + int id = regulators[i].id; + int shift = max77802_get_opmode_shift(id); + int ret; + + ret = regmap_read(iodev->regmap, regulators[i].enable_reg, &val); + if (ret < 0) { + dev_warn(&pdev->dev, + "cannot read current mode for %d\n", i); + val = MAX77802_OPMODE_NORMAL; + } else { + val = val >> shift & MAX77802_OPMODE_MASK; + } + + /* + * If the regulator is disabled and the system warm rebooted, + * the hardware reports OFF as the regulator operating mode. + * Default to operating mode NORMAL in that case. + */ + if (val == MAX77802_STATUS_OFF) + max77802->opmode[id] = MAX77802_OPMODE_NORMAL; + else + max77802->opmode[id] = val; + + rdev = devm_regulator_register(&pdev->dev, + ®ulators[i], &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(&pdev->dev, + "regulator init failed for %d: %d\n", i, ret); + return ret; + } + } + + return 0; +} + +static const struct platform_device_id max77802_pmic_id[] = { + {"max77802-pmic", 0}, + { }, +}; +MODULE_DEVICE_TABLE(platform, max77802_pmic_id); + +static struct platform_driver max77802_pmic_driver = { + .driver = { + .name = "max77802-pmic", + }, + .probe = max77802_pmic_probe, + .id_table = max77802_pmic_id, +}; + +module_platform_driver(max77802_pmic_driver); + +MODULE_DESCRIPTION("MAXIM 77802 Regulator Driver"); +MODULE_AUTHOR("Simon Glass <sjg@chromium.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/max8649.c b/drivers/regulator/max8649.c new file mode 100644 index 000000000..81229579e --- /dev/null +++ b/drivers/regulator/max8649.c @@ -0,0 +1,275 @@ +/* + * Regulators driver for Maxim max8649 + * + * Copyright (C) 2009-2010 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/slab.h> +#include <linux/regulator/max8649.h> +#include <linux/regmap.h> + +#define MAX8649_DCDC_VMIN 750000 /* uV */ +#define MAX8649_DCDC_VMAX 1380000 /* uV */ +#define MAX8649_DCDC_STEP 10000 /* uV */ +#define MAX8649_VOL_MASK 0x3f + +/* Registers */ +#define MAX8649_MODE0 0x00 +#define MAX8649_MODE1 0x01 +#define MAX8649_MODE2 0x02 +#define MAX8649_MODE3 0x03 +#define MAX8649_CONTROL 0x04 +#define MAX8649_SYNC 0x05 +#define MAX8649_RAMP 0x06 +#define MAX8649_CHIP_ID1 0x08 +#define MAX8649_CHIP_ID2 0x09 + +/* Bits */ +#define MAX8649_EN_PD (1 << 7) +#define MAX8649_VID0_PD (1 << 6) +#define MAX8649_VID1_PD (1 << 5) +#define MAX8649_VID_MASK (3 << 5) + +#define MAX8649_FORCE_PWM (1 << 7) +#define MAX8649_SYNC_EXTCLK (1 << 6) + +#define MAX8649_EXT_MASK (3 << 6) + +#define MAX8649_RAMP_MASK (7 << 5) +#define MAX8649_RAMP_DOWN (1 << 1) + +struct max8649_regulator_info { + struct device *dev; + struct regmap *regmap; + + unsigned mode:2; /* bit[1:0] = VID1, VID0 */ + unsigned extclk_freq:2; + unsigned extclk:1; + unsigned ramp_timing:3; + unsigned ramp_down:1; +}; + +static int max8649_enable_time(struct regulator_dev *rdev) +{ + struct max8649_regulator_info *info = rdev_get_drvdata(rdev); + int voltage, rate, ret; + unsigned int val; + + /* get voltage */ + ret = regmap_read(info->regmap, rdev->desc->vsel_reg, &val); + if (ret != 0) + return ret; + val &= MAX8649_VOL_MASK; + voltage = regulator_list_voltage_linear(rdev, (unsigned char)val); + + /* get rate */ + ret = regmap_read(info->regmap, MAX8649_RAMP, &val); + if (ret != 0) + return ret; + ret = (val & MAX8649_RAMP_MASK) >> 5; + rate = (32 * 1000) >> ret; /* uV/uS */ + + return DIV_ROUND_UP(voltage, rate); +} + +static int max8649_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct max8649_regulator_info *info = rdev_get_drvdata(rdev); + + switch (mode) { + case REGULATOR_MODE_FAST: + regmap_update_bits(info->regmap, rdev->desc->vsel_reg, + MAX8649_FORCE_PWM, MAX8649_FORCE_PWM); + break; + case REGULATOR_MODE_NORMAL: + regmap_update_bits(info->regmap, rdev->desc->vsel_reg, + MAX8649_FORCE_PWM, 0); + break; + default: + return -EINVAL; + } + return 0; +} + +static unsigned int max8649_get_mode(struct regulator_dev *rdev) +{ + struct max8649_regulator_info *info = rdev_get_drvdata(rdev); + unsigned int val; + int ret; + + ret = regmap_read(info->regmap, rdev->desc->vsel_reg, &val); + if (ret != 0) + return ret; + if (val & MAX8649_FORCE_PWM) + return REGULATOR_MODE_FAST; + return REGULATOR_MODE_NORMAL; +} + +static const struct regulator_ops max8649_dcdc_ops = { + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .enable_time = max8649_enable_time, + .set_mode = max8649_set_mode, + .get_mode = max8649_get_mode, + +}; + +static struct regulator_desc dcdc_desc = { + .name = "max8649", + .ops = &max8649_dcdc_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = 1 << 6, + .owner = THIS_MODULE, + .vsel_mask = MAX8649_VOL_MASK, + .min_uV = MAX8649_DCDC_VMIN, + .uV_step = MAX8649_DCDC_STEP, + .enable_reg = MAX8649_CONTROL, + .enable_mask = MAX8649_EN_PD, + .enable_is_inverted = true, +}; + +static const struct regmap_config max8649_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int max8649_regulator_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max8649_platform_data *pdata = dev_get_platdata(&client->dev); + struct max8649_regulator_info *info = NULL; + struct regulator_dev *regulator; + struct regulator_config config = { }; + unsigned int val; + unsigned char data; + int ret; + + info = devm_kzalloc(&client->dev, sizeof(struct max8649_regulator_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->regmap = devm_regmap_init_i2c(client, &max8649_regmap_config); + if (IS_ERR(info->regmap)) { + ret = PTR_ERR(info->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", ret); + return ret; + } + + info->dev = &client->dev; + i2c_set_clientdata(client, info); + + info->mode = pdata->mode; + switch (info->mode) { + case 0: + dcdc_desc.vsel_reg = MAX8649_MODE0; + break; + case 1: + dcdc_desc.vsel_reg = MAX8649_MODE1; + break; + case 2: + dcdc_desc.vsel_reg = MAX8649_MODE2; + break; + case 3: + dcdc_desc.vsel_reg = MAX8649_MODE3; + break; + default: + break; + } + + ret = regmap_read(info->regmap, MAX8649_CHIP_ID1, &val); + if (ret != 0) { + dev_err(info->dev, "Failed to detect ID of MAX8649:%d\n", + ret); + return ret; + } + dev_info(info->dev, "Detected MAX8649 (ID:%x)\n", val); + + /* enable VID0 & VID1 */ + regmap_update_bits(info->regmap, MAX8649_CONTROL, MAX8649_VID_MASK, 0); + + /* enable/disable external clock synchronization */ + info->extclk = pdata->extclk; + data = (info->extclk) ? MAX8649_SYNC_EXTCLK : 0; + regmap_update_bits(info->regmap, dcdc_desc.vsel_reg, + MAX8649_SYNC_EXTCLK, data); + if (info->extclk) { + /* set external clock frequency */ + info->extclk_freq = pdata->extclk_freq; + regmap_update_bits(info->regmap, MAX8649_SYNC, MAX8649_EXT_MASK, + info->extclk_freq << 6); + } + + if (pdata->ramp_timing) { + info->ramp_timing = pdata->ramp_timing; + regmap_update_bits(info->regmap, MAX8649_RAMP, MAX8649_RAMP_MASK, + info->ramp_timing << 5); + } + + info->ramp_down = pdata->ramp_down; + if (info->ramp_down) { + regmap_update_bits(info->regmap, MAX8649_RAMP, MAX8649_RAMP_DOWN, + MAX8649_RAMP_DOWN); + } + + config.dev = &client->dev; + config.init_data = pdata->regulator; + config.driver_data = info; + config.regmap = info->regmap; + + regulator = devm_regulator_register(&client->dev, &dcdc_desc, + &config); + if (IS_ERR(regulator)) { + dev_err(info->dev, "failed to register regulator %s\n", + dcdc_desc.name); + return PTR_ERR(regulator); + } + + return 0; +} + +static const struct i2c_device_id max8649_id[] = { + { "max8649", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max8649_id); + +static struct i2c_driver max8649_driver = { + .probe = max8649_regulator_probe, + .driver = { + .name = "max8649", + }, + .id_table = max8649_id, +}; + +static int __init max8649_init(void) +{ + return i2c_add_driver(&max8649_driver); +} +subsys_initcall(max8649_init); + +static void __exit max8649_exit(void) +{ + i2c_del_driver(&max8649_driver); +} +module_exit(max8649_exit); + +/* Module information */ +MODULE_DESCRIPTION("MAXIM 8649 voltage regulator driver"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/max8660.c b/drivers/regulator/max8660.c new file mode 100644 index 000000000..4cf6897a4 --- /dev/null +++ b/drivers/regulator/max8660.c @@ -0,0 +1,542 @@ +/* + * max8660.c -- Voltage regulation for the Maxim 8660/8661 + * + * based on max1586.c and wm8400-regulator.c + * + * Copyright (C) 2009 Wolfram Sang, Pengutronix e.K. + * + * 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 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA + * + * Some info: + * + * Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX8660-MAX8661.pdf + * + * This chip is a bit nasty because it is a write-only device. Thus, the driver + * uses shadow registers to keep track of its values. The main problem appears + * to be the initialization: When Linux boots up, we cannot know if the chip is + * in the default state or not, so we would have to pass such information in + * platform_data. As this adds a bit of complexity to the driver, this is left + * out for now until it is really needed. + * + * [A|S|M]DTV1 registers are currently not used, but [A|S|M]DTV2. + * + * If the driver is feature complete, it might be worth to check if one set of + * functions for V3-V7 is sufficient. For maximum flexibility during + * development, they are separated for now. + * + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/slab.h> +#include <linux/regulator/max8660.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/of_regulator.h> + +#define MAX8660_DCDC_MIN_UV 725000 +#define MAX8660_DCDC_MAX_UV 1800000 +#define MAX8660_DCDC_STEP 25000 +#define MAX8660_DCDC_MAX_SEL 0x2b + +#define MAX8660_LDO5_MIN_UV 1700000 +#define MAX8660_LDO5_MAX_UV 2000000 +#define MAX8660_LDO5_STEP 25000 +#define MAX8660_LDO5_MAX_SEL 0x0c + +#define MAX8660_LDO67_MIN_UV 1800000 +#define MAX8660_LDO67_MAX_UV 3300000 +#define MAX8660_LDO67_STEP 100000 +#define MAX8660_LDO67_MAX_SEL 0x0f + +enum { + MAX8660_OVER1, + MAX8660_OVER2, + MAX8660_VCC1, + MAX8660_ADTV1, + MAX8660_ADTV2, + MAX8660_SDTV1, + MAX8660_SDTV2, + MAX8660_MDTV1, + MAX8660_MDTV2, + MAX8660_L12VCR, + MAX8660_FPWM, + MAX8660_N_REGS, /* not a real register */ +}; + +struct max8660 { + struct i2c_client *client; + u8 shadow_regs[MAX8660_N_REGS]; /* as chip is write only */ +}; + +static int max8660_write(struct max8660 *max8660, u8 reg, u8 mask, u8 val) +{ + static const u8 max8660_addresses[MAX8660_N_REGS] = { + 0x10, 0x12, 0x20, 0x23, 0x24, 0x29, 0x2a, 0x32, 0x33, 0x39, 0x80 + }; + + int ret; + u8 reg_val = (max8660->shadow_regs[reg] & mask) | val; + + dev_vdbg(&max8660->client->dev, "Writing reg %02x with %02x\n", + max8660_addresses[reg], reg_val); + + ret = i2c_smbus_write_byte_data(max8660->client, + max8660_addresses[reg], reg_val); + if (ret == 0) + max8660->shadow_regs[reg] = reg_val; + + return ret; +} + + +/* + * DCDC functions + */ + +static int max8660_dcdc_is_enabled(struct regulator_dev *rdev) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + u8 val = max8660->shadow_regs[MAX8660_OVER1]; + u8 mask = (rdev_get_id(rdev) == MAX8660_V3) ? 1 : 4; + + return !!(val & mask); +} + +static int max8660_dcdc_enable(struct regulator_dev *rdev) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + u8 bit = (rdev_get_id(rdev) == MAX8660_V3) ? 1 : 4; + + return max8660_write(max8660, MAX8660_OVER1, 0xff, bit); +} + +static int max8660_dcdc_disable(struct regulator_dev *rdev) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + u8 mask = (rdev_get_id(rdev) == MAX8660_V3) ? ~1 : ~4; + + return max8660_write(max8660, MAX8660_OVER1, mask, 0); +} + +static int max8660_dcdc_get_voltage_sel(struct regulator_dev *rdev) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + u8 reg = (rdev_get_id(rdev) == MAX8660_V3) ? MAX8660_ADTV2 : MAX8660_SDTV2; + u8 selector = max8660->shadow_regs[reg]; + + return selector; +} + +static int max8660_dcdc_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + u8 reg, bits; + int ret; + + reg = (rdev_get_id(rdev) == MAX8660_V3) ? MAX8660_ADTV2 : MAX8660_SDTV2; + ret = max8660_write(max8660, reg, 0, selector); + if (ret) + return ret; + + /* Select target voltage register and activate regulation */ + bits = (rdev_get_id(rdev) == MAX8660_V3) ? 0x03 : 0x30; + return max8660_write(max8660, MAX8660_VCC1, 0xff, bits); +} + +static struct regulator_ops max8660_dcdc_ops = { + .is_enabled = max8660_dcdc_is_enabled, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_voltage_sel = max8660_dcdc_set_voltage_sel, + .get_voltage_sel = max8660_dcdc_get_voltage_sel, +}; + + +/* + * LDO5 functions + */ + +static int max8660_ldo5_get_voltage_sel(struct regulator_dev *rdev) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + + u8 selector = max8660->shadow_regs[MAX8660_MDTV2]; + return selector; +} + +static int max8660_ldo5_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + int ret; + + ret = max8660_write(max8660, MAX8660_MDTV2, 0, selector); + if (ret) + return ret; + + /* Select target voltage register and activate regulation */ + return max8660_write(max8660, MAX8660_VCC1, 0xff, 0xc0); +} + +static const struct regulator_ops max8660_ldo5_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_voltage_sel = max8660_ldo5_set_voltage_sel, + .get_voltage_sel = max8660_ldo5_get_voltage_sel, +}; + + +/* + * LDO67 functions + */ + +static int max8660_ldo67_is_enabled(struct regulator_dev *rdev) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + u8 val = max8660->shadow_regs[MAX8660_OVER2]; + u8 mask = (rdev_get_id(rdev) == MAX8660_V6) ? 2 : 4; + + return !!(val & mask); +} + +static int max8660_ldo67_enable(struct regulator_dev *rdev) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + u8 bit = (rdev_get_id(rdev) == MAX8660_V6) ? 2 : 4; + + return max8660_write(max8660, MAX8660_OVER2, 0xff, bit); +} + +static int max8660_ldo67_disable(struct regulator_dev *rdev) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + u8 mask = (rdev_get_id(rdev) == MAX8660_V6) ? ~2 : ~4; + + return max8660_write(max8660, MAX8660_OVER2, mask, 0); +} + +static int max8660_ldo67_get_voltage_sel(struct regulator_dev *rdev) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + u8 shift = (rdev_get_id(rdev) == MAX8660_V6) ? 0 : 4; + u8 selector = (max8660->shadow_regs[MAX8660_L12VCR] >> shift) & 0xf; + + return selector; +} + +static int max8660_ldo67_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct max8660 *max8660 = rdev_get_drvdata(rdev); + + if (rdev_get_id(rdev) == MAX8660_V6) + return max8660_write(max8660, MAX8660_L12VCR, 0xf0, selector); + else + return max8660_write(max8660, MAX8660_L12VCR, 0x0f, + selector << 4); +} + +static const struct regulator_ops max8660_ldo67_ops = { + .is_enabled = max8660_ldo67_is_enabled, + .enable = max8660_ldo67_enable, + .disable = max8660_ldo67_disable, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = max8660_ldo67_get_voltage_sel, + .set_voltage_sel = max8660_ldo67_set_voltage_sel, +}; + +static const struct regulator_desc max8660_reg[] = { + { + .name = "V3(DCDC)", + .id = MAX8660_V3, + .ops = &max8660_dcdc_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = MAX8660_DCDC_MAX_SEL + 1, + .owner = THIS_MODULE, + .min_uV = MAX8660_DCDC_MIN_UV, + .uV_step = MAX8660_DCDC_STEP, + }, + { + .name = "V4(DCDC)", + .id = MAX8660_V4, + .ops = &max8660_dcdc_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = MAX8660_DCDC_MAX_SEL + 1, + .owner = THIS_MODULE, + .min_uV = MAX8660_DCDC_MIN_UV, + .uV_step = MAX8660_DCDC_STEP, + }, + { + .name = "V5(LDO)", + .id = MAX8660_V5, + .ops = &max8660_ldo5_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = MAX8660_LDO5_MAX_SEL + 1, + .owner = THIS_MODULE, + .min_uV = MAX8660_LDO5_MIN_UV, + .uV_step = MAX8660_LDO5_STEP, + }, + { + .name = "V6(LDO)", + .id = MAX8660_V6, + .ops = &max8660_ldo67_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = MAX8660_LDO67_MAX_SEL + 1, + .owner = THIS_MODULE, + .min_uV = MAX8660_LDO67_MIN_UV, + .uV_step = MAX8660_LDO67_STEP, + }, + { + .name = "V7(LDO)", + .id = MAX8660_V7, + .ops = &max8660_ldo67_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = MAX8660_LDO67_MAX_SEL + 1, + .owner = THIS_MODULE, + .min_uV = MAX8660_LDO67_MIN_UV, + .uV_step = MAX8660_LDO67_STEP, + }, +}; + +enum { + MAX8660 = 0, + MAX8661 = 1, +}; + +#ifdef CONFIG_OF +static const struct of_device_id max8660_dt_ids[] = { + { .compatible = "maxim,max8660", .data = (void *) MAX8660 }, + { .compatible = "maxim,max8661", .data = (void *) MAX8661 }, + { } +}; +MODULE_DEVICE_TABLE(of, max8660_dt_ids); + +static int max8660_pdata_from_dt(struct device *dev, + struct device_node **of_node, + struct max8660_platform_data *pdata) +{ + int matched, i; + struct device_node *np; + struct max8660_subdev_data *sub; + struct of_regulator_match rmatch[ARRAY_SIZE(max8660_reg)] = { }; + + np = of_get_child_by_name(dev->of_node, "regulators"); + if (!np) { + dev_err(dev, "missing 'regulators' subnode in DT\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(rmatch); i++) + rmatch[i].name = max8660_reg[i].name; + + matched = of_regulator_match(dev, np, rmatch, ARRAY_SIZE(rmatch)); + of_node_put(np); + if (matched <= 0) + return matched; + + pdata->subdevs = devm_kcalloc(dev, + matched, + sizeof(struct max8660_subdev_data), + GFP_KERNEL); + if (!pdata->subdevs) + return -ENOMEM; + + pdata->num_subdevs = matched; + sub = pdata->subdevs; + + for (i = 0; i < matched; i++) { + sub->id = i; + sub->name = rmatch[i].name; + sub->platform_data = rmatch[i].init_data; + of_node[i] = rmatch[i].of_node; + sub++; + } + + return 0; +} +#else +static inline int max8660_pdata_from_dt(struct device *dev, + struct device_node **of_node, + struct max8660_platform_data *pdata) +{ + return 0; +} +#endif + +static int max8660_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct device *dev = &client->dev; + struct max8660_platform_data pdata_of, *pdata = dev_get_platdata(dev); + struct regulator_config config = { }; + struct max8660 *max8660; + int boot_on, i, id, ret = -EINVAL; + struct device_node *of_node[MAX8660_V_END]; + unsigned long type; + + if (dev->of_node && !pdata) { + const struct of_device_id *id; + + id = of_match_device(of_match_ptr(max8660_dt_ids), dev); + if (!id) + return -ENODEV; + + ret = max8660_pdata_from_dt(dev, of_node, &pdata_of); + if (ret < 0) + return ret; + + pdata = &pdata_of; + type = (unsigned long) id->data; + } else { + type = i2c_id->driver_data; + memset(of_node, 0, sizeof(of_node)); + } + + if (pdata->num_subdevs > MAX8660_V_END) { + dev_err(dev, "Too many regulators found!\n"); + return -EINVAL; + } + + max8660 = devm_kzalloc(dev, sizeof(struct max8660), GFP_KERNEL); + if (!max8660) + return -ENOMEM; + + max8660->client = client; + + if (pdata->en34_is_high) { + /* Simulate always on */ + max8660->shadow_regs[MAX8660_OVER1] = 5; + } else { + /* Otherwise devices can be toggled via software */ + max8660_dcdc_ops.enable = max8660_dcdc_enable; + max8660_dcdc_ops.disable = max8660_dcdc_disable; + } + + /* + * First, set up shadow registers to prevent glitches. As some + * registers are shared between regulators, everything must be properly + * set up for all regulators in advance. + */ + max8660->shadow_regs[MAX8660_ADTV1] = + max8660->shadow_regs[MAX8660_ADTV2] = + max8660->shadow_regs[MAX8660_SDTV1] = + max8660->shadow_regs[MAX8660_SDTV2] = 0x1b; + max8660->shadow_regs[MAX8660_MDTV1] = + max8660->shadow_regs[MAX8660_MDTV2] = 0x04; + + for (i = 0; i < pdata->num_subdevs; i++) { + + if (!pdata->subdevs[i].platform_data) + boot_on = false; + else + boot_on = pdata->subdevs[i].platform_data->constraints.boot_on; + + switch (pdata->subdevs[i].id) { + case MAX8660_V3: + if (boot_on) + max8660->shadow_regs[MAX8660_OVER1] |= 1; + break; + + case MAX8660_V4: + if (boot_on) + max8660->shadow_regs[MAX8660_OVER1] |= 4; + break; + + case MAX8660_V5: + break; + + case MAX8660_V6: + if (boot_on) + max8660->shadow_regs[MAX8660_OVER2] |= 2; + break; + + case MAX8660_V7: + if (type == MAX8661) { + dev_err(dev, "Regulator not on this chip!\n"); + return -EINVAL; + } + + if (boot_on) + max8660->shadow_regs[MAX8660_OVER2] |= 4; + break; + + default: + dev_err(dev, "invalid regulator %s\n", + pdata->subdevs[i].name); + return ret; + } + } + + /* Finally register devices */ + for (i = 0; i < pdata->num_subdevs; i++) { + struct regulator_dev *rdev; + + id = pdata->subdevs[i].id; + + config.dev = dev; + config.init_data = pdata->subdevs[i].platform_data; + config.of_node = of_node[i]; + config.driver_data = max8660; + + rdev = devm_regulator_register(&client->dev, + &max8660_reg[id], &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(&client->dev, "failed to register %s\n", + max8660_reg[id].name); + return PTR_ERR(rdev); + } + } + + i2c_set_clientdata(client, max8660); + return 0; +} + +static const struct i2c_device_id max8660_id[] = { + { .name = "max8660", .driver_data = MAX8660 }, + { .name = "max8661", .driver_data = MAX8661 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max8660_id); + +static struct i2c_driver max8660_driver = { + .probe = max8660_probe, + .driver = { + .name = "max8660", + }, + .id_table = max8660_id, +}; + +static int __init max8660_init(void) +{ + return i2c_add_driver(&max8660_driver); +} +subsys_initcall(max8660_init); + +static void __exit max8660_exit(void) +{ + i2c_del_driver(&max8660_driver); +} +module_exit(max8660_exit); + +/* Module information */ +MODULE_DESCRIPTION("MAXIM 8660/8661 voltage regulator driver"); +MODULE_AUTHOR("Wolfram Sang"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/max8907-regulator.c b/drivers/regulator/max8907-regulator.c new file mode 100644 index 000000000..a8f2f0723 --- /dev/null +++ b/drivers/regulator/max8907-regulator.c @@ -0,0 +1,399 @@ +/* + * max8907-regulator.c -- support regulators in max8907 + * + * Copyright (C) 2010 Gyungoh Yoo <jack.yoo@maxim-ic.com> + * Copyright (C) 2010-2012, NVIDIA CORPORATION. All rights reserved. + * + * Portions based on drivers/regulator/tps65910-regulator.c, + * Copyright 2010 Texas Instruments Inc. + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk> + * + * 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/err.h> +#include <linux/init.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max8907.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define MAX8907_II2RR_VERSION_MASK 0xF0 +#define MAX8907_II2RR_VERSION_REV_A 0x00 +#define MAX8907_II2RR_VERSION_REV_B 0x10 +#define MAX8907_II2RR_VERSION_REV_C 0x30 + +struct max8907_regulator { + struct regulator_desc desc[MAX8907_NUM_REGULATORS]; +}; + +#define REG_MBATT() \ + [MAX8907_MBATT] = { \ + .name = "MBATT", \ + .supply_name = "mbatt", \ + .id = MAX8907_MBATT, \ + .ops = &max8907_mbatt_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +#define REG_LDO(ids, supply, base, min, max, step) \ + [MAX8907_##ids] = { \ + .name = #ids, \ + .supply_name = supply, \ + .id = MAX8907_##ids, \ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .ops = &max8907_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = (min), \ + .uV_step = (step), \ + .vsel_reg = (base) + MAX8907_VOUT, \ + .vsel_mask = 0x3f, \ + .enable_reg = (base) + MAX8907_CTL, \ + .enable_mask = MAX8907_MASK_LDO_EN, \ + } + +#define REG_FIXED(ids, supply, voltage) \ + [MAX8907_##ids] = { \ + .name = #ids, \ + .supply_name = supply, \ + .id = MAX8907_##ids, \ + .n_voltages = 1, \ + .ops = &max8907_fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = (voltage), \ + } + +#define REG_OUT5V(ids, supply, base, voltage) \ + [MAX8907_##ids] = { \ + .name = #ids, \ + .supply_name = supply, \ + .id = MAX8907_##ids, \ + .n_voltages = 1, \ + .ops = &max8907_out5v_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = (voltage), \ + .enable_reg = (base), \ + .enable_mask = MAX8907_MASK_OUT5V_EN, \ + } + +#define REG_BBAT(ids, supply, base, min, max, step) \ + [MAX8907_##ids] = { \ + .name = #ids, \ + .supply_name = supply, \ + .id = MAX8907_##ids, \ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .ops = &max8907_bbat_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = (min), \ + .uV_step = (step), \ + .vsel_reg = (base), \ + .vsel_mask = MAX8907_MASK_VBBATTCV, \ + } + +#define LDO_750_50(id, supply, base) REG_LDO(id, supply, (base), \ + 750000, 3900000, 50000) +#define LDO_650_25(id, supply, base) REG_LDO(id, supply, (base), \ + 650000, 2225000, 25000) + +static const struct regulator_ops max8907_mbatt_ops = { +}; + +static struct regulator_ops max8907_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct regulator_ops max8907_ldo_hwctl_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + +static const struct regulator_ops max8907_fixed_ops = { + .list_voltage = regulator_list_voltage_linear, +}; + +static struct regulator_ops max8907_out5v_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct regulator_ops max8907_out5v_hwctl_ops = { + .list_voltage = regulator_list_voltage_linear, +}; + +static const struct regulator_ops max8907_bbat_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + +static struct regulator_desc max8907_regulators[] = { + REG_MBATT(), + REG_LDO(SD1, "in-v1", MAX8907_REG_SDCTL1, 650000, 2225000, 25000), + REG_LDO(SD2, "in-v2", MAX8907_REG_SDCTL2, 637500, 1425000, 12500), + REG_LDO(SD3, "in-v3", MAX8907_REG_SDCTL3, 750000, 3900000, 50000), + LDO_750_50(LDO1, "in1", MAX8907_REG_LDOCTL1), + LDO_650_25(LDO2, "in2", MAX8907_REG_LDOCTL2), + LDO_650_25(LDO3, "in3", MAX8907_REG_LDOCTL3), + LDO_750_50(LDO4, "in4", MAX8907_REG_LDOCTL4), + LDO_750_50(LDO5, "in5", MAX8907_REG_LDOCTL5), + LDO_750_50(LDO6, "in6", MAX8907_REG_LDOCTL6), + LDO_750_50(LDO7, "in7", MAX8907_REG_LDOCTL7), + LDO_750_50(LDO8, "in8", MAX8907_REG_LDOCTL8), + LDO_750_50(LDO9, "in9", MAX8907_REG_LDOCTL9), + LDO_750_50(LDO10, "in10", MAX8907_REG_LDOCTL10), + LDO_750_50(LDO11, "in11", MAX8907_REG_LDOCTL11), + LDO_750_50(LDO12, "in12", MAX8907_REG_LDOCTL12), + LDO_750_50(LDO13, "in13", MAX8907_REG_LDOCTL13), + LDO_750_50(LDO14, "in14", MAX8907_REG_LDOCTL14), + LDO_750_50(LDO15, "in15", MAX8907_REG_LDOCTL15), + LDO_750_50(LDO16, "in16", MAX8907_REG_LDOCTL16), + LDO_650_25(LDO17, "in17", MAX8907_REG_LDOCTL17), + LDO_650_25(LDO18, "in18", MAX8907_REG_LDOCTL18), + LDO_750_50(LDO19, "in19", MAX8907_REG_LDOCTL19), + LDO_750_50(LDO20, "in20", MAX8907_REG_LDOCTL20), + REG_OUT5V(OUT5V, "mbatt", MAX8907_REG_OUT5VEN, 5000000), + REG_OUT5V(OUT33V, "mbatt", MAX8907_REG_OUT33VEN, 3300000), + REG_BBAT(BBAT, "MBATT", MAX8907_REG_BBAT_CNFG, + 2400000, 3000000, 200000), + REG_FIXED(SDBY, "MBATT", 1200000), + REG_FIXED(VRTC, "MBATT", 3300000), +}; + +#ifdef CONFIG_OF + +#define MATCH(_name, _id) \ + [MAX8907_##_id] = { \ + .name = #_name, \ + .driver_data = (void *)&max8907_regulators[MAX8907_##_id], \ + } + +static struct of_regulator_match max8907_matches[] = { + MATCH(mbatt, MBATT), + MATCH(sd1, SD1), + MATCH(sd2, SD2), + MATCH(sd3, SD3), + MATCH(ldo1, LDO1), + MATCH(ldo2, LDO2), + MATCH(ldo3, LDO3), + MATCH(ldo4, LDO4), + MATCH(ldo5, LDO5), + MATCH(ldo6, LDO6), + MATCH(ldo7, LDO7), + MATCH(ldo8, LDO8), + MATCH(ldo9, LDO9), + MATCH(ldo10, LDO10), + MATCH(ldo11, LDO11), + MATCH(ldo12, LDO12), + MATCH(ldo13, LDO13), + MATCH(ldo14, LDO14), + MATCH(ldo15, LDO15), + MATCH(ldo16, LDO16), + MATCH(ldo17, LDO17), + MATCH(ldo18, LDO18), + MATCH(ldo19, LDO19), + MATCH(ldo20, LDO20), + MATCH(out5v, OUT5V), + MATCH(out33v, OUT33V), + MATCH(bbat, BBAT), + MATCH(sdby, SDBY), + MATCH(vrtc, VRTC), +}; + +static int max8907_regulator_parse_dt(struct platform_device *pdev) +{ + struct device_node *np, *regulators; + int ret; + + np = pdev->dev.parent->of_node; + if (!np) + return 0; + + regulators = of_get_child_by_name(np, "regulators"); + if (!regulators) { + dev_err(&pdev->dev, "regulators node not found\n"); + return -EINVAL; + } + + ret = of_regulator_match(&pdev->dev, regulators, max8907_matches, + ARRAY_SIZE(max8907_matches)); + of_node_put(regulators); + if (ret < 0) { + dev_err(&pdev->dev, "Error parsing regulator init data: %d\n", + ret); + return ret; + } + + return 0; +} + +static inline struct regulator_init_data *match_init_data(int index) +{ + return max8907_matches[index].init_data; +} + +static inline struct device_node *match_of_node(int index) +{ + return max8907_matches[index].of_node; +} +#else +static int max8907_regulator_parse_dt(struct platform_device *pdev) +{ + return 0; +} + +static inline struct regulator_init_data *match_init_data(int index) +{ + return NULL; +} + +static inline struct device_node *match_of_node(int index) +{ + return NULL; +} +#endif + +static int max8907_regulator_probe(struct platform_device *pdev) +{ + struct max8907 *max8907 = dev_get_drvdata(pdev->dev.parent); + struct max8907_platform_data *pdata = dev_get_platdata(max8907->dev); + int ret; + struct max8907_regulator *pmic; + unsigned int val; + int i; + struct regulator_config config = {}; + struct regulator_init_data *idata; + const char *mbatt_rail_name = NULL; + + ret = max8907_regulator_parse_dt(pdev); + if (ret) + return ret; + + pmic = devm_kzalloc(&pdev->dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + platform_set_drvdata(pdev, pmic); + + memcpy(pmic->desc, max8907_regulators, sizeof(pmic->desc)); + + /* Backwards compatibility with MAX8907B; SD1 uses different voltages */ + ret = regmap_read(max8907->regmap_gen, MAX8907_REG_II2RR, &val); + if (ret) + return ret; + + if ((val & MAX8907_II2RR_VERSION_MASK) == + MAX8907_II2RR_VERSION_REV_B) { + pmic->desc[MAX8907_SD1].min_uV = 637500; + pmic->desc[MAX8907_SD1].uV_step = 12500; + pmic->desc[MAX8907_SD1].n_voltages = + (1425000 - 637500) / 12500 + 1; + } + + for (i = 0; i < MAX8907_NUM_REGULATORS; i++) { + struct regulator_dev *rdev; + + config.dev = pdev->dev.parent; + if (pdata) + idata = pdata->init_data[i]; + else + idata = match_init_data(i); + config.init_data = idata; + config.driver_data = pmic; + config.regmap = max8907->regmap_gen; + config.of_node = match_of_node(i); + + switch (pmic->desc[i].id) { + case MAX8907_MBATT: + if (idata && idata->constraints.name) + mbatt_rail_name = idata->constraints.name; + else + mbatt_rail_name = pmic->desc[i].name; + break; + case MAX8907_BBAT: + case MAX8907_SDBY: + case MAX8907_VRTC: + idata->supply_regulator = mbatt_rail_name; + break; + } + + if (pmic->desc[i].ops == &max8907_ldo_ops) { + ret = regmap_read(config.regmap, pmic->desc[i].enable_reg, + &val); + if (ret) + return ret; + + if ((val & MAX8907_MASK_LDO_SEQ) != + MAX8907_MASK_LDO_SEQ) + pmic->desc[i].ops = &max8907_ldo_hwctl_ops; + } else if (pmic->desc[i].ops == &max8907_out5v_ops) { + ret = regmap_read(config.regmap, pmic->desc[i].enable_reg, + &val); + if (ret) + return ret; + + if ((val & (MAX8907_MASK_OUT5V_VINEN | + MAX8907_MASK_OUT5V_ENSRC)) != + MAX8907_MASK_OUT5V_ENSRC) + pmic->desc[i].ops = &max8907_out5v_hwctl_ops; + } + + rdev = devm_regulator_register(&pdev->dev, + &pmic->desc[i], &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, + "failed to register %s regulator\n", + pmic->desc[i].name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static struct platform_driver max8907_regulator_driver = { + .driver = { + .name = "max8907-regulator", + }, + .probe = max8907_regulator_probe, +}; + +static int __init max8907_regulator_init(void) +{ + return platform_driver_register(&max8907_regulator_driver); +} + +subsys_initcall(max8907_regulator_init); + +static void __exit max8907_reg_exit(void) +{ + platform_driver_unregister(&max8907_regulator_driver); +} + +module_exit(max8907_reg_exit); + +MODULE_DESCRIPTION("MAX8907 regulator driver"); +MODULE_AUTHOR("Gyungoh Yoo <jack.yoo@maxim-ic.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:max8907-regulator"); diff --git a/drivers/regulator/max8925-regulator.c b/drivers/regulator/max8925-regulator.c new file mode 100644 index 000000000..39b63ddef --- /dev/null +++ b/drivers/regulator/max8925-regulator.c @@ -0,0 +1,346 @@ +/* + * Regulators driver for Maxim max8925 + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/max8925.h> +#include <linux/of.h> +#include <linux/regulator/of_regulator.h> + +#define SD1_DVM_VMIN 850000 +#define SD1_DVM_VMAX 1000000 +#define SD1_DVM_STEP 50000 +#define SD1_DVM_SHIFT 5 /* SDCTL1 bit5 */ +#define SD1_DVM_EN 6 /* SDV1 bit 6 */ + +/* bit definitions in LDO control registers */ +#define LDO_SEQ_I2C 0x7 /* Power U/D by i2c */ +#define LDO_SEQ_MASK 0x7 /* Power U/D sequence mask */ +#define LDO_SEQ_SHIFT 2 /* Power U/D sequence offset */ +#define LDO_I2C_EN 0x1 /* Enable by i2c */ +#define LDO_I2C_EN_MASK 0x1 /* Enable mask by i2c */ +#define LDO_I2C_EN_SHIFT 0 /* Enable offset by i2c */ + +struct max8925_regulator_info { + struct regulator_desc desc; + struct i2c_client *i2c; + + int vol_reg; + int enable_reg; +}; + +static int max8925_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + unsigned char mask = rdev->desc->n_voltages - 1; + + return max8925_set_bits(info->i2c, info->vol_reg, mask, selector); +} + +static int max8925_get_voltage_sel(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + unsigned char data, mask; + int ret; + + ret = max8925_reg_read(info->i2c, info->vol_reg); + if (ret < 0) + return ret; + mask = rdev->desc->n_voltages - 1; + data = ret & mask; + + return data; +} + +static int max8925_enable(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + + return max8925_set_bits(info->i2c, info->enable_reg, + LDO_SEQ_MASK << LDO_SEQ_SHIFT | + LDO_I2C_EN_MASK << LDO_I2C_EN_SHIFT, + LDO_SEQ_I2C << LDO_SEQ_SHIFT | + LDO_I2C_EN << LDO_I2C_EN_SHIFT); +} + +static int max8925_disable(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + + return max8925_set_bits(info->i2c, info->enable_reg, + LDO_SEQ_MASK << LDO_SEQ_SHIFT | + LDO_I2C_EN_MASK << LDO_I2C_EN_SHIFT, + LDO_SEQ_I2C << LDO_SEQ_SHIFT); +} + +static int max8925_is_enabled(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + int ldo_seq, ret; + + ret = max8925_reg_read(info->i2c, info->enable_reg); + if (ret < 0) + return ret; + ldo_seq = (ret >> LDO_SEQ_SHIFT) & LDO_SEQ_MASK; + if (ldo_seq != LDO_SEQ_I2C) + return 1; + else + return ret & (LDO_I2C_EN_MASK << LDO_I2C_EN_SHIFT); +} + +static int max8925_set_dvm_voltage(struct regulator_dev *rdev, int uV) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + unsigned char data, mask; + + if (uV < SD1_DVM_VMIN || uV > SD1_DVM_VMAX) + return -EINVAL; + + data = DIV_ROUND_UP(uV - SD1_DVM_VMIN, SD1_DVM_STEP); + data <<= SD1_DVM_SHIFT; + mask = 3 << SD1_DVM_SHIFT; + + return max8925_set_bits(info->i2c, info->enable_reg, mask, data); +} + +static int max8925_set_dvm_enable(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + + return max8925_set_bits(info->i2c, info->vol_reg, 1 << SD1_DVM_EN, + 1 << SD1_DVM_EN); +} + +static int max8925_set_dvm_disable(struct regulator_dev *rdev) +{ + struct max8925_regulator_info *info = rdev_get_drvdata(rdev); + + return max8925_set_bits(info->i2c, info->vol_reg, 1 << SD1_DVM_EN, 0); +} + +static const struct regulator_ops max8925_regulator_sdv_ops = { + .map_voltage = regulator_map_voltage_linear, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = max8925_set_voltage_sel, + .get_voltage_sel = max8925_get_voltage_sel, + .enable = max8925_enable, + .disable = max8925_disable, + .is_enabled = max8925_is_enabled, + .set_suspend_voltage = max8925_set_dvm_voltage, + .set_suspend_enable = max8925_set_dvm_enable, + .set_suspend_disable = max8925_set_dvm_disable, +}; + +static const struct regulator_ops max8925_regulator_ldo_ops = { + .map_voltage = regulator_map_voltage_linear, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = max8925_set_voltage_sel, + .get_voltage_sel = max8925_get_voltage_sel, + .enable = max8925_enable, + .disable = max8925_disable, + .is_enabled = max8925_is_enabled, +}; + +#define MAX8925_SDV(_id, min, max, step) \ +{ \ + .desc = { \ + .name = "SDV" #_id, \ + .ops = &max8925_regulator_sdv_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MAX8925_ID_SD##_id, \ + .owner = THIS_MODULE, \ + .n_voltages = 64, \ + .min_uV = min * 1000, \ + .uV_step = step * 1000, \ + }, \ + .vol_reg = MAX8925_SDV##_id, \ + .enable_reg = MAX8925_SDCTL##_id, \ +} + +#define MAX8925_LDO(_id, min, max, step) \ +{ \ + .desc = { \ + .name = "LDO" #_id, \ + .ops = &max8925_regulator_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MAX8925_ID_LDO##_id, \ + .owner = THIS_MODULE, \ + .n_voltages = 64, \ + .min_uV = min * 1000, \ + .uV_step = step * 1000, \ + }, \ + .vol_reg = MAX8925_LDOVOUT##_id, \ + .enable_reg = MAX8925_LDOCTL##_id, \ +} + +#ifdef CONFIG_OF +static struct of_regulator_match max8925_regulator_matches[] = { + { .name = "SDV1",}, + { .name = "SDV2",}, + { .name = "SDV3",}, + { .name = "LDO1",}, + { .name = "LDO2",}, + { .name = "LDO3",}, + { .name = "LDO4",}, + { .name = "LDO5",}, + { .name = "LDO6",}, + { .name = "LDO7",}, + { .name = "LDO8",}, + { .name = "LDO9",}, + { .name = "LDO10",}, + { .name = "LDO11",}, + { .name = "LDO12",}, + { .name = "LDO13",}, + { .name = "LDO14",}, + { .name = "LDO15",}, + { .name = "LDO16",}, + { .name = "LDO17",}, + { .name = "LDO18",}, + { .name = "LDO19",}, + { .name = "LDO20",}, +}; +#endif + +static struct max8925_regulator_info max8925_regulator_info[] = { + MAX8925_SDV(1, 637.5, 1425, 12.5), + MAX8925_SDV(2, 650, 2225, 25), + MAX8925_SDV(3, 750, 3900, 50), + + MAX8925_LDO(1, 750, 3900, 50), + MAX8925_LDO(2, 650, 2250, 25), + MAX8925_LDO(3, 650, 2250, 25), + MAX8925_LDO(4, 750, 3900, 50), + MAX8925_LDO(5, 750, 3900, 50), + MAX8925_LDO(6, 750, 3900, 50), + MAX8925_LDO(7, 750, 3900, 50), + MAX8925_LDO(8, 750, 3900, 50), + MAX8925_LDO(9, 750, 3900, 50), + MAX8925_LDO(10, 750, 3900, 50), + MAX8925_LDO(11, 750, 3900, 50), + MAX8925_LDO(12, 750, 3900, 50), + MAX8925_LDO(13, 750, 3900, 50), + MAX8925_LDO(14, 750, 3900, 50), + MAX8925_LDO(15, 750, 3900, 50), + MAX8925_LDO(16, 750, 3900, 50), + MAX8925_LDO(17, 650, 2250, 25), + MAX8925_LDO(18, 650, 2250, 25), + MAX8925_LDO(19, 750, 3900, 50), + MAX8925_LDO(20, 750, 3900, 50), +}; + +#ifdef CONFIG_OF +static int max8925_regulator_dt_init(struct platform_device *pdev, + struct regulator_config *config, + int ridx) +{ + struct device_node *nproot, *np; + int rcount; + + nproot = pdev->dev.parent->of_node; + if (!nproot) + return -ENODEV; + np = of_get_child_by_name(nproot, "regulators"); + if (!np) { + dev_err(&pdev->dev, "failed to find regulators node\n"); + return -ENODEV; + } + + rcount = of_regulator_match(&pdev->dev, np, + &max8925_regulator_matches[ridx], 1); + of_node_put(np); + if (rcount < 0) + return rcount; + config->init_data = max8925_regulator_matches[ridx].init_data; + config->of_node = max8925_regulator_matches[ridx].of_node; + + return 0; +} +#else +#define max8925_regulator_dt_init(x, y, z) (-1) +#endif + +static int max8925_regulator_probe(struct platform_device *pdev) +{ + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct regulator_init_data *pdata = dev_get_platdata(&pdev->dev); + struct regulator_config config = { }; + struct max8925_regulator_info *ri; + struct resource *res; + struct regulator_dev *rdev; + int i, regulator_idx; + + res = platform_get_resource(pdev, IORESOURCE_REG, 0); + if (!res) { + dev_err(&pdev->dev, "No REG resource!\n"); + return -EINVAL; + } + for (i = 0; i < ARRAY_SIZE(max8925_regulator_info); i++) { + ri = &max8925_regulator_info[i]; + if (ri->vol_reg == res->start) { + regulator_idx = i; + break; + } + } + + if (i == ARRAY_SIZE(max8925_regulator_info)) { + dev_err(&pdev->dev, "Failed to find regulator %llu\n", + (unsigned long long)res->start); + return -EINVAL; + } + ri->i2c = chip->i2c; + + config.dev = &pdev->dev; + config.driver_data = ri; + + if (max8925_regulator_dt_init(pdev, &config, regulator_idx)) + if (pdata) + config.init_data = pdata; + + rdev = devm_regulator_register(&pdev->dev, &ri->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + ri->desc.name); + return PTR_ERR(rdev); + } + + platform_set_drvdata(pdev, rdev); + return 0; +} + +static struct platform_driver max8925_regulator_driver = { + .driver = { + .name = "max8925-regulator", + }, + .probe = max8925_regulator_probe, +}; + +static int __init max8925_regulator_init(void) +{ + return platform_driver_register(&max8925_regulator_driver); +} +subsys_initcall(max8925_regulator_init); + +static void __exit max8925_regulator_exit(void) +{ + platform_driver_unregister(&max8925_regulator_driver); +} +module_exit(max8925_regulator_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_DESCRIPTION("Regulator Driver for Maxim 8925 PMIC"); +MODULE_ALIAS("platform:max8925-regulator"); diff --git a/drivers/regulator/max8952.c b/drivers/regulator/max8952.c new file mode 100644 index 000000000..f1e77ed5d --- /dev/null +++ b/drivers/regulator/max8952.c @@ -0,0 +1,353 @@ +/* + * max8952.c - Voltage and current regulation for the Maxim 8952 + * + * Copyright (C) 2010 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/max8952.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/regulator/of_regulator.h> +#include <linux/slab.h> + +/* Registers */ +enum { + MAX8952_REG_MODE0, + MAX8952_REG_MODE1, + MAX8952_REG_MODE2, + MAX8952_REG_MODE3, + MAX8952_REG_CONTROL, + MAX8952_REG_SYNC, + MAX8952_REG_RAMP, + MAX8952_REG_CHIP_ID1, + MAX8952_REG_CHIP_ID2, +}; + +struct max8952_data { + struct i2c_client *client; + struct max8952_platform_data *pdata; + + bool vid0; + bool vid1; +}; + +static int max8952_read_reg(struct max8952_data *max8952, u8 reg) +{ + int ret = i2c_smbus_read_byte_data(max8952->client, reg); + + if (ret > 0) + ret &= 0xff; + + return ret; +} + +static int max8952_write_reg(struct max8952_data *max8952, + u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(max8952->client, reg, value); +} + +static int max8952_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + struct max8952_data *max8952 = rdev_get_drvdata(rdev); + + if (rdev_get_id(rdev) != 0) + return -EINVAL; + + return (max8952->pdata->dvs_mode[selector] * 10 + 770) * 1000; +} + +static int max8952_get_voltage_sel(struct regulator_dev *rdev) +{ + struct max8952_data *max8952 = rdev_get_drvdata(rdev); + u8 vid = 0; + + if (max8952->vid0) + vid += 1; + if (max8952->vid1) + vid += 2; + + return vid; +} + +static int max8952_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct max8952_data *max8952 = rdev_get_drvdata(rdev); + + if (!gpio_is_valid(max8952->pdata->gpio_vid0) || + !gpio_is_valid(max8952->pdata->gpio_vid1)) { + /* DVS not supported */ + return -EPERM; + } + + max8952->vid0 = selector & 0x1; + max8952->vid1 = (selector >> 1) & 0x1; + gpio_set_value(max8952->pdata->gpio_vid0, max8952->vid0); + gpio_set_value(max8952->pdata->gpio_vid1, max8952->vid1); + + return 0; +} + +static const struct regulator_ops max8952_ops = { + .list_voltage = max8952_list_voltage, + .get_voltage_sel = max8952_get_voltage_sel, + .set_voltage_sel = max8952_set_voltage_sel, +}; + +static const struct regulator_desc regulator = { + .name = "MAX8952_VOUT", + .id = 0, + .n_voltages = MAX8952_NUM_DVS_MODE, + .ops = &max8952_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_OF +static const struct of_device_id max8952_dt_match[] = { + { .compatible = "maxim,max8952" }, + {}, +}; +MODULE_DEVICE_TABLE(of, max8952_dt_match); + +static struct max8952_platform_data *max8952_parse_dt(struct device *dev) +{ + struct max8952_platform_data *pd; + struct device_node *np = dev->of_node; + int ret; + int i; + + pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return NULL; + + pd->gpio_vid0 = of_get_named_gpio(np, "max8952,vid-gpios", 0); + pd->gpio_vid1 = of_get_named_gpio(np, "max8952,vid-gpios", 1); + + if (of_property_read_u32(np, "max8952,default-mode", &pd->default_mode)) + dev_warn(dev, "Default mode not specified, assuming 0\n"); + + ret = of_property_read_u32_array(np, "max8952,dvs-mode-microvolt", + pd->dvs_mode, ARRAY_SIZE(pd->dvs_mode)); + if (ret) { + dev_err(dev, "max8952,dvs-mode-microvolt property not specified"); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(pd->dvs_mode); ++i) { + if (pd->dvs_mode[i] < 770000 || pd->dvs_mode[i] > 1400000) { + dev_err(dev, "DVS voltage %d out of range\n", i); + return NULL; + } + pd->dvs_mode[i] = (pd->dvs_mode[i] - 770000) / 10000; + } + + if (of_property_read_u32(np, "max8952,sync-freq", &pd->sync_freq)) + dev_warn(dev, "max8952,sync-freq property not specified, defaulting to 26MHz\n"); + + if (of_property_read_u32(np, "max8952,ramp-speed", &pd->ramp_speed)) + dev_warn(dev, "max8952,ramp-speed property not specified, defaulting to 32mV/us\n"); + + pd->reg_data = of_get_regulator_init_data(dev, np, ®ulator); + if (!pd->reg_data) { + dev_err(dev, "Failed to parse regulator init data\n"); + return NULL; + } + + return pd; +} +#else +static struct max8952_platform_data *max8952_parse_dt(struct device *dev) +{ + return NULL; +} +#endif + +static int max8952_pmic_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct max8952_platform_data *pdata = dev_get_platdata(&client->dev); + struct regulator_config config = { }; + struct max8952_data *max8952; + struct regulator_dev *rdev; + struct gpio_desc *gpiod; + enum gpiod_flags gflags; + + int ret = 0, err = 0; + + if (client->dev.of_node) + pdata = max8952_parse_dt(&client->dev); + + if (!pdata) { + dev_err(&client->dev, "Require the platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + max8952 = devm_kzalloc(&client->dev, sizeof(struct max8952_data), + GFP_KERNEL); + if (!max8952) + return -ENOMEM; + + max8952->client = client; + max8952->pdata = pdata; + + config.dev = &client->dev; + config.init_data = pdata->reg_data; + config.driver_data = max8952; + config.of_node = client->dev.of_node; + + if (pdata->reg_data->constraints.boot_on) + gflags = GPIOD_OUT_HIGH; + else + gflags = GPIOD_OUT_LOW; + gpiod = devm_gpiod_get_optional(&client->dev, + "max8952,en", + gflags); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + if (gpiod) + config.ena_gpiod = gpiod; + + rdev = devm_regulator_register(&client->dev, ®ulator, &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(&client->dev, "regulator init failed (%d)\n", ret); + return ret; + } + + max8952->vid0 = pdata->default_mode & 0x1; + max8952->vid1 = (pdata->default_mode >> 1) & 0x1; + + if (gpio_is_valid(pdata->gpio_vid0) && + gpio_is_valid(pdata->gpio_vid1)) { + unsigned long gpio_flags; + + gpio_flags = max8952->vid0 ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + if (devm_gpio_request_one(&client->dev, pdata->gpio_vid0, + gpio_flags, "MAX8952 VID0")) + err = 1; + + gpio_flags = max8952->vid1 ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + if (devm_gpio_request_one(&client->dev, pdata->gpio_vid1, + gpio_flags, "MAX8952 VID1")) + err = 2; + } else + err = 3; + + if (err) { + dev_warn(&client->dev, "VID0/1 gpio invalid: " + "DVS not available.\n"); + max8952->vid0 = 0; + max8952->vid1 = 0; + /* Mark invalid */ + pdata->gpio_vid0 = -1; + pdata->gpio_vid1 = -1; + + /* Disable Pulldown of EN only */ + max8952_write_reg(max8952, MAX8952_REG_CONTROL, 0x60); + + dev_err(&client->dev, "DVS modes disabled because VID0 and VID1" + " do not have proper controls.\n"); + } else { + /* + * Disable Pulldown on EN, VID0, VID1 to reduce + * leakage current of MAX8952 assuming that MAX8952 + * is turned on (EN==1). Note that without having VID0/1 + * properly connected, turning pulldown off can be + * problematic. Thus, turn this off only when they are + * controllable by GPIO. + */ + max8952_write_reg(max8952, MAX8952_REG_CONTROL, 0x0); + } + + max8952_write_reg(max8952, MAX8952_REG_MODE0, + (max8952_read_reg(max8952, + MAX8952_REG_MODE0) & 0xC0) | + (pdata->dvs_mode[0] & 0x3F)); + max8952_write_reg(max8952, MAX8952_REG_MODE1, + (max8952_read_reg(max8952, + MAX8952_REG_MODE1) & 0xC0) | + (pdata->dvs_mode[1] & 0x3F)); + max8952_write_reg(max8952, MAX8952_REG_MODE2, + (max8952_read_reg(max8952, + MAX8952_REG_MODE2) & 0xC0) | + (pdata->dvs_mode[2] & 0x3F)); + max8952_write_reg(max8952, MAX8952_REG_MODE3, + (max8952_read_reg(max8952, + MAX8952_REG_MODE3) & 0xC0) | + (pdata->dvs_mode[3] & 0x3F)); + + max8952_write_reg(max8952, MAX8952_REG_SYNC, + (max8952_read_reg(max8952, MAX8952_REG_SYNC) & 0x3F) | + ((pdata->sync_freq & 0x3) << 6)); + max8952_write_reg(max8952, MAX8952_REG_RAMP, + (max8952_read_reg(max8952, MAX8952_REG_RAMP) & 0x1F) | + ((pdata->ramp_speed & 0x7) << 5)); + + i2c_set_clientdata(client, max8952); + + return 0; +} + +static const struct i2c_device_id max8952_ids[] = { + { "max8952", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max8952_ids); + +static struct i2c_driver max8952_pmic_driver = { + .probe = max8952_pmic_probe, + .driver = { + .name = "max8952", + .of_match_table = of_match_ptr(max8952_dt_match), + }, + .id_table = max8952_ids, +}; + +static int __init max8952_pmic_init(void) +{ + return i2c_add_driver(&max8952_pmic_driver); +} +subsys_initcall(max8952_pmic_init); + +static void __exit max8952_pmic_exit(void) +{ + i2c_del_driver(&max8952_pmic_driver); +} +module_exit(max8952_pmic_exit); + +MODULE_DESCRIPTION("MAXIM 8952 voltage regulator driver"); +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/max8973-regulator.c b/drivers/regulator/max8973-regulator.c new file mode 100644 index 000000000..7cd493ec6 --- /dev/null +++ b/drivers/regulator/max8973-regulator.c @@ -0,0 +1,852 @@ +/* + * max8973-regulator.c -- Maxim max8973 + * + * Regulator driver for MAXIM 8973 DC-DC step-down switching regulator. + * + * Copyright (c) 2012, NVIDIA Corporation. + * + * Author: Laxman Dewangan <ldewangan@nvidia.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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/max8973-regulator.h> +#include <linux/regulator/of_regulator.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/of_gpio.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/thermal.h> +#include <linux/irq.h> +#include <linux/interrupt.h> + +/* Register definitions */ +#define MAX8973_VOUT 0x0 +#define MAX8973_VOUT_DVS 0x1 +#define MAX8973_CONTROL1 0x2 +#define MAX8973_CONTROL2 0x3 +#define MAX8973_CHIPID1 0x4 +#define MAX8973_CHIPID2 0x5 + +#define MAX8973_MAX_VOUT_REG 2 + +/* MAX8973_VOUT */ +#define MAX8973_VOUT_ENABLE BIT(7) +#define MAX8973_VOUT_MASK 0x7F + +/* MAX8973_VOUT_DVS */ +#define MAX8973_DVS_VOUT_MASK 0x7F + +/* MAX8973_CONTROL1 */ +#define MAX8973_SNS_ENABLE BIT(7) +#define MAX8973_FPWM_EN_M BIT(6) +#define MAX8973_NFSR_ENABLE BIT(5) +#define MAX8973_AD_ENABLE BIT(4) +#define MAX8973_BIAS_ENABLE BIT(3) +#define MAX8973_FREQSHIFT_9PER BIT(2) + +#define MAX8973_RAMP_12mV_PER_US 0x0 +#define MAX8973_RAMP_25mV_PER_US 0x1 +#define MAX8973_RAMP_50mV_PER_US 0x2 +#define MAX8973_RAMP_200mV_PER_US 0x3 +#define MAX8973_RAMP_MASK 0x3 + +/* MAX8973_CONTROL2 */ +#define MAX8973_WDTMR_ENABLE BIT(6) +#define MAX8973_DISCH_ENBABLE BIT(5) +#define MAX8973_FT_ENABLE BIT(4) +#define MAX77621_T_JUNCTION_120 BIT(7) + +#define MAX8973_CKKADV_TRIP_MASK 0xC +#define MAX8973_CKKADV_TRIP_DISABLE 0xC +#define MAX8973_CKKADV_TRIP_75mV_PER_US 0x0 +#define MAX8973_CKKADV_TRIP_150mV_PER_US 0x4 +#define MAX8973_CKKADV_TRIP_75mV_PER_US_HIST_DIS 0x8 +#define MAX8973_CONTROL_CLKADV_TRIP_MASK 0x00030000 + +#define MAX8973_INDUCTOR_MIN_30_PER 0x0 +#define MAX8973_INDUCTOR_NOMINAL 0x1 +#define MAX8973_INDUCTOR_PLUS_30_PER 0x2 +#define MAX8973_INDUCTOR_PLUS_60_PER 0x3 +#define MAX8973_CONTROL_INDUCTOR_VALUE_MASK 0x00300000 + +#define MAX8973_MIN_VOLATGE 606250 +#define MAX8973_MAX_VOLATGE 1400000 +#define MAX8973_VOLATGE_STEP 6250 +#define MAX8973_BUCK_N_VOLTAGE 0x80 + +#define MAX77621_CHIPID_TJINT_S BIT(0) + +#define MAX77621_NORMAL_OPERATING_TEMP 100000 +#define MAX77621_TJINT_WARNING_TEMP_120 120000 +#define MAX77621_TJINT_WARNING_TEMP_140 140000 + +enum device_id { + MAX8973, + MAX77621 +}; + +/* Maxim 8973 chip information */ +struct max8973_chip { + struct device *dev; + struct regulator_desc desc; + struct regmap *regmap; + bool enable_external_control; + int dvs_gpio; + int lru_index[MAX8973_MAX_VOUT_REG]; + int curr_vout_val[MAX8973_MAX_VOUT_REG]; + int curr_vout_reg; + int curr_gpio_val; + struct regulator_ops ops; + enum device_id id; + int junction_temp_warning; + int irq; + struct thermal_zone_device *tz_device; +}; + +/* + * find_voltage_set_register: Find new voltage configuration register (VOUT). + * The finding of the new VOUT register will be based on the LRU mechanism. + * Each VOUT register will have different voltage configured . This + * Function will look if any of the VOUT register have requested voltage set + * or not. + * - If it is already there then it will make that register as most + * recently used and return as found so that caller need not to set + * the VOUT register but need to set the proper gpios to select this + * VOUT register. + * - If requested voltage is not found then it will use the least + * recently mechanism to get new VOUT register for new configuration + * and will return not_found so that caller need to set new VOUT + * register and then gpios (both). + */ +static bool find_voltage_set_register(struct max8973_chip *tps, + int req_vsel, int *vout_reg, int *gpio_val) +{ + int i; + bool found = false; + int new_vout_reg = tps->lru_index[MAX8973_MAX_VOUT_REG - 1]; + int found_index = MAX8973_MAX_VOUT_REG - 1; + + for (i = 0; i < MAX8973_MAX_VOUT_REG; ++i) { + if (tps->curr_vout_val[tps->lru_index[i]] == req_vsel) { + new_vout_reg = tps->lru_index[i]; + found_index = i; + found = true; + goto update_lru_index; + } + } + +update_lru_index: + for (i = found_index; i > 0; i--) + tps->lru_index[i] = tps->lru_index[i - 1]; + + tps->lru_index[0] = new_vout_reg; + *gpio_val = new_vout_reg; + *vout_reg = MAX8973_VOUT + new_vout_reg; + return found; +} + +static int max8973_dcdc_get_voltage_sel(struct regulator_dev *rdev) +{ + struct max8973_chip *max = rdev_get_drvdata(rdev); + unsigned int data; + int ret; + + ret = regmap_read(max->regmap, max->curr_vout_reg, &data); + if (ret < 0) { + dev_err(max->dev, "register %d read failed, err = %d\n", + max->curr_vout_reg, ret); + return ret; + } + return data & MAX8973_VOUT_MASK; +} + +static int max8973_dcdc_set_voltage_sel(struct regulator_dev *rdev, + unsigned vsel) +{ + struct max8973_chip *max = rdev_get_drvdata(rdev); + int ret; + bool found = false; + int vout_reg = max->curr_vout_reg; + int gpio_val = max->curr_gpio_val; + + /* + * If gpios are available to select the VOUT register then least + * recently used register for new configuration. + */ + if (gpio_is_valid(max->dvs_gpio)) + found = find_voltage_set_register(max, vsel, + &vout_reg, &gpio_val); + + if (!found) { + ret = regmap_update_bits(max->regmap, vout_reg, + MAX8973_VOUT_MASK, vsel); + if (ret < 0) { + dev_err(max->dev, "register %d update failed, err %d\n", + vout_reg, ret); + return ret; + } + max->curr_vout_reg = vout_reg; + max->curr_vout_val[gpio_val] = vsel; + } + + /* Select proper VOUT register vio gpios */ + if (gpio_is_valid(max->dvs_gpio)) { + gpio_set_value_cansleep(max->dvs_gpio, gpio_val & 0x1); + max->curr_gpio_val = gpio_val; + } + return 0; +} + +static int max8973_dcdc_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct max8973_chip *max = rdev_get_drvdata(rdev); + int ret; + int pwm; + + /* Enable force PWM mode in FAST mode only. */ + switch (mode) { + case REGULATOR_MODE_FAST: + pwm = MAX8973_FPWM_EN_M; + break; + + case REGULATOR_MODE_NORMAL: + pwm = 0; + break; + + default: + return -EINVAL; + } + + ret = regmap_update_bits(max->regmap, MAX8973_CONTROL1, + MAX8973_FPWM_EN_M, pwm); + if (ret < 0) + dev_err(max->dev, "register %d update failed, err %d\n", + MAX8973_CONTROL1, ret); + return ret; +} + +static unsigned int max8973_dcdc_get_mode(struct regulator_dev *rdev) +{ + struct max8973_chip *max = rdev_get_drvdata(rdev); + unsigned int data; + int ret; + + ret = regmap_read(max->regmap, MAX8973_CONTROL1, &data); + if (ret < 0) { + dev_err(max->dev, "register %d read failed, err %d\n", + MAX8973_CONTROL1, ret); + return ret; + } + return (data & MAX8973_FPWM_EN_M) ? + REGULATOR_MODE_FAST : REGULATOR_MODE_NORMAL; +} + +static int max8973_set_ramp_delay(struct regulator_dev *rdev, + int ramp_delay) +{ + struct max8973_chip *max = rdev_get_drvdata(rdev); + unsigned int control; + int ret; + + /* Set ramp delay */ + if (ramp_delay <= 12000) + control = MAX8973_RAMP_12mV_PER_US; + else if (ramp_delay <= 25000) + control = MAX8973_RAMP_25mV_PER_US; + else if (ramp_delay <= 50000) + control = MAX8973_RAMP_50mV_PER_US; + else if (ramp_delay <= 200000) + control = MAX8973_RAMP_200mV_PER_US; + else + return -EINVAL; + + ret = regmap_update_bits(max->regmap, MAX8973_CONTROL1, + MAX8973_RAMP_MASK, control); + if (ret < 0) + dev_err(max->dev, "register %d update failed, %d", + MAX8973_CONTROL1, ret); + return ret; +} + +static int max8973_set_current_limit(struct regulator_dev *rdev, + int min_ua, int max_ua) +{ + struct max8973_chip *max = rdev_get_drvdata(rdev); + unsigned int val; + int ret; + + if (max_ua <= 9000000) + val = MAX8973_CKKADV_TRIP_75mV_PER_US; + else if (max_ua <= 12000000) + val = MAX8973_CKKADV_TRIP_150mV_PER_US; + else + val = MAX8973_CKKADV_TRIP_DISABLE; + + ret = regmap_update_bits(max->regmap, MAX8973_CONTROL2, + MAX8973_CKKADV_TRIP_MASK, val); + if (ret < 0) { + dev_err(max->dev, "register %d update failed: %d\n", + MAX8973_CONTROL2, ret); + return ret; + } + return 0; +} + +static int max8973_get_current_limit(struct regulator_dev *rdev) +{ + struct max8973_chip *max = rdev_get_drvdata(rdev); + unsigned int control2; + int ret; + + ret = regmap_read(max->regmap, MAX8973_CONTROL2, &control2); + if (ret < 0) { + dev_err(max->dev, "register %d read failed: %d\n", + MAX8973_CONTROL2, ret); + return ret; + } + switch (control2 & MAX8973_CKKADV_TRIP_MASK) { + case MAX8973_CKKADV_TRIP_DISABLE: + return 15000000; + case MAX8973_CKKADV_TRIP_150mV_PER_US: + return 12000000; + case MAX8973_CKKADV_TRIP_75mV_PER_US: + return 9000000; + default: + break; + } + return 9000000; +} + +static const struct regulator_ops max8973_dcdc_ops = { + .get_voltage_sel = max8973_dcdc_get_voltage_sel, + .set_voltage_sel = max8973_dcdc_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .set_mode = max8973_dcdc_set_mode, + .get_mode = max8973_dcdc_get_mode, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = max8973_set_ramp_delay, +}; + +static int max8973_init_dcdc(struct max8973_chip *max, + struct max8973_regulator_platform_data *pdata) +{ + int ret; + uint8_t control1 = 0; + uint8_t control2 = 0; + unsigned int data; + + ret = regmap_read(max->regmap, MAX8973_CONTROL1, &data); + if (ret < 0) { + dev_err(max->dev, "register %d read failed, err = %d", + MAX8973_CONTROL1, ret); + return ret; + } + control1 = data & MAX8973_RAMP_MASK; + switch (control1) { + case MAX8973_RAMP_12mV_PER_US: + max->desc.ramp_delay = 12000; + break; + case MAX8973_RAMP_25mV_PER_US: + max->desc.ramp_delay = 25000; + break; + case MAX8973_RAMP_50mV_PER_US: + max->desc.ramp_delay = 50000; + break; + case MAX8973_RAMP_200mV_PER_US: + max->desc.ramp_delay = 200000; + break; + } + + if (pdata->control_flags & MAX8973_CONTROL_REMOTE_SENSE_ENABLE) + control1 |= MAX8973_SNS_ENABLE; + + if (!(pdata->control_flags & MAX8973_CONTROL_FALLING_SLEW_RATE_ENABLE)) + control1 |= MAX8973_NFSR_ENABLE; + + if (pdata->control_flags & MAX8973_CONTROL_OUTPUT_ACTIVE_DISCH_ENABLE) + control1 |= MAX8973_AD_ENABLE; + + if (pdata->control_flags & MAX8973_CONTROL_BIAS_ENABLE) { + control1 |= MAX8973_BIAS_ENABLE; + max->desc.enable_time = 20; + } else { + max->desc.enable_time = 240; + } + + if (pdata->control_flags & MAX8973_CONTROL_FREQ_SHIFT_9PER_ENABLE) + control1 |= MAX8973_FREQSHIFT_9PER; + + if ((pdata->junction_temp_warning == MAX77621_TJINT_WARNING_TEMP_120) && + (max->id == MAX77621)) + control2 |= MAX77621_T_JUNCTION_120; + + if (!(pdata->control_flags & MAX8973_CONTROL_PULL_DOWN_ENABLE)) + control2 |= MAX8973_DISCH_ENBABLE; + + /* Clock advance trip configuration */ + switch (pdata->control_flags & MAX8973_CONTROL_CLKADV_TRIP_MASK) { + case MAX8973_CONTROL_CLKADV_TRIP_DISABLED: + control2 |= MAX8973_CKKADV_TRIP_DISABLE; + break; + + case MAX8973_CONTROL_CLKADV_TRIP_75mV_PER_US: + control2 |= MAX8973_CKKADV_TRIP_75mV_PER_US; + break; + + case MAX8973_CONTROL_CLKADV_TRIP_150mV_PER_US: + control2 |= MAX8973_CKKADV_TRIP_150mV_PER_US; + break; + + case MAX8973_CONTROL_CLKADV_TRIP_75mV_PER_US_HIST_DIS: + control2 |= MAX8973_CKKADV_TRIP_75mV_PER_US_HIST_DIS; + break; + } + + /* Configure inductor value */ + switch (pdata->control_flags & MAX8973_CONTROL_INDUCTOR_VALUE_MASK) { + case MAX8973_CONTROL_INDUCTOR_VALUE_NOMINAL: + control2 |= MAX8973_INDUCTOR_NOMINAL; + break; + + case MAX8973_CONTROL_INDUCTOR_VALUE_MINUS_30_PER: + control2 |= MAX8973_INDUCTOR_MIN_30_PER; + break; + + case MAX8973_CONTROL_INDUCTOR_VALUE_PLUS_30_PER: + control2 |= MAX8973_INDUCTOR_PLUS_30_PER; + break; + + case MAX8973_CONTROL_INDUCTOR_VALUE_PLUS_60_PER: + control2 |= MAX8973_INDUCTOR_PLUS_60_PER; + break; + } + + ret = regmap_write(max->regmap, MAX8973_CONTROL1, control1); + if (ret < 0) { + dev_err(max->dev, "register %d write failed, err = %d", + MAX8973_CONTROL1, ret); + return ret; + } + + ret = regmap_write(max->regmap, MAX8973_CONTROL2, control2); + if (ret < 0) { + dev_err(max->dev, "register %d write failed, err = %d", + MAX8973_CONTROL2, ret); + return ret; + } + + /* If external control is enabled then disable EN bit */ + if (max->enable_external_control && (max->id == MAX8973)) { + ret = regmap_update_bits(max->regmap, MAX8973_VOUT, + MAX8973_VOUT_ENABLE, 0); + if (ret < 0) + dev_err(max->dev, "register %d update failed, err = %d", + MAX8973_VOUT, ret); + } + return ret; +} + +static int max8973_thermal_read_temp(void *data, int *temp) +{ + struct max8973_chip *mchip = data; + unsigned int val; + int ret; + + ret = regmap_read(mchip->regmap, MAX8973_CHIPID1, &val); + if (ret < 0) { + dev_err(mchip->dev, "Failed to read register CHIPID1, %d", ret); + return ret; + } + + /* +1 degC to trigger cool devive */ + if (val & MAX77621_CHIPID_TJINT_S) + *temp = mchip->junction_temp_warning + 1000; + else + *temp = MAX77621_NORMAL_OPERATING_TEMP; + + return 0; +} + +static irqreturn_t max8973_thermal_irq(int irq, void *data) +{ + struct max8973_chip *mchip = data; + + thermal_zone_device_update(mchip->tz_device, + THERMAL_EVENT_UNSPECIFIED); + + return IRQ_HANDLED; +} + +static const struct thermal_zone_of_device_ops max77621_tz_ops = { + .get_temp = max8973_thermal_read_temp, +}; + +static int max8973_thermal_init(struct max8973_chip *mchip) +{ + struct thermal_zone_device *tzd; + struct irq_data *irq_data; + unsigned long irq_flags = 0; + int ret; + + if (mchip->id != MAX77621) + return 0; + + tzd = devm_thermal_zone_of_sensor_register(mchip->dev, 0, mchip, + &max77621_tz_ops); + if (IS_ERR(tzd)) { + ret = PTR_ERR(tzd); + dev_err(mchip->dev, "Failed to register thermal sensor: %d\n", + ret); + return ret; + } + + if (mchip->irq <= 0) + return 0; + + irq_data = irq_get_irq_data(mchip->irq); + if (irq_data) + irq_flags = irqd_get_trigger_type(irq_data); + + ret = devm_request_threaded_irq(mchip->dev, mchip->irq, NULL, + max8973_thermal_irq, + IRQF_ONESHOT | IRQF_SHARED | irq_flags, + dev_name(mchip->dev), mchip); + if (ret < 0) { + dev_err(mchip->dev, "Failed to request irq %d, %d\n", + mchip->irq, ret); + return ret; + } + + return 0; +} + +static const struct regmap_config max8973_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX8973_CHIPID2, + .cache_type = REGCACHE_RBTREE, +}; + +static struct max8973_regulator_platform_data *max8973_parse_dt( + struct device *dev) +{ + struct max8973_regulator_platform_data *pdata; + struct device_node *np = dev->of_node; + int ret; + u32 pval; + bool etr_enable; + bool etr_sensitivity_high; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + pdata->enable_ext_control = of_property_read_bool(np, + "maxim,externally-enable"); + pdata->dvs_gpio = of_get_named_gpio(np, "maxim,dvs-gpio", 0); + + ret = of_property_read_u32(np, "maxim,dvs-default-state", &pval); + if (!ret) + pdata->dvs_def_state = pval; + + if (of_property_read_bool(np, "maxim,enable-remote-sense")) + pdata->control_flags |= MAX8973_CONTROL_REMOTE_SENSE_ENABLE; + + if (of_property_read_bool(np, "maxim,enable-falling-slew-rate")) + pdata->control_flags |= + MAX8973_CONTROL_FALLING_SLEW_RATE_ENABLE; + + if (of_property_read_bool(np, "maxim,enable-active-discharge")) + pdata->control_flags |= + MAX8973_CONTROL_OUTPUT_ACTIVE_DISCH_ENABLE; + + if (of_property_read_bool(np, "maxim,enable-frequency-shift")) + pdata->control_flags |= MAX8973_CONTROL_FREQ_SHIFT_9PER_ENABLE; + + if (of_property_read_bool(np, "maxim,enable-bias-control")) + pdata->control_flags |= MAX8973_CONTROL_BIAS_ENABLE; + + etr_enable = of_property_read_bool(np, "maxim,enable-etr"); + etr_sensitivity_high = of_property_read_bool(np, + "maxim,enable-high-etr-sensitivity"); + if (etr_sensitivity_high) + etr_enable = true; + + if (etr_enable) { + if (etr_sensitivity_high) + pdata->control_flags |= + MAX8973_CONTROL_CLKADV_TRIP_75mV_PER_US; + else + pdata->control_flags |= + MAX8973_CONTROL_CLKADV_TRIP_150mV_PER_US; + } else { + pdata->control_flags |= MAX8973_CONTROL_CLKADV_TRIP_DISABLED; + } + + pdata->junction_temp_warning = MAX77621_TJINT_WARNING_TEMP_140; + ret = of_property_read_u32(np, "junction-warn-millicelsius", &pval); + if (!ret && (pval <= MAX77621_TJINT_WARNING_TEMP_120)) + pdata->junction_temp_warning = MAX77621_TJINT_WARNING_TEMP_120; + + return pdata; +} + +static const struct of_device_id of_max8973_match_tbl[] = { + { .compatible = "maxim,max8973", .data = (void *)MAX8973, }, + { .compatible = "maxim,max77621", .data = (void *)MAX77621, }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_max8973_match_tbl); + +static int max8973_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max8973_regulator_platform_data *pdata; + struct regulator_init_data *ridata; + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct max8973_chip *max; + bool pdata_from_dt = false; + unsigned int chip_id; + struct gpio_desc *gpiod; + enum gpiod_flags gflags; + int ret; + + pdata = dev_get_platdata(&client->dev); + + if (!pdata && client->dev.of_node) { + pdata = max8973_parse_dt(&client->dev); + pdata_from_dt = true; + } + + if (!pdata) { + dev_err(&client->dev, "No Platform data"); + return -EIO; + } + + if (pdata->dvs_gpio == -EPROBE_DEFER) + return -EPROBE_DEFER; + + max = devm_kzalloc(&client->dev, sizeof(*max), GFP_KERNEL); + if (!max) + return -ENOMEM; + + max->regmap = devm_regmap_init_i2c(client, &max8973_regmap_config); + if (IS_ERR(max->regmap)) { + ret = PTR_ERR(max->regmap); + dev_err(&client->dev, "regmap init failed, err %d\n", ret); + return ret; + } + + if (client->dev.of_node) { + const struct of_device_id *match; + + match = of_match_device(of_match_ptr(of_max8973_match_tbl), + &client->dev); + if (!match) + return -ENODATA; + max->id = (u32)((uintptr_t)match->data); + } else { + max->id = id->driver_data; + } + + ret = regmap_read(max->regmap, MAX8973_CHIPID1, &chip_id); + if (ret < 0) { + dev_err(&client->dev, "register CHIPID1 read failed, %d", ret); + return ret; + } + + dev_info(&client->dev, "CHIP-ID OTP: 0x%02x ID_M: 0x%02x\n", + (chip_id >> 4) & 0xF, (chip_id >> 1) & 0x7); + + i2c_set_clientdata(client, max); + max->ops = max8973_dcdc_ops; + max->dev = &client->dev; + max->desc.name = id->name; + max->desc.id = 0; + max->desc.ops = &max->ops; + max->desc.type = REGULATOR_VOLTAGE; + max->desc.owner = THIS_MODULE; + max->desc.min_uV = MAX8973_MIN_VOLATGE; + max->desc.uV_step = MAX8973_VOLATGE_STEP; + max->desc.n_voltages = MAX8973_BUCK_N_VOLTAGE; + + max->dvs_gpio = (pdata->dvs_gpio) ? pdata->dvs_gpio : -EINVAL; + max->enable_external_control = pdata->enable_ext_control; + max->curr_gpio_val = pdata->dvs_def_state; + max->curr_vout_reg = MAX8973_VOUT + pdata->dvs_def_state; + max->junction_temp_warning = pdata->junction_temp_warning; + + max->lru_index[0] = max->curr_vout_reg; + + if (gpio_is_valid(max->dvs_gpio)) { + int gpio_flags; + int i; + + gpio_flags = (pdata->dvs_def_state) ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + ret = devm_gpio_request_one(&client->dev, max->dvs_gpio, + gpio_flags, "max8973-dvs"); + if (ret) { + dev_err(&client->dev, + "gpio_request for gpio %d failed, err = %d\n", + max->dvs_gpio, ret); + return ret; + } + + /* + * Initialize the lru index with vout_reg id + * The index 0 will be most recently used and + * set with the max->curr_vout_reg */ + for (i = 0; i < MAX8973_MAX_VOUT_REG; ++i) + max->lru_index[i] = i; + max->lru_index[0] = max->curr_vout_reg; + max->lru_index[max->curr_vout_reg] = 0; + } else { + /* + * If there is no DVS GPIO, the VOUT register + * address is fixed. + */ + max->ops.set_voltage_sel = regulator_set_voltage_sel_regmap; + max->ops.get_voltage_sel = regulator_get_voltage_sel_regmap; + max->desc.vsel_reg = max->curr_vout_reg; + max->desc.vsel_mask = MAX8973_VOUT_MASK; + } + + if (pdata_from_dt) + pdata->reg_init_data = of_get_regulator_init_data(&client->dev, + client->dev.of_node, &max->desc); + + ridata = pdata->reg_init_data; + switch (max->id) { + case MAX8973: + if (!pdata->enable_ext_control) { + max->desc.enable_reg = MAX8973_VOUT; + max->desc.enable_mask = MAX8973_VOUT_ENABLE; + max->ops.enable = regulator_enable_regmap; + max->ops.disable = regulator_disable_regmap; + max->ops.is_enabled = regulator_is_enabled_regmap; + break; + } + + if (ridata && (ridata->constraints.always_on || + ridata->constraints.boot_on)) + gflags = GPIOD_OUT_HIGH; + else + gflags = GPIOD_OUT_LOW; + gpiod = devm_gpiod_get_optional(&client->dev, + "maxim,enable", + gflags); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + if (gpiod) { + config.ena_gpiod = gpiod; + max->enable_external_control = true; + } + + break; + + case MAX77621: + /* + * We do not let the core switch this regulator on/off, + * we just leave it on. + */ + gpiod = devm_gpiod_get_optional(&client->dev, + "maxim,enable", + GPIOD_OUT_HIGH); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + if (gpiod) + max->enable_external_control = true; + + max->desc.enable_reg = MAX8973_VOUT; + max->desc.enable_mask = MAX8973_VOUT_ENABLE; + max->ops.enable = regulator_enable_regmap; + max->ops.disable = regulator_disable_regmap; + max->ops.is_enabled = regulator_is_enabled_regmap; + max->ops.set_current_limit = max8973_set_current_limit; + max->ops.get_current_limit = max8973_get_current_limit; + break; + default: + break; + } + + ret = max8973_init_dcdc(max, pdata); + if (ret < 0) { + dev_err(max->dev, "Max8973 Init failed, err = %d\n", ret); + return ret; + } + + config.dev = &client->dev; + config.init_data = pdata->reg_init_data; + config.driver_data = max; + config.of_node = client->dev.of_node; + config.regmap = max->regmap; + + /* Register the regulators */ + rdev = devm_regulator_register(&client->dev, &max->desc, &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(max->dev, "regulator register failed, err %d\n", ret); + return ret; + } + + max8973_thermal_init(max); + return 0; +} + +static const struct i2c_device_id max8973_id[] = { + {.name = "max8973", .driver_data = MAX8973}, + {.name = "max77621", .driver_data = MAX77621}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, max8973_id); + +static struct i2c_driver max8973_i2c_driver = { + .driver = { + .name = "max8973", + .of_match_table = of_max8973_match_tbl, + }, + .probe = max8973_probe, + .id_table = max8973_id, +}; + +static int __init max8973_init(void) +{ + return i2c_add_driver(&max8973_i2c_driver); +} +subsys_initcall(max8973_init); + +static void __exit max8973_cleanup(void) +{ + i2c_del_driver(&max8973_i2c_driver); +} +module_exit(max8973_cleanup); + +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_DESCRIPTION("MAX8973 voltage regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/max8997-regulator.c b/drivers/regulator/max8997-regulator.c new file mode 100644 index 000000000..ad0c806b0 --- /dev/null +++ b/drivers/regulator/max8997-regulator.c @@ -0,0 +1,1224 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// max8997.c - Regulator driver for the Maxim 8997/8966 +// +// Copyright (C) 2011 Samsung Electronics +// MyungJoo Ham <myungjoo.ham@samsung.com> +// +// This driver is based on max8998.c + +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/max8997.h> +#include <linux/mfd/max8997-private.h> +#include <linux/regulator/of_regulator.h> + +struct max8997_data { + struct device *dev; + struct max8997_dev *iodev; + int num_regulators; + int ramp_delay; /* in mV/us */ + + bool buck1_gpiodvs; + bool buck2_gpiodvs; + bool buck5_gpiodvs; + u8 buck1_vol[8]; + u8 buck2_vol[8]; + u8 buck5_vol[8]; + int buck125_gpios[3]; + int buck125_gpioindex; + bool ignore_gpiodvs_side_effect; + + u8 saved_states[MAX8997_REG_MAX]; +}; + +static const unsigned int safeoutvolt[] = { + 4850000, + 4900000, + 4950000, + 3300000, +}; + +static inline void max8997_set_gpio(struct max8997_data *max8997) +{ + int set3 = (max8997->buck125_gpioindex) & 0x1; + int set2 = ((max8997->buck125_gpioindex) >> 1) & 0x1; + int set1 = ((max8997->buck125_gpioindex) >> 2) & 0x1; + + gpio_set_value(max8997->buck125_gpios[0], set1); + gpio_set_value(max8997->buck125_gpios[1], set2); + gpio_set_value(max8997->buck125_gpios[2], set3); +} + +struct voltage_map_desc { + int min; + int max; + int step; +}; + +/* Voltage maps in uV */ +static const struct voltage_map_desc ldo_voltage_map_desc = { + .min = 800000, .max = 3950000, .step = 50000, +}; /* LDO1 ~ 18, 21 all */ + +static const struct voltage_map_desc buck1245_voltage_map_desc = { + .min = 650000, .max = 2225000, .step = 25000, +}; /* Buck1, 2, 4, 5 */ + +static const struct voltage_map_desc buck37_voltage_map_desc = { + .min = 750000, .max = 3900000, .step = 50000, +}; /* Buck3, 7 */ + +/* current map in uA */ +static const struct voltage_map_desc charger_current_map_desc = { + .min = 200000, .max = 950000, .step = 50000, +}; + +static const struct voltage_map_desc topoff_current_map_desc = { + .min = 50000, .max = 200000, .step = 10000, +}; + +static const struct voltage_map_desc *reg_voltage_map[] = { + [MAX8997_LDO1] = &ldo_voltage_map_desc, + [MAX8997_LDO2] = &ldo_voltage_map_desc, + [MAX8997_LDO3] = &ldo_voltage_map_desc, + [MAX8997_LDO4] = &ldo_voltage_map_desc, + [MAX8997_LDO5] = &ldo_voltage_map_desc, + [MAX8997_LDO6] = &ldo_voltage_map_desc, + [MAX8997_LDO7] = &ldo_voltage_map_desc, + [MAX8997_LDO8] = &ldo_voltage_map_desc, + [MAX8997_LDO9] = &ldo_voltage_map_desc, + [MAX8997_LDO10] = &ldo_voltage_map_desc, + [MAX8997_LDO11] = &ldo_voltage_map_desc, + [MAX8997_LDO12] = &ldo_voltage_map_desc, + [MAX8997_LDO13] = &ldo_voltage_map_desc, + [MAX8997_LDO14] = &ldo_voltage_map_desc, + [MAX8997_LDO15] = &ldo_voltage_map_desc, + [MAX8997_LDO16] = &ldo_voltage_map_desc, + [MAX8997_LDO17] = &ldo_voltage_map_desc, + [MAX8997_LDO18] = &ldo_voltage_map_desc, + [MAX8997_LDO21] = &ldo_voltage_map_desc, + [MAX8997_BUCK1] = &buck1245_voltage_map_desc, + [MAX8997_BUCK2] = &buck1245_voltage_map_desc, + [MAX8997_BUCK3] = &buck37_voltage_map_desc, + [MAX8997_BUCK4] = &buck1245_voltage_map_desc, + [MAX8997_BUCK5] = &buck1245_voltage_map_desc, + [MAX8997_BUCK6] = NULL, + [MAX8997_BUCK7] = &buck37_voltage_map_desc, + [MAX8997_EN32KHZ_AP] = NULL, + [MAX8997_EN32KHZ_CP] = NULL, + [MAX8997_ENVICHG] = NULL, + [MAX8997_ESAFEOUT1] = NULL, + [MAX8997_ESAFEOUT2] = NULL, + [MAX8997_CHARGER_CV] = NULL, + [MAX8997_CHARGER] = &charger_current_map_desc, + [MAX8997_CHARGER_TOPOFF] = &topoff_current_map_desc, +}; + +static int max8997_list_voltage_charger_cv(struct regulator_dev *rdev, + unsigned int selector) +{ + int rid = rdev_get_id(rdev); + + if (rid != MAX8997_CHARGER_CV) + goto err; + + switch (selector) { + case 0x00: + return 4200000; + case 0x01 ... 0x0E: + return 4000000 + 20000 * (selector - 0x01); + case 0x0F: + return 4350000; + default: + return -EINVAL; + } +err: + return -EINVAL; +} + +static int max8997_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + const struct voltage_map_desc *desc; + int rid = rdev_get_id(rdev); + int val; + + if (rid < 0 || rid >= ARRAY_SIZE(reg_voltage_map)) + return -EINVAL; + + desc = reg_voltage_map[rid]; + if (desc == NULL) + return -EINVAL; + + val = desc->min + desc->step * selector; + if (val > desc->max) + return -EINVAL; + + return val; +} + +static int max8997_get_enable_register(struct regulator_dev *rdev, + int *reg, int *mask, int *pattern) +{ + int rid = rdev_get_id(rdev); + + switch (rid) { + case MAX8997_LDO1 ... MAX8997_LDO21: + *reg = MAX8997_REG_LDO1CTRL + (rid - MAX8997_LDO1); + *mask = 0xC0; + *pattern = 0xC0; + break; + case MAX8997_BUCK1: + *reg = MAX8997_REG_BUCK1CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK2: + *reg = MAX8997_REG_BUCK2CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK3: + *reg = MAX8997_REG_BUCK3CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK4: + *reg = MAX8997_REG_BUCK4CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK5: + *reg = MAX8997_REG_BUCK5CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK6: + *reg = MAX8997_REG_BUCK6CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_BUCK7: + *reg = MAX8997_REG_BUCK7CTRL; + *mask = 0x01; + *pattern = 0x01; + break; + case MAX8997_EN32KHZ_AP ... MAX8997_EN32KHZ_CP: + *reg = MAX8997_REG_MAINCON1; + *mask = 0x01 << (rid - MAX8997_EN32KHZ_AP); + *pattern = 0x01 << (rid - MAX8997_EN32KHZ_AP); + break; + case MAX8997_ENVICHG: + *reg = MAX8997_REG_MBCCTRL1; + *mask = 0x80; + *pattern = 0x80; + break; + case MAX8997_ESAFEOUT1 ... MAX8997_ESAFEOUT2: + *reg = MAX8997_REG_SAFEOUTCTRL; + *mask = 0x40 << (rid - MAX8997_ESAFEOUT1); + *pattern = 0x40 << (rid - MAX8997_ESAFEOUT1); + break; + case MAX8997_CHARGER: + *reg = MAX8997_REG_MBCCTRL2; + *mask = 0x40; + *pattern = 0x40; + break; + default: + /* Not controllable or not exists */ + return -EINVAL; + } + + return 0; +} + +static int max8997_reg_is_enabled(struct regulator_dev *rdev) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int ret, reg, mask, pattern; + u8 val; + + ret = max8997_get_enable_register(rdev, ®, &mask, &pattern); + if (ret) + return ret; + + ret = max8997_read_reg(i2c, reg, &val); + if (ret) + return ret; + + return (val & mask) == pattern; +} + +static int max8997_reg_enable(struct regulator_dev *rdev) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int ret, reg, mask, pattern; + + ret = max8997_get_enable_register(rdev, ®, &mask, &pattern); + if (ret) + return ret; + + return max8997_update_reg(i2c, reg, pattern, mask); +} + +static int max8997_reg_disable(struct regulator_dev *rdev) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int ret, reg, mask, pattern; + + ret = max8997_get_enable_register(rdev, ®, &mask, &pattern); + if (ret) + return ret; + + return max8997_update_reg(i2c, reg, ~pattern, mask); +} + +static int max8997_get_voltage_register(struct regulator_dev *rdev, + int *_reg, int *_shift, int *_mask) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + int rid = rdev_get_id(rdev); + int reg, shift = 0, mask = 0x3f; + + switch (rid) { + case MAX8997_LDO1 ... MAX8997_LDO21: + reg = MAX8997_REG_LDO1CTRL + (rid - MAX8997_LDO1); + break; + case MAX8997_BUCK1: + reg = MAX8997_REG_BUCK1DVS1; + if (max8997->buck1_gpiodvs) + reg += max8997->buck125_gpioindex; + break; + case MAX8997_BUCK2: + reg = MAX8997_REG_BUCK2DVS1; + if (max8997->buck2_gpiodvs) + reg += max8997->buck125_gpioindex; + break; + case MAX8997_BUCK3: + reg = MAX8997_REG_BUCK3DVS; + break; + case MAX8997_BUCK4: + reg = MAX8997_REG_BUCK4DVS; + break; + case MAX8997_BUCK5: + reg = MAX8997_REG_BUCK5DVS1; + if (max8997->buck5_gpiodvs) + reg += max8997->buck125_gpioindex; + break; + case MAX8997_BUCK7: + reg = MAX8997_REG_BUCK7DVS; + break; + case MAX8997_ESAFEOUT1 ... MAX8997_ESAFEOUT2: + reg = MAX8997_REG_SAFEOUTCTRL; + shift = (rid == MAX8997_ESAFEOUT2) ? 2 : 0; + mask = 0x3; + break; + case MAX8997_CHARGER_CV: + reg = MAX8997_REG_MBCCTRL3; + shift = 0; + mask = 0xf; + break; + case MAX8997_CHARGER: + reg = MAX8997_REG_MBCCTRL4; + shift = 0; + mask = 0xf; + break; + case MAX8997_CHARGER_TOPOFF: + reg = MAX8997_REG_MBCCTRL5; + shift = 0; + mask = 0xf; + break; + default: + return -EINVAL; + } + + *_reg = reg; + *_shift = shift; + *_mask = mask; + + return 0; +} + +static int max8997_get_voltage_sel(struct regulator_dev *rdev) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int reg, shift, mask, ret; + u8 val; + + ret = max8997_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + ret = max8997_read_reg(i2c, reg, &val); + if (ret) + return ret; + + val >>= shift; + val &= mask; + + return val; +} + +static inline int max8997_get_voltage_proper_val( + const struct voltage_map_desc *desc, + int min_vol, int max_vol) +{ + int i; + + if (desc == NULL) + return -EINVAL; + + if (max_vol < desc->min || min_vol > desc->max) + return -EINVAL; + + if (min_vol < desc->min) + min_vol = desc->min; + + i = DIV_ROUND_UP(min_vol - desc->min, desc->step); + + if (desc->min + desc->step * i > max_vol) + return -EINVAL; + + return i; +} + +static int max8997_set_voltage_charger_cv(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int rid = rdev_get_id(rdev); + int lb, ub; + int reg, shift = 0, mask, ret = 0; + u8 val = 0x0; + + if (rid != MAX8997_CHARGER_CV) + return -EINVAL; + + ret = max8997_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + if (max_uV < 4000000 || min_uV > 4350000) + return -EINVAL; + + if (min_uV <= 4000000) + val = 0x1; + else if (min_uV <= 4200000 && max_uV >= 4200000) + val = 0x0; + else { + lb = (min_uV - 4000001) / 20000 + 2; + ub = (max_uV - 4000000) / 20000 + 1; + + if (lb > ub) + return -EINVAL; + + if (lb < 0xf) + val = lb; + else { + if (ub >= 0xf) + val = 0xf; + else + return -EINVAL; + } + } + + *selector = val; + + ret = max8997_update_reg(i2c, reg, val << shift, mask); + + return ret; +} + +/* + * For LDO1 ~ LDO21, BUCK1~5, BUCK7, CHARGER, CHARGER_TOPOFF + * BUCK1, 2, and 5 are available if they are not controlled by gpio + */ +static int max8997_set_voltage_ldobuck(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + const struct voltage_map_desc *desc; + int rid = rdev_get_id(rdev); + int i, reg, shift, mask, ret; + + switch (rid) { + case MAX8997_LDO1 ... MAX8997_LDO21: + break; + case MAX8997_BUCK1 ... MAX8997_BUCK5: + break; + case MAX8997_BUCK6: + return -EINVAL; + case MAX8997_BUCK7: + break; + case MAX8997_CHARGER: + break; + case MAX8997_CHARGER_TOPOFF: + break; + default: + return -EINVAL; + } + + desc = reg_voltage_map[rid]; + + i = max8997_get_voltage_proper_val(desc, min_uV, max_uV); + if (i < 0) + return i; + + ret = max8997_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + ret = max8997_update_reg(i2c, reg, i << shift, mask << shift); + *selector = i; + + return ret; +} + +static int max8997_set_voltage_buck_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + int rid = rdev_get_id(rdev); + const struct voltage_map_desc *desc = reg_voltage_map[rid]; + + /* Delay is required only if the voltage is increasing */ + if (old_selector >= new_selector) + return 0; + + /* No need to delay if gpio_dvs_mode */ + switch (rid) { + case MAX8997_BUCK1: + if (max8997->buck1_gpiodvs) + return 0; + break; + case MAX8997_BUCK2: + if (max8997->buck2_gpiodvs) + return 0; + break; + case MAX8997_BUCK5: + if (max8997->buck5_gpiodvs) + return 0; + break; + } + + switch (rid) { + case MAX8997_BUCK1: + case MAX8997_BUCK2: + case MAX8997_BUCK4: + case MAX8997_BUCK5: + return DIV_ROUND_UP(desc->step * (new_selector - old_selector), + max8997->ramp_delay * 1000); + } + + return 0; +} + +/* + * Assess the damage on the voltage setting of BUCK1,2,5 by the change. + * + * When GPIO-DVS mode is used for multiple bucks, changing the voltage value + * of one of the bucks may affect that of another buck, which is the side + * effect of the change (set_voltage). This function examines the GPIO-DVS + * configurations and checks whether such side-effect exists. + */ +static int max8997_assess_side_effect(struct regulator_dev *rdev, + u8 new_val, int *best) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + int rid = rdev_get_id(rdev); + u8 *buckx_val[3]; + bool buckx_gpiodvs[3]; + int side_effect[8]; + int min_side_effect = INT_MAX; + int i; + + *best = -1; + + switch (rid) { + case MAX8997_BUCK1: + rid = 0; + break; + case MAX8997_BUCK2: + rid = 1; + break; + case MAX8997_BUCK5: + rid = 2; + break; + default: + return -EINVAL; + } + + buckx_val[0] = max8997->buck1_vol; + buckx_val[1] = max8997->buck2_vol; + buckx_val[2] = max8997->buck5_vol; + buckx_gpiodvs[0] = max8997->buck1_gpiodvs; + buckx_gpiodvs[1] = max8997->buck2_gpiodvs; + buckx_gpiodvs[2] = max8997->buck5_gpiodvs; + + for (i = 0; i < 8; i++) { + int others; + + if (new_val != (buckx_val[rid])[i]) { + side_effect[i] = -1; + continue; + } + + side_effect[i] = 0; + for (others = 0; others < 3; others++) { + int diff; + + if (others == rid) + continue; + if (buckx_gpiodvs[others] == false) + continue; /* Not affected */ + diff = (buckx_val[others])[i] - + (buckx_val[others])[max8997->buck125_gpioindex]; + if (diff > 0) + side_effect[i] += diff; + else if (diff < 0) + side_effect[i] -= diff; + } + if (side_effect[i] == 0) { + *best = i; + return 0; /* NO SIDE EFFECT! Use This! */ + } + if (side_effect[i] < min_side_effect) { + min_side_effect = side_effect[i]; + *best = i; + } + } + + if (*best == -1) + return -EINVAL; + + return side_effect[*best]; +} + +/* + * For Buck 1 ~ 5 and 7. If it is not controlled by GPIO, this calls + * max8997_set_voltage_ldobuck to do the job. + */ +static int max8997_set_voltage_buck(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + int rid = rdev_get_id(rdev); + const struct voltage_map_desc *desc; + int new_val, new_idx, damage, tmp_val, tmp_idx, tmp_dmg; + bool gpio_dvs_mode = false; + + if (rid < MAX8997_BUCK1 || rid > MAX8997_BUCK7) + return -EINVAL; + + switch (rid) { + case MAX8997_BUCK1: + if (max8997->buck1_gpiodvs) + gpio_dvs_mode = true; + break; + case MAX8997_BUCK2: + if (max8997->buck2_gpiodvs) + gpio_dvs_mode = true; + break; + case MAX8997_BUCK5: + if (max8997->buck5_gpiodvs) + gpio_dvs_mode = true; + break; + } + + if (!gpio_dvs_mode) + return max8997_set_voltage_ldobuck(rdev, min_uV, max_uV, + selector); + + desc = reg_voltage_map[rid]; + new_val = max8997_get_voltage_proper_val(desc, min_uV, max_uV); + if (new_val < 0) + return new_val; + + tmp_dmg = INT_MAX; + tmp_idx = -1; + tmp_val = -1; + do { + damage = max8997_assess_side_effect(rdev, new_val, &new_idx); + if (damage == 0) + goto out; + + if (tmp_dmg > damage) { + tmp_idx = new_idx; + tmp_val = new_val; + tmp_dmg = damage; + } + + new_val++; + } while (desc->min + desc->step * new_val <= desc->max); + + new_idx = tmp_idx; + new_val = tmp_val; + + if (max8997->ignore_gpiodvs_side_effect == false) + return -EINVAL; + + dev_warn(&rdev->dev, + "MAX8997 GPIO-DVS Side Effect Warning: GPIO SET: %d -> %d\n", + max8997->buck125_gpioindex, tmp_idx); + +out: + if (new_idx < 0 || new_val < 0) + return -EINVAL; + + max8997->buck125_gpioindex = new_idx; + max8997_set_gpio(max8997); + *selector = new_val; + + return 0; +} + +/* For SAFEOUT1 and SAFEOUT2 */ +static int max8997_set_voltage_safeout_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int rid = rdev_get_id(rdev); + int reg, shift = 0, mask, ret; + + if (rid != MAX8997_ESAFEOUT1 && rid != MAX8997_ESAFEOUT2) + return -EINVAL; + + ret = max8997_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + return max8997_update_reg(i2c, reg, selector << shift, mask << shift); +} + +static int max8997_reg_disable_suspend(struct regulator_dev *rdev) +{ + struct max8997_data *max8997 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8997->iodev->i2c; + int ret, reg, mask, pattern; + int rid = rdev_get_id(rdev); + + ret = max8997_get_enable_register(rdev, ®, &mask, &pattern); + if (ret) + return ret; + + max8997_read_reg(i2c, reg, &max8997->saved_states[rid]); + + if (rid == MAX8997_LDO1 || + rid == MAX8997_LDO10 || + rid == MAX8997_LDO21) { + dev_dbg(&rdev->dev, "Conditional Power-Off for %s\n", + rdev->desc->name); + return max8997_update_reg(i2c, reg, 0x40, mask); + } + + dev_dbg(&rdev->dev, "Full Power-Off for %s (%xh -> %xh)\n", + rdev->desc->name, max8997->saved_states[rid] & mask, + (~pattern) & mask); + return max8997_update_reg(i2c, reg, ~pattern, mask); +} + +static struct regulator_ops max8997_ldo_ops = { + .list_voltage = max8997_list_voltage, + .is_enabled = max8997_reg_is_enabled, + .enable = max8997_reg_enable, + .disable = max8997_reg_disable, + .get_voltage_sel = max8997_get_voltage_sel, + .set_voltage = max8997_set_voltage_ldobuck, + .set_suspend_disable = max8997_reg_disable_suspend, +}; + +static struct regulator_ops max8997_buck_ops = { + .list_voltage = max8997_list_voltage, + .is_enabled = max8997_reg_is_enabled, + .enable = max8997_reg_enable, + .disable = max8997_reg_disable, + .get_voltage_sel = max8997_get_voltage_sel, + .set_voltage = max8997_set_voltage_buck, + .set_voltage_time_sel = max8997_set_voltage_buck_time_sel, + .set_suspend_disable = max8997_reg_disable_suspend, +}; + +static struct regulator_ops max8997_fixedvolt_ops = { + .list_voltage = max8997_list_voltage, + .is_enabled = max8997_reg_is_enabled, + .enable = max8997_reg_enable, + .disable = max8997_reg_disable, + .set_suspend_disable = max8997_reg_disable_suspend, +}; + +static struct regulator_ops max8997_safeout_ops = { + .list_voltage = regulator_list_voltage_table, + .is_enabled = max8997_reg_is_enabled, + .enable = max8997_reg_enable, + .disable = max8997_reg_disable, + .get_voltage_sel = max8997_get_voltage_sel, + .set_voltage_sel = max8997_set_voltage_safeout_sel, + .set_suspend_disable = max8997_reg_disable_suspend, +}; + +static struct regulator_ops max8997_fixedstate_ops = { + .list_voltage = max8997_list_voltage_charger_cv, + .get_voltage_sel = max8997_get_voltage_sel, + .set_voltage = max8997_set_voltage_charger_cv, +}; + +static int max8997_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + unsigned dummy; + int rid = rdev_get_id(rdev); + + if (rid != MAX8997_CHARGER && rid != MAX8997_CHARGER_TOPOFF) + return -EINVAL; + + /* Reuse max8997_set_voltage_ldobuck to set current_limit. */ + return max8997_set_voltage_ldobuck(rdev, min_uA, max_uA, &dummy); +} + +static int max8997_get_current_limit(struct regulator_dev *rdev) +{ + int sel, rid = rdev_get_id(rdev); + + if (rid != MAX8997_CHARGER && rid != MAX8997_CHARGER_TOPOFF) + return -EINVAL; + + sel = max8997_get_voltage_sel(rdev); + if (sel < 0) + return sel; + + /* Reuse max8997_list_voltage to get current_limit. */ + return max8997_list_voltage(rdev, sel); +} + +static struct regulator_ops max8997_charger_ops = { + .is_enabled = max8997_reg_is_enabled, + .enable = max8997_reg_enable, + .disable = max8997_reg_disable, + .get_current_limit = max8997_get_current_limit, + .set_current_limit = max8997_set_current_limit, +}; + +static struct regulator_ops max8997_charger_fixedstate_ops = { + .get_current_limit = max8997_get_current_limit, + .set_current_limit = max8997_set_current_limit, +}; + +#define MAX8997_VOLTAGE_REGULATOR(_name, _ops) {\ + .name = #_name, \ + .id = MAX8997_##_name, \ + .ops = &_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ +} + +#define MAX8997_CURRENT_REGULATOR(_name, _ops) {\ + .name = #_name, \ + .id = MAX8997_##_name, \ + .ops = &_ops, \ + .type = REGULATOR_CURRENT, \ + .owner = THIS_MODULE, \ +} + +static struct regulator_desc regulators[] = { + MAX8997_VOLTAGE_REGULATOR(LDO1, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO2, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO3, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO4, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO5, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO6, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO7, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO8, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO9, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO10, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO11, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO12, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO13, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO14, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO15, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO16, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO17, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO18, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(LDO21, max8997_ldo_ops), + MAX8997_VOLTAGE_REGULATOR(BUCK1, max8997_buck_ops), + MAX8997_VOLTAGE_REGULATOR(BUCK2, max8997_buck_ops), + MAX8997_VOLTAGE_REGULATOR(BUCK3, max8997_buck_ops), + MAX8997_VOLTAGE_REGULATOR(BUCK4, max8997_buck_ops), + MAX8997_VOLTAGE_REGULATOR(BUCK5, max8997_buck_ops), + MAX8997_VOLTAGE_REGULATOR(BUCK6, max8997_fixedvolt_ops), + MAX8997_VOLTAGE_REGULATOR(BUCK7, max8997_buck_ops), + MAX8997_VOLTAGE_REGULATOR(EN32KHZ_AP, max8997_fixedvolt_ops), + MAX8997_VOLTAGE_REGULATOR(EN32KHZ_CP, max8997_fixedvolt_ops), + MAX8997_VOLTAGE_REGULATOR(ENVICHG, max8997_fixedvolt_ops), + MAX8997_VOLTAGE_REGULATOR(ESAFEOUT1, max8997_safeout_ops), + MAX8997_VOLTAGE_REGULATOR(ESAFEOUT2, max8997_safeout_ops), + MAX8997_VOLTAGE_REGULATOR(CHARGER_CV, max8997_fixedstate_ops), + MAX8997_CURRENT_REGULATOR(CHARGER, max8997_charger_ops), + MAX8997_CURRENT_REGULATOR(CHARGER_TOPOFF, + max8997_charger_fixedstate_ops), +}; + +#ifdef CONFIG_OF +static int max8997_pmic_dt_parse_dvs_gpio(struct platform_device *pdev, + struct max8997_platform_data *pdata, + struct device_node *pmic_np) +{ + int i, gpio; + + for (i = 0; i < 3; i++) { + gpio = of_get_named_gpio(pmic_np, + "max8997,pmic-buck125-dvs-gpios", i); + if (!gpio_is_valid(gpio)) { + dev_err(&pdev->dev, "invalid gpio[%d]: %d\n", i, gpio); + return -EINVAL; + } + pdata->buck125_gpios[i] = gpio; + } + return 0; +} + +static int max8997_pmic_dt_parse_pdata(struct platform_device *pdev, + struct max8997_platform_data *pdata) +{ + struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct device_node *pmic_np, *regulators_np, *reg_np; + struct max8997_regulator_data *rdata; + unsigned int i, dvs_voltage_nr = 1, ret; + + pmic_np = iodev->dev->of_node; + if (!pmic_np) { + dev_err(&pdev->dev, "could not find pmic sub-node\n"); + return -ENODEV; + } + + regulators_np = of_get_child_by_name(pmic_np, "regulators"); + if (!regulators_np) { + dev_err(&pdev->dev, "could not find regulators sub-node\n"); + return -EINVAL; + } + + /* count the number of regulators to be supported in pmic */ + pdata->num_regulators = of_get_child_count(regulators_np); + + rdata = devm_kcalloc(&pdev->dev, + pdata->num_regulators, sizeof(*rdata), + GFP_KERNEL); + if (!rdata) { + of_node_put(regulators_np); + return -ENOMEM; + } + + pdata->regulators = rdata; + for_each_child_of_node(regulators_np, reg_np) { + for (i = 0; i < ARRAY_SIZE(regulators); i++) + if (!of_node_cmp(reg_np->name, regulators[i].name)) + break; + + if (i == ARRAY_SIZE(regulators)) { + dev_warn(&pdev->dev, "don't know how to configure regulator %s\n", + reg_np->name); + continue; + } + + rdata->id = i; + rdata->initdata = of_get_regulator_init_data(&pdev->dev, + reg_np, + ®ulators[i]); + rdata->reg_node = reg_np; + rdata++; + } + of_node_put(regulators_np); + + if (of_get_property(pmic_np, "max8997,pmic-buck1-uses-gpio-dvs", NULL)) + pdata->buck1_gpiodvs = true; + + if (of_get_property(pmic_np, "max8997,pmic-buck2-uses-gpio-dvs", NULL)) + pdata->buck2_gpiodvs = true; + + if (of_get_property(pmic_np, "max8997,pmic-buck5-uses-gpio-dvs", NULL)) + pdata->buck5_gpiodvs = true; + + if (pdata->buck1_gpiodvs || pdata->buck2_gpiodvs || + pdata->buck5_gpiodvs) { + ret = max8997_pmic_dt_parse_dvs_gpio(pdev, pdata, pmic_np); + if (ret) + return -EINVAL; + + if (of_property_read_u32(pmic_np, + "max8997,pmic-buck125-default-dvs-idx", + &pdata->buck125_default_idx)) { + pdata->buck125_default_idx = 0; + } else { + if (pdata->buck125_default_idx >= 8) { + pdata->buck125_default_idx = 0; + dev_info(&pdev->dev, "invalid value for default dvs index, using 0 instead\n"); + } + } + + if (of_get_property(pmic_np, + "max8997,pmic-ignore-gpiodvs-side-effect", NULL)) + pdata->ignore_gpiodvs_side_effect = true; + + dvs_voltage_nr = 8; + } + + if (of_property_read_u32_array(pmic_np, + "max8997,pmic-buck1-dvs-voltage", + pdata->buck1_voltage, dvs_voltage_nr)) { + dev_err(&pdev->dev, "buck1 voltages not specified\n"); + return -EINVAL; + } + + if (of_property_read_u32_array(pmic_np, + "max8997,pmic-buck2-dvs-voltage", + pdata->buck2_voltage, dvs_voltage_nr)) { + dev_err(&pdev->dev, "buck2 voltages not specified\n"); + return -EINVAL; + } + + if (of_property_read_u32_array(pmic_np, + "max8997,pmic-buck5-dvs-voltage", + pdata->buck5_voltage, dvs_voltage_nr)) { + dev_err(&pdev->dev, "buck5 voltages not specified\n"); + return -EINVAL; + } + + return 0; +} +#else +static int max8997_pmic_dt_parse_pdata(struct platform_device *pdev, + struct max8997_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_OF */ + +static int max8997_pmic_probe(struct platform_device *pdev) +{ + struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *pdata = iodev->pdata; + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct max8997_data *max8997; + struct i2c_client *i2c; + int i, ret, nr_dvs; + u8 max_buck1 = 0, max_buck2 = 0, max_buck5 = 0; + + if (!pdata) { + dev_err(&pdev->dev, "No platform init data supplied.\n"); + return -ENODEV; + } + + if (iodev->dev->of_node) { + ret = max8997_pmic_dt_parse_pdata(pdev, pdata); + if (ret) + return ret; + } + + max8997 = devm_kzalloc(&pdev->dev, sizeof(struct max8997_data), + GFP_KERNEL); + if (!max8997) + return -ENOMEM; + + max8997->dev = &pdev->dev; + max8997->iodev = iodev; + max8997->num_regulators = pdata->num_regulators; + platform_set_drvdata(pdev, max8997); + i2c = max8997->iodev->i2c; + + max8997->buck125_gpioindex = pdata->buck125_default_idx; + max8997->buck1_gpiodvs = pdata->buck1_gpiodvs; + max8997->buck2_gpiodvs = pdata->buck2_gpiodvs; + max8997->buck5_gpiodvs = pdata->buck5_gpiodvs; + memcpy(max8997->buck125_gpios, pdata->buck125_gpios, sizeof(int) * 3); + max8997->ignore_gpiodvs_side_effect = pdata->ignore_gpiodvs_side_effect; + + nr_dvs = (pdata->buck1_gpiodvs || pdata->buck2_gpiodvs || + pdata->buck5_gpiodvs) ? 8 : 1; + + for (i = 0; i < nr_dvs; i++) { + max8997->buck1_vol[i] = ret = + max8997_get_voltage_proper_val( + &buck1245_voltage_map_desc, + pdata->buck1_voltage[i], + pdata->buck1_voltage[i] + + buck1245_voltage_map_desc.step); + if (ret < 0) + return ret; + + max8997->buck2_vol[i] = ret = + max8997_get_voltage_proper_val( + &buck1245_voltage_map_desc, + pdata->buck2_voltage[i], + pdata->buck2_voltage[i] + + buck1245_voltage_map_desc.step); + if (ret < 0) + return ret; + + max8997->buck5_vol[i] = ret = + max8997_get_voltage_proper_val( + &buck1245_voltage_map_desc, + pdata->buck5_voltage[i], + pdata->buck5_voltage[i] + + buck1245_voltage_map_desc.step); + if (ret < 0) + return ret; + + if (max_buck1 < max8997->buck1_vol[i]) + max_buck1 = max8997->buck1_vol[i]; + if (max_buck2 < max8997->buck2_vol[i]) + max_buck2 = max8997->buck2_vol[i]; + if (max_buck5 < max8997->buck5_vol[i]) + max_buck5 = max8997->buck5_vol[i]; + } + + /* For the safety, set max voltage before setting up */ + for (i = 0; i < 8; i++) { + max8997_update_reg(i2c, MAX8997_REG_BUCK1DVS1 + i, + max_buck1, 0x3f); + max8997_update_reg(i2c, MAX8997_REG_BUCK2DVS1 + i, + max_buck2, 0x3f); + max8997_update_reg(i2c, MAX8997_REG_BUCK5DVS1 + i, + max_buck5, 0x3f); + } + + /* Initialize all the DVS related BUCK registers */ + for (i = 0; i < nr_dvs; i++) { + max8997_update_reg(i2c, MAX8997_REG_BUCK1DVS1 + i, + max8997->buck1_vol[i], + 0x3f); + max8997_update_reg(i2c, MAX8997_REG_BUCK2DVS1 + i, + max8997->buck2_vol[i], + 0x3f); + max8997_update_reg(i2c, MAX8997_REG_BUCK5DVS1 + i, + max8997->buck5_vol[i], + 0x3f); + } + + /* + * If buck 1, 2, and 5 do not care DVS GPIO settings, ignore them. + * If at least one of them cares, set gpios. + */ + if (pdata->buck1_gpiodvs || pdata->buck2_gpiodvs || + pdata->buck5_gpiodvs) { + + if (!gpio_is_valid(pdata->buck125_gpios[0]) || + !gpio_is_valid(pdata->buck125_gpios[1]) || + !gpio_is_valid(pdata->buck125_gpios[2])) { + dev_err(&pdev->dev, "GPIO NOT VALID\n"); + return -EINVAL; + } + + ret = devm_gpio_request(&pdev->dev, pdata->buck125_gpios[0], + "MAX8997 SET1"); + if (ret) + return ret; + + ret = devm_gpio_request(&pdev->dev, pdata->buck125_gpios[1], + "MAX8997 SET2"); + if (ret) + return ret; + + ret = devm_gpio_request(&pdev->dev, pdata->buck125_gpios[2], + "MAX8997 SET3"); + if (ret) + return ret; + + gpio_direction_output(pdata->buck125_gpios[0], + (max8997->buck125_gpioindex >> 2) + & 0x1); /* SET1 */ + gpio_direction_output(pdata->buck125_gpios[1], + (max8997->buck125_gpioindex >> 1) + & 0x1); /* SET2 */ + gpio_direction_output(pdata->buck125_gpios[2], + (max8997->buck125_gpioindex >> 0) + & 0x1); /* SET3 */ + } + + /* DVS-GPIO disabled */ + max8997_update_reg(i2c, MAX8997_REG_BUCK1CTRL, (pdata->buck1_gpiodvs) ? + (1 << 1) : (0 << 1), 1 << 1); + max8997_update_reg(i2c, MAX8997_REG_BUCK2CTRL, (pdata->buck2_gpiodvs) ? + (1 << 1) : (0 << 1), 1 << 1); + max8997_update_reg(i2c, MAX8997_REG_BUCK5CTRL, (pdata->buck5_gpiodvs) ? + (1 << 1) : (0 << 1), 1 << 1); + + /* Misc Settings */ + max8997->ramp_delay = 10; /* set 10mV/us, which is the default */ + max8997_write_reg(i2c, MAX8997_REG_BUCKRAMP, (0xf << 4) | 0x9); + + for (i = 0; i < pdata->num_regulators; i++) { + const struct voltage_map_desc *desc; + int id = pdata->regulators[i].id; + + desc = reg_voltage_map[id]; + if (desc) { + regulators[id].n_voltages = + (desc->max - desc->min) / desc->step + 1; + } else if (id == MAX8997_ESAFEOUT1 || id == MAX8997_ESAFEOUT2) { + regulators[id].volt_table = safeoutvolt; + regulators[id].n_voltages = ARRAY_SIZE(safeoutvolt); + } else if (id == MAX8997_CHARGER_CV) { + regulators[id].n_voltages = 16; + } + + config.dev = max8997->dev; + config.init_data = pdata->regulators[i].initdata; + config.driver_data = max8997; + config.of_node = pdata->regulators[i].reg_node; + + rdev = devm_regulator_register(&pdev->dev, ®ulators[id], + &config); + if (IS_ERR(rdev)) { + dev_err(max8997->dev, "regulator init failed for %d\n", + id); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct platform_device_id max8997_pmic_id[] = { + { "max8997-pmic", 0}, + { }, +}; +MODULE_DEVICE_TABLE(platform, max8997_pmic_id); + +static struct platform_driver max8997_pmic_driver = { + .driver = { + .name = "max8997-pmic", + }, + .probe = max8997_pmic_probe, + .id_table = max8997_pmic_id, +}; + +static int __init max8997_pmic_init(void) +{ + return platform_driver_register(&max8997_pmic_driver); +} +subsys_initcall(max8997_pmic_init); + +static void __exit max8997_pmic_cleanup(void) +{ + platform_driver_unregister(&max8997_pmic_driver); +} +module_exit(max8997_pmic_cleanup); + +MODULE_DESCRIPTION("MAXIM 8997/8966 Regulator Driver"); +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/max8998.c b/drivers/regulator/max8998.c new file mode 100644 index 000000000..271bb736f --- /dev/null +++ b/drivers/regulator/max8998.c @@ -0,0 +1,903 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// max8998.c - Voltage regulator driver for the Maxim 8998 +// +// Copyright (C) 2009-2010 Samsung Electronics +// Kyungmin Park <kyungmin.park@samsung.com> +// Marek Szyprowski <m.szyprowski@samsung.com> + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/max8998.h> +#include <linux/mfd/max8998-private.h> + +struct max8998_data { + struct device *dev; + struct max8998_dev *iodev; + int num_regulators; + u8 buck1_vol[4]; /* voltages for selection */ + u8 buck2_vol[2]; + unsigned int buck1_idx; /* index to last changed voltage */ + /* value in a set */ + unsigned int buck2_idx; +}; + +struct voltage_map_desc { + int min; + int max; + int step; +}; + +/* Voltage maps in uV*/ +static const struct voltage_map_desc ldo23_voltage_map_desc = { + .min = 800000, .step = 50000, .max = 1300000, +}; +static const struct voltage_map_desc ldo456711_voltage_map_desc = { + .min = 1600000, .step = 100000, .max = 3600000, +}; +static const struct voltage_map_desc ldo8_voltage_map_desc = { + .min = 3000000, .step = 100000, .max = 3600000, +}; +static const struct voltage_map_desc ldo9_voltage_map_desc = { + .min = 2800000, .step = 100000, .max = 3100000, +}; +static const struct voltage_map_desc ldo10_voltage_map_desc = { + .min = 950000, .step = 50000, .max = 1300000, +}; +static const struct voltage_map_desc ldo1213_voltage_map_desc = { + .min = 800000, .step = 100000, .max = 3300000, +}; +static const struct voltage_map_desc ldo1415_voltage_map_desc = { + .min = 1200000, .step = 100000, .max = 3300000, +}; +static const struct voltage_map_desc ldo1617_voltage_map_desc = { + .min = 1600000, .step = 100000, .max = 3600000, +}; +static const struct voltage_map_desc buck12_voltage_map_desc = { + .min = 750000, .step = 25000, .max = 1525000, +}; +static const struct voltage_map_desc buck3_voltage_map_desc = { + .min = 1600000, .step = 100000, .max = 3600000, +}; +static const struct voltage_map_desc buck4_voltage_map_desc = { + .min = 800000, .step = 100000, .max = 2300000, +}; + +static const struct voltage_map_desc *ldo_voltage_map[] = { + NULL, + NULL, + &ldo23_voltage_map_desc, /* LDO2 */ + &ldo23_voltage_map_desc, /* LDO3 */ + &ldo456711_voltage_map_desc, /* LDO4 */ + &ldo456711_voltage_map_desc, /* LDO5 */ + &ldo456711_voltage_map_desc, /* LDO6 */ + &ldo456711_voltage_map_desc, /* LDO7 */ + &ldo8_voltage_map_desc, /* LDO8 */ + &ldo9_voltage_map_desc, /* LDO9 */ + &ldo10_voltage_map_desc, /* LDO10 */ + &ldo456711_voltage_map_desc, /* LDO11 */ + &ldo1213_voltage_map_desc, /* LDO12 */ + &ldo1213_voltage_map_desc, /* LDO13 */ + &ldo1415_voltage_map_desc, /* LDO14 */ + &ldo1415_voltage_map_desc, /* LDO15 */ + &ldo1617_voltage_map_desc, /* LDO16 */ + &ldo1617_voltage_map_desc, /* LDO17 */ + &buck12_voltage_map_desc, /* BUCK1 */ + &buck12_voltage_map_desc, /* BUCK2 */ + &buck3_voltage_map_desc, /* BUCK3 */ + &buck4_voltage_map_desc, /* BUCK4 */ +}; + +static int max8998_get_enable_register(struct regulator_dev *rdev, + int *reg, int *shift) +{ + int ldo = rdev_get_id(rdev); + + switch (ldo) { + case MAX8998_LDO2 ... MAX8998_LDO5: + *reg = MAX8998_REG_ONOFF1; + *shift = 3 - (ldo - MAX8998_LDO2); + break; + case MAX8998_LDO6 ... MAX8998_LDO13: + *reg = MAX8998_REG_ONOFF2; + *shift = 7 - (ldo - MAX8998_LDO6); + break; + case MAX8998_LDO14 ... MAX8998_LDO17: + *reg = MAX8998_REG_ONOFF3; + *shift = 7 - (ldo - MAX8998_LDO14); + break; + case MAX8998_BUCK1 ... MAX8998_BUCK4: + *reg = MAX8998_REG_ONOFF1; + *shift = 7 - (ldo - MAX8998_BUCK1); + break; + case MAX8998_EN32KHZ_AP ... MAX8998_ENVICHG: + *reg = MAX8998_REG_ONOFF4; + *shift = 7 - (ldo - MAX8998_EN32KHZ_AP); + break; + case MAX8998_ESAFEOUT1 ... MAX8998_ESAFEOUT2: + *reg = MAX8998_REG_CHGR2; + *shift = 7 - (ldo - MAX8998_ESAFEOUT1); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int max8998_ldo_is_enabled(struct regulator_dev *rdev) +{ + struct max8998_data *max8998 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8998->iodev->i2c; + int ret, reg, shift = 8; + u8 val; + + ret = max8998_get_enable_register(rdev, ®, &shift); + if (ret) + return ret; + + ret = max8998_read_reg(i2c, reg, &val); + if (ret) + return ret; + + return val & (1 << shift); +} + +static int max8998_ldo_enable(struct regulator_dev *rdev) +{ + struct max8998_data *max8998 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8998->iodev->i2c; + int reg, shift = 8, ret; + + ret = max8998_get_enable_register(rdev, ®, &shift); + if (ret) + return ret; + + return max8998_update_reg(i2c, reg, 1<<shift, 1<<shift); +} + +static int max8998_ldo_disable(struct regulator_dev *rdev) +{ + struct max8998_data *max8998 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8998->iodev->i2c; + int reg, shift = 8, ret; + + ret = max8998_get_enable_register(rdev, ®, &shift); + if (ret) + return ret; + + return max8998_update_reg(i2c, reg, 0, 1<<shift); +} + +static int max8998_get_voltage_register(struct regulator_dev *rdev, + int *_reg, int *_shift, int *_mask) +{ + int ldo = rdev_get_id(rdev); + struct max8998_data *max8998 = rdev_get_drvdata(rdev); + int reg, shift = 0, mask = 0xff; + + switch (ldo) { + case MAX8998_LDO2 ... MAX8998_LDO3: + reg = MAX8998_REG_LDO2_LDO3; + mask = 0xf; + if (ldo == MAX8998_LDO2) + shift = 4; + else + shift = 0; + break; + case MAX8998_LDO4 ... MAX8998_LDO7: + reg = MAX8998_REG_LDO4 + (ldo - MAX8998_LDO4); + break; + case MAX8998_LDO8 ... MAX8998_LDO9: + reg = MAX8998_REG_LDO8_LDO9; + mask = 0xf; + if (ldo == MAX8998_LDO8) + shift = 4; + else + shift = 0; + break; + case MAX8998_LDO10 ... MAX8998_LDO11: + reg = MAX8998_REG_LDO10_LDO11; + if (ldo == MAX8998_LDO10) { + shift = 5; + mask = 0x7; + } else { + shift = 0; + mask = 0x1f; + } + break; + case MAX8998_LDO12 ... MAX8998_LDO17: + reg = MAX8998_REG_LDO12 + (ldo - MAX8998_LDO12); + break; + case MAX8998_BUCK1: + reg = MAX8998_REG_BUCK1_VOLTAGE1 + max8998->buck1_idx; + break; + case MAX8998_BUCK2: + reg = MAX8998_REG_BUCK2_VOLTAGE1 + max8998->buck2_idx; + break; + case MAX8998_BUCK3: + reg = MAX8998_REG_BUCK3; + break; + case MAX8998_BUCK4: + reg = MAX8998_REG_BUCK4; + break; + default: + return -EINVAL; + } + + *_reg = reg; + *_shift = shift; + *_mask = mask; + + return 0; +} + +static int max8998_get_voltage_sel(struct regulator_dev *rdev) +{ + struct max8998_data *max8998 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8998->iodev->i2c; + int reg, shift = 0, mask, ret; + u8 val; + + ret = max8998_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + ret = max8998_read_reg(i2c, reg, &val); + if (ret) + return ret; + + val >>= shift; + val &= mask; + + return val; +} + +static int max8998_set_voltage_ldo_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct max8998_data *max8998 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8998->iodev->i2c; + int reg, shift = 0, mask, ret; + + ret = max8998_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + ret = max8998_update_reg(i2c, reg, selector<<shift, mask<<shift); + + return ret; +} + +static inline void buck1_gpio_set(int gpio1, int gpio2, int v) +{ + gpio_set_value(gpio1, v & 0x1); + gpio_set_value(gpio2, (v >> 1) & 0x1); +} + +static inline void buck2_gpio_set(int gpio, int v) +{ + gpio_set_value(gpio, v & 0x1); +} + +static int max8998_set_voltage_buck_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct max8998_data *max8998 = rdev_get_drvdata(rdev); + struct max8998_platform_data *pdata = max8998->iodev->pdata; + struct i2c_client *i2c = max8998->iodev->i2c; + int buck = rdev_get_id(rdev); + int reg, shift = 0, mask, ret, j; + static u8 buck1_last_val; + + ret = max8998_get_voltage_register(rdev, ®, &shift, &mask); + if (ret) + return ret; + + switch (buck) { + case MAX8998_BUCK1: + dev_dbg(max8998->dev, + "BUCK1, selector:%d, buck1_vol1:%d, buck1_vol2:%d\n" + "buck1_vol3:%d, buck1_vol4:%d\n", + selector, max8998->buck1_vol[0], max8998->buck1_vol[1], + max8998->buck1_vol[2], max8998->buck1_vol[3]); + + if (gpio_is_valid(pdata->buck1_set1) && + gpio_is_valid(pdata->buck1_set2)) { + + /* check if requested voltage */ + /* value is already defined */ + for (j = 0; j < ARRAY_SIZE(max8998->buck1_vol); j++) { + if (max8998->buck1_vol[j] == selector) { + max8998->buck1_idx = j; + buck1_gpio_set(pdata->buck1_set1, + pdata->buck1_set2, j); + goto buck1_exit; + } + } + + if (pdata->buck_voltage_lock) + return -EINVAL; + + /* no predefine regulator found */ + max8998->buck1_idx = (buck1_last_val % 2) + 2; + dev_dbg(max8998->dev, "max8998->buck1_idx:%d\n", + max8998->buck1_idx); + max8998->buck1_vol[max8998->buck1_idx] = selector; + ret = max8998_get_voltage_register(rdev, ®, + &shift, + &mask); + ret = max8998_write_reg(i2c, reg, selector); + buck1_gpio_set(pdata->buck1_set1, + pdata->buck1_set2, max8998->buck1_idx); + buck1_last_val++; +buck1_exit: + dev_dbg(max8998->dev, "%s: SET1:%d, SET2:%d\n", + i2c->name, gpio_get_value(pdata->buck1_set1), + gpio_get_value(pdata->buck1_set2)); + break; + } else { + ret = max8998_write_reg(i2c, reg, selector); + } + break; + + case MAX8998_BUCK2: + dev_dbg(max8998->dev, + "BUCK2, selector:%d buck2_vol1:%d, buck2_vol2:%d\n", + selector, max8998->buck2_vol[0], max8998->buck2_vol[1]); + if (gpio_is_valid(pdata->buck2_set3)) { + + /* check if requested voltage */ + /* value is already defined */ + for (j = 0; j < ARRAY_SIZE(max8998->buck2_vol); j++) { + if (max8998->buck2_vol[j] == selector) { + max8998->buck2_idx = j; + buck2_gpio_set(pdata->buck2_set3, j); + goto buck2_exit; + } + } + + if (pdata->buck_voltage_lock) + return -EINVAL; + + max8998_get_voltage_register(rdev, + ®, &shift, &mask); + ret = max8998_write_reg(i2c, reg, selector); + max8998->buck2_vol[max8998->buck2_idx] = selector; + buck2_gpio_set(pdata->buck2_set3, max8998->buck2_idx); +buck2_exit: + dev_dbg(max8998->dev, "%s: SET3:%d\n", i2c->name, + gpio_get_value(pdata->buck2_set3)); + } else { + ret = max8998_write_reg(i2c, reg, selector); + } + break; + + case MAX8998_BUCK3: + case MAX8998_BUCK4: + ret = max8998_update_reg(i2c, reg, selector<<shift, + mask<<shift); + break; + } + + return ret; +} + +static int max8998_set_voltage_buck_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + struct max8998_data *max8998 = rdev_get_drvdata(rdev); + struct i2c_client *i2c = max8998->iodev->i2c; + const struct voltage_map_desc *desc; + int buck = rdev_get_id(rdev); + u8 val = 0; + int difference, ret; + + if (buck < MAX8998_BUCK1 || buck > MAX8998_BUCK4) + return -EINVAL; + + desc = ldo_voltage_map[buck]; + + /* Voltage stabilization */ + ret = max8998_read_reg(i2c, MAX8998_REG_ONOFF4, &val); + if (ret) + return ret; + + /* lp3974 hasn't got ENRAMP bit - ramp is assumed as true */ + /* MAX8998 has ENRAMP bit implemented, so test it*/ + if (max8998->iodev->type == TYPE_MAX8998 && !(val & MAX8998_ENRAMP)) + return 0; + + difference = (new_selector - old_selector) * desc->step / 1000; + if (difference > 0) + return DIV_ROUND_UP(difference, (val & 0x0f) + 1); + + return 0; +} + +static struct regulator_ops max8998_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = max8998_ldo_is_enabled, + .enable = max8998_ldo_enable, + .disable = max8998_ldo_disable, + .get_voltage_sel = max8998_get_voltage_sel, + .set_voltage_sel = max8998_set_voltage_ldo_sel, +}; + +static struct regulator_ops max8998_buck_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = max8998_ldo_is_enabled, + .enable = max8998_ldo_enable, + .disable = max8998_ldo_disable, + .get_voltage_sel = max8998_get_voltage_sel, + .set_voltage_sel = max8998_set_voltage_buck_sel, + .set_voltage_time_sel = max8998_set_voltage_buck_time_sel, +}; + +static struct regulator_ops max8998_others_ops = { + .is_enabled = max8998_ldo_is_enabled, + .enable = max8998_ldo_enable, + .disable = max8998_ldo_disable, +}; + +static struct regulator_desc regulators[] = { + { + .name = "LDO2", + .id = MAX8998_LDO2, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO3", + .id = MAX8998_LDO3, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO4", + .id = MAX8998_LDO4, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO5", + .id = MAX8998_LDO5, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO6", + .id = MAX8998_LDO6, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO7", + .id = MAX8998_LDO7, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO8", + .id = MAX8998_LDO8, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO9", + .id = MAX8998_LDO9, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO10", + .id = MAX8998_LDO10, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO11", + .id = MAX8998_LDO11, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO12", + .id = MAX8998_LDO12, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO13", + .id = MAX8998_LDO13, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO14", + .id = MAX8998_LDO14, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO15", + .id = MAX8998_LDO15, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO16", + .id = MAX8998_LDO16, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "LDO17", + .id = MAX8998_LDO17, + .ops = &max8998_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "BUCK1", + .id = MAX8998_BUCK1, + .ops = &max8998_buck_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "BUCK2", + .id = MAX8998_BUCK2, + .ops = &max8998_buck_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "BUCK3", + .id = MAX8998_BUCK3, + .ops = &max8998_buck_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "BUCK4", + .id = MAX8998_BUCK4, + .ops = &max8998_buck_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "EN32KHz-AP", + .id = MAX8998_EN32KHZ_AP, + .ops = &max8998_others_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "EN32KHz-CP", + .id = MAX8998_EN32KHZ_CP, + .ops = &max8998_others_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "ENVICHG", + .id = MAX8998_ENVICHG, + .ops = &max8998_others_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "ESAFEOUT1", + .id = MAX8998_ESAFEOUT1, + .ops = &max8998_others_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, { + .name = "ESAFEOUT2", + .id = MAX8998_ESAFEOUT2, + .ops = &max8998_others_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + } +}; + +static int max8998_pmic_dt_parse_dvs_gpio(struct max8998_dev *iodev, + struct max8998_platform_data *pdata, + struct device_node *pmic_np) +{ + int gpio; + + gpio = of_get_named_gpio(pmic_np, "max8998,pmic-buck1-dvs-gpios", 0); + if (!gpio_is_valid(gpio)) { + dev_err(iodev->dev, "invalid buck1 gpio[0]: %d\n", gpio); + return -EINVAL; + } + pdata->buck1_set1 = gpio; + + gpio = of_get_named_gpio(pmic_np, "max8998,pmic-buck1-dvs-gpios", 1); + if (!gpio_is_valid(gpio)) { + dev_err(iodev->dev, "invalid buck1 gpio[1]: %d\n", gpio); + return -EINVAL; + } + pdata->buck1_set2 = gpio; + + gpio = of_get_named_gpio(pmic_np, "max8998,pmic-buck2-dvs-gpio", 0); + if (!gpio_is_valid(gpio)) { + dev_err(iodev->dev, "invalid buck 2 gpio: %d\n", gpio); + return -EINVAL; + } + pdata->buck2_set3 = gpio; + + return 0; +} + +static int max8998_pmic_dt_parse_pdata(struct max8998_dev *iodev, + struct max8998_platform_data *pdata) +{ + struct device_node *pmic_np = iodev->dev->of_node; + struct device_node *regulators_np, *reg_np; + struct max8998_regulator_data *rdata; + unsigned int i; + int ret; + + regulators_np = of_get_child_by_name(pmic_np, "regulators"); + if (!regulators_np) { + dev_err(iodev->dev, "could not find regulators sub-node\n"); + return -EINVAL; + } + + /* count the number of regulators to be supported in pmic */ + pdata->num_regulators = of_get_child_count(regulators_np); + + rdata = devm_kcalloc(iodev->dev, + pdata->num_regulators, sizeof(*rdata), + GFP_KERNEL); + if (!rdata) { + of_node_put(regulators_np); + return -ENOMEM; + } + + pdata->regulators = rdata; + for (i = 0; i < ARRAY_SIZE(regulators); ++i) { + reg_np = of_get_child_by_name(regulators_np, + regulators[i].name); + if (!reg_np) + continue; + + rdata->id = regulators[i].id; + rdata->initdata = of_get_regulator_init_data(iodev->dev, + reg_np, + ®ulators[i]); + rdata->reg_node = reg_np; + ++rdata; + } + pdata->num_regulators = rdata - pdata->regulators; + + of_node_put(reg_np); + of_node_put(regulators_np); + + ret = max8998_pmic_dt_parse_dvs_gpio(iodev, pdata, pmic_np); + if (ret) + return -EINVAL; + + if (of_find_property(pmic_np, "max8998,pmic-buck-voltage-lock", NULL)) + pdata->buck_voltage_lock = true; + + ret = of_property_read_u32(pmic_np, + "max8998,pmic-buck1-default-dvs-idx", + &pdata->buck1_default_idx); + if (!ret && pdata->buck1_default_idx >= 4) { + pdata->buck1_default_idx = 0; + dev_warn(iodev->dev, "invalid value for default dvs index, using 0 instead\n"); + } + + ret = of_property_read_u32(pmic_np, + "max8998,pmic-buck2-default-dvs-idx", + &pdata->buck2_default_idx); + if (!ret && pdata->buck2_default_idx >= 2) { + pdata->buck2_default_idx = 0; + dev_warn(iodev->dev, "invalid value for default dvs index, using 0 instead\n"); + } + + ret = of_property_read_u32_array(pmic_np, + "max8998,pmic-buck1-dvs-voltage", + pdata->buck1_voltage, + ARRAY_SIZE(pdata->buck1_voltage)); + if (ret) { + dev_err(iodev->dev, "buck1 voltages not specified\n"); + return -EINVAL; + } + + ret = of_property_read_u32_array(pmic_np, + "max8998,pmic-buck2-dvs-voltage", + pdata->buck2_voltage, + ARRAY_SIZE(pdata->buck2_voltage)); + if (ret) { + dev_err(iodev->dev, "buck2 voltages not specified\n"); + return -EINVAL; + } + + return 0; +} + +static int max8998_pmic_probe(struct platform_device *pdev) +{ + struct max8998_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct max8998_platform_data *pdata = iodev->pdata; + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct max8998_data *max8998; + struct i2c_client *i2c; + int i, ret; + unsigned int v; + + if (!pdata) { + dev_err(pdev->dev.parent, "No platform init data supplied\n"); + return -ENODEV; + } + + if (IS_ENABLED(CONFIG_OF) && iodev->dev->of_node) { + ret = max8998_pmic_dt_parse_pdata(iodev, pdata); + if (ret) + return ret; + } + + max8998 = devm_kzalloc(&pdev->dev, sizeof(struct max8998_data), + GFP_KERNEL); + if (!max8998) + return -ENOMEM; + + max8998->dev = &pdev->dev; + max8998->iodev = iodev; + max8998->num_regulators = pdata->num_regulators; + platform_set_drvdata(pdev, max8998); + i2c = max8998->iodev->i2c; + + max8998->buck1_idx = pdata->buck1_default_idx; + max8998->buck2_idx = pdata->buck2_default_idx; + + /* NOTE: */ + /* For unused GPIO NOT marked as -1 (thereof equal to 0) WARN_ON */ + /* will be displayed */ + + /* Check if MAX8998 voltage selection GPIOs are defined */ + if (gpio_is_valid(pdata->buck1_set1) && + gpio_is_valid(pdata->buck1_set2)) { + /* Check if SET1 is not equal to 0 */ + if (!pdata->buck1_set1) { + dev_err(&pdev->dev, + "MAX8998 SET1 GPIO defined as 0 !\n"); + WARN_ON(!pdata->buck1_set1); + return -EIO; + } + /* Check if SET2 is not equal to 0 */ + if (!pdata->buck1_set2) { + dev_err(&pdev->dev, + "MAX8998 SET2 GPIO defined as 0 !\n"); + WARN_ON(!pdata->buck1_set2); + return -EIO; + } + + gpio_request(pdata->buck1_set1, "MAX8998 BUCK1_SET1"); + gpio_direction_output(pdata->buck1_set1, + max8998->buck1_idx & 0x1); + + + gpio_request(pdata->buck1_set2, "MAX8998 BUCK1_SET2"); + gpio_direction_output(pdata->buck1_set2, + (max8998->buck1_idx >> 1) & 0x1); + + /* Set predefined values for BUCK1 registers */ + for (v = 0; v < ARRAY_SIZE(pdata->buck1_voltage); ++v) { + i = 0; + while (buck12_voltage_map_desc.min + + buck12_voltage_map_desc.step*i + < pdata->buck1_voltage[v]) + i++; + + max8998->buck1_vol[v] = i; + ret = max8998_write_reg(i2c, + MAX8998_REG_BUCK1_VOLTAGE1 + v, i); + if (ret) + return ret; + } + } + + if (gpio_is_valid(pdata->buck2_set3)) { + /* Check if SET3 is not equal to 0 */ + if (!pdata->buck2_set3) { + dev_err(&pdev->dev, + "MAX8998 SET3 GPIO defined as 0 !\n"); + WARN_ON(!pdata->buck2_set3); + return -EIO; + } + gpio_request(pdata->buck2_set3, "MAX8998 BUCK2_SET3"); + gpio_direction_output(pdata->buck2_set3, + max8998->buck2_idx & 0x1); + + /* Set predefined values for BUCK2 registers */ + for (v = 0; v < ARRAY_SIZE(pdata->buck2_voltage); ++v) { + i = 0; + while (buck12_voltage_map_desc.min + + buck12_voltage_map_desc.step*i + < pdata->buck2_voltage[v]) + i++; + + max8998->buck2_vol[v] = i; + ret = max8998_write_reg(i2c, + MAX8998_REG_BUCK2_VOLTAGE1 + v, i); + if (ret) + return ret; + } + } + + for (i = 0; i < pdata->num_regulators; i++) { + const struct voltage_map_desc *desc; + int id = pdata->regulators[i].id; + int index = id - MAX8998_LDO2; + + desc = ldo_voltage_map[id]; + if (desc && regulators[index].ops != &max8998_others_ops) { + int count = (desc->max - desc->min) / desc->step + 1; + + regulators[index].n_voltages = count; + regulators[index].min_uV = desc->min; + regulators[index].uV_step = desc->step; + } + + config.dev = max8998->dev; + config.of_node = pdata->regulators[i].reg_node; + config.init_data = pdata->regulators[i].initdata; + config.driver_data = max8998; + + rdev = devm_regulator_register(&pdev->dev, ®ulators[index], + &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(max8998->dev, "regulator %s init failed (%d)\n", + regulators[index].name, ret); + return ret; + } + } + + + return 0; +} + +static const struct platform_device_id max8998_pmic_id[] = { + { "max8998-pmic", TYPE_MAX8998 }, + { "lp3974-pmic", TYPE_LP3974 }, + { } +}; +MODULE_DEVICE_TABLE(platform, max8998_pmic_id); + +static struct platform_driver max8998_pmic_driver = { + .driver = { + .name = "max8998-pmic", + }, + .probe = max8998_pmic_probe, + .id_table = max8998_pmic_id, +}; + +static int __init max8998_pmic_init(void) +{ + return platform_driver_register(&max8998_pmic_driver); +} +subsys_initcall(max8998_pmic_init); + +static void __exit max8998_pmic_cleanup(void) +{ + platform_driver_unregister(&max8998_pmic_driver); +} +module_exit(max8998_pmic_cleanup); + +MODULE_DESCRIPTION("MAXIM 8998 voltage regulator driver"); +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/mc13783-regulator.c b/drivers/regulator/mc13783-regulator.c new file mode 100644 index 000000000..8fd1adc9c --- /dev/null +++ b/drivers/regulator/mc13783-regulator.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Regulator Driver for Freescale MC13783 PMIC +// +// Copyright 2010 Yong Shen <yong.shen@linaro.org> +// Copyright (C) 2008 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> +// Copyright 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com> + +#include <linux/mfd/mc13783.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include "mc13xxx.h" + +#define MC13783_REG_SWITCHERS0 24 +/* Enable does not exist for SW1A */ +#define MC13783_REG_SWITCHERS0_SW1AEN 0 +#define MC13783_REG_SWITCHERS0_SW1AVSEL 0 +#define MC13783_REG_SWITCHERS0_SW1AVSEL_M (63 << 0) + +#define MC13783_REG_SWITCHERS1 25 +/* Enable does not exist for SW1B */ +#define MC13783_REG_SWITCHERS1_SW1BEN 0 +#define MC13783_REG_SWITCHERS1_SW1BVSEL 0 +#define MC13783_REG_SWITCHERS1_SW1BVSEL_M (63 << 0) + +#define MC13783_REG_SWITCHERS2 26 +/* Enable does not exist for SW2A */ +#define MC13783_REG_SWITCHERS2_SW2AEN 0 +#define MC13783_REG_SWITCHERS2_SW2AVSEL 0 +#define MC13783_REG_SWITCHERS2_SW2AVSEL_M (63 << 0) + +#define MC13783_REG_SWITCHERS3 27 +/* Enable does not exist for SW2B */ +#define MC13783_REG_SWITCHERS3_SW2BEN 0 +#define MC13783_REG_SWITCHERS3_SW2BVSEL 0 +#define MC13783_REG_SWITCHERS3_SW2BVSEL_M (63 << 0) + +#define MC13783_REG_SWITCHERS5 29 +#define MC13783_REG_SWITCHERS5_SW3EN (1 << 20) +#define MC13783_REG_SWITCHERS5_SW3VSEL 18 +#define MC13783_REG_SWITCHERS5_SW3VSEL_M (3 << 18) + +#define MC13783_REG_REGULATORSETTING0 30 +#define MC13783_REG_REGULATORSETTING0_VIOLOVSEL 2 +#define MC13783_REG_REGULATORSETTING0_VDIGVSEL 4 +#define MC13783_REG_REGULATORSETTING0_VGENVSEL 6 +#define MC13783_REG_REGULATORSETTING0_VRFDIGVSEL 9 +#define MC13783_REG_REGULATORSETTING0_VRFREFVSEL 11 +#define MC13783_REG_REGULATORSETTING0_VRFCPVSEL 13 +#define MC13783_REG_REGULATORSETTING0_VSIMVSEL 14 +#define MC13783_REG_REGULATORSETTING0_VESIMVSEL 15 +#define MC13783_REG_REGULATORSETTING0_VCAMVSEL 16 + +#define MC13783_REG_REGULATORSETTING0_VIOLOVSEL_M (3 << 2) +#define MC13783_REG_REGULATORSETTING0_VDIGVSEL_M (3 << 4) +#define MC13783_REG_REGULATORSETTING0_VGENVSEL_M (7 << 6) +#define MC13783_REG_REGULATORSETTING0_VRFDIGVSEL_M (3 << 9) +#define MC13783_REG_REGULATORSETTING0_VRFREFVSEL_M (3 << 11) +#define MC13783_REG_REGULATORSETTING0_VRFCPVSEL_M (1 << 13) +#define MC13783_REG_REGULATORSETTING0_VSIMVSEL_M (1 << 14) +#define MC13783_REG_REGULATORSETTING0_VESIMVSEL_M (1 << 15) +#define MC13783_REG_REGULATORSETTING0_VCAMVSEL_M (7 << 16) + +#define MC13783_REG_REGULATORSETTING1 31 +#define MC13783_REG_REGULATORSETTING1_VVIBVSEL 0 +#define MC13783_REG_REGULATORSETTING1_VRF1VSEL 2 +#define MC13783_REG_REGULATORSETTING1_VRF2VSEL 4 +#define MC13783_REG_REGULATORSETTING1_VMMC1VSEL 6 +#define MC13783_REG_REGULATORSETTING1_VMMC2VSEL 9 + +#define MC13783_REG_REGULATORSETTING1_VVIBVSEL_M (3 << 0) +#define MC13783_REG_REGULATORSETTING1_VRF1VSEL_M (3 << 2) +#define MC13783_REG_REGULATORSETTING1_VRF2VSEL_M (3 << 4) +#define MC13783_REG_REGULATORSETTING1_VMMC1VSEL_M (7 << 6) +#define MC13783_REG_REGULATORSETTING1_VMMC2VSEL_M (7 << 9) + +#define MC13783_REG_REGULATORMODE0 32 +#define MC13783_REG_REGULATORMODE0_VAUDIOEN (1 << 0) +#define MC13783_REG_REGULATORMODE0_VIOHIEN (1 << 3) +#define MC13783_REG_REGULATORMODE0_VIOLOEN (1 << 6) +#define MC13783_REG_REGULATORMODE0_VDIGEN (1 << 9) +#define MC13783_REG_REGULATORMODE0_VGENEN (1 << 12) +#define MC13783_REG_REGULATORMODE0_VRFDIGEN (1 << 15) +#define MC13783_REG_REGULATORMODE0_VRFREFEN (1 << 18) +#define MC13783_REG_REGULATORMODE0_VRFCPEN (1 << 21) + +#define MC13783_REG_REGULATORMODE1 33 +#define MC13783_REG_REGULATORMODE1_VSIMEN (1 << 0) +#define MC13783_REG_REGULATORMODE1_VESIMEN (1 << 3) +#define MC13783_REG_REGULATORMODE1_VCAMEN (1 << 6) +#define MC13783_REG_REGULATORMODE1_VRFBGEN (1 << 9) +#define MC13783_REG_REGULATORMODE1_VVIBEN (1 << 11) +#define MC13783_REG_REGULATORMODE1_VRF1EN (1 << 12) +#define MC13783_REG_REGULATORMODE1_VRF2EN (1 << 15) +#define MC13783_REG_REGULATORMODE1_VMMC1EN (1 << 18) +#define MC13783_REG_REGULATORMODE1_VMMC2EN (1 << 21) + +#define MC13783_REG_POWERMISC 34 +#define MC13783_REG_POWERMISC_GPO1EN (1 << 6) +#define MC13783_REG_POWERMISC_GPO2EN (1 << 8) +#define MC13783_REG_POWERMISC_GPO3EN (1 << 10) +#define MC13783_REG_POWERMISC_GPO4EN (1 << 12) +#define MC13783_REG_POWERMISC_PWGT1SPIEN (1 << 15) +#define MC13783_REG_POWERMISC_PWGT2SPIEN (1 << 16) + +#define MC13783_REG_POWERMISC_PWGTSPI_M (3 << 15) + + +/* Voltage Values */ +static const int mc13783_sw1x_val[] = { + 900000, 925000, 950000, 975000, + 1000000, 1025000, 1050000, 1075000, + 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, + 1300000, 1325000, 1350000, 1375000, + 1400000, 1425000, 1450000, 1475000, + 1500000, 1525000, 1550000, 1575000, + 1600000, 1625000, 1650000, 1675000, + 1700000, 1700000, 1700000, 1700000, + 1800000, 1800000, 1800000, 1800000, + 1850000, 1850000, 1850000, 1850000, + 2000000, 2000000, 2000000, 2000000, + 2100000, 2100000, 2100000, 2100000, + 2200000, 2200000, 2200000, 2200000, + 2200000, 2200000, 2200000, 2200000, + 2200000, 2200000, 2200000, 2200000, +}; + +static const int mc13783_sw2x_val[] = { + 900000, 925000, 950000, 975000, + 1000000, 1025000, 1050000, 1075000, + 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, + 1300000, 1325000, 1350000, 1375000, + 1400000, 1425000, 1450000, 1475000, + 1500000, 1525000, 1550000, 1575000, + 1600000, 1625000, 1650000, 1675000, + 1700000, 1700000, 1700000, 1700000, + 1800000, 1800000, 1800000, 1800000, + 1900000, 1900000, 1900000, 1900000, + 2000000, 2000000, 2000000, 2000000, + 2100000, 2100000, 2100000, 2100000, + 2200000, 2200000, 2200000, 2200000, + 2200000, 2200000, 2200000, 2200000, + 2200000, 2200000, 2200000, 2200000, +}; + +static const unsigned int mc13783_sw3_val[] = { + 5000000, 5000000, 5000000, 5500000, +}; + +static const unsigned int mc13783_vaudio_val[] = { + 2775000, +}; + +static const unsigned int mc13783_viohi_val[] = { + 2775000, +}; + +static const unsigned int mc13783_violo_val[] = { + 1200000, 1300000, 1500000, 1800000, +}; + +static const unsigned int mc13783_vdig_val[] = { + 1200000, 1300000, 1500000, 1800000, +}; + +static const unsigned int mc13783_vgen_val[] = { + 1200000, 1300000, 1500000, 1800000, + 1100000, 2000000, 2775000, 2400000, +}; + +static const unsigned int mc13783_vrfdig_val[] = { + 1200000, 1500000, 1800000, 1875000, +}; + +static const unsigned int mc13783_vrfref_val[] = { + 2475000, 2600000, 2700000, 2775000, +}; + +static const unsigned int mc13783_vrfcp_val[] = { + 2700000, 2775000, +}; + +static const unsigned int mc13783_vsim_val[] = { + 1800000, 2900000, 3000000, +}; + +static const unsigned int mc13783_vesim_val[] = { + 1800000, 2900000, +}; + +static const unsigned int mc13783_vcam_val[] = { + 1500000, 1800000, 2500000, 2550000, + 2600000, 2750000, 2800000, 3000000, +}; + +static const unsigned int mc13783_vrfbg_val[] = { + 1250000, +}; + +static const unsigned int mc13783_vvib_val[] = { + 1300000, 1800000, 2000000, 3000000, +}; + +static const unsigned int mc13783_vmmc_val[] = { + 1600000, 1800000, 2000000, 2600000, + 2700000, 2800000, 2900000, 3000000, +}; + +static const unsigned int mc13783_vrf_val[] = { + 1500000, 1875000, 2700000, 2775000, +}; + +static const unsigned int mc13783_gpo_val[] = { + 3100000, +}; + +static const unsigned int mc13783_pwgtdrv_val[] = { + 5500000, +}; + +static struct regulator_ops mc13783_gpo_regulator_ops; + +#define MC13783_DEFINE(prefix, name, reg, vsel_reg, voltages) \ + MC13xxx_DEFINE(MC13783_REG_, name, reg, vsel_reg, voltages, \ + mc13xxx_regulator_ops) + +#define MC13783_FIXED_DEFINE(prefix, name, reg, voltages) \ + MC13xxx_FIXED_DEFINE(MC13783_REG_, name, reg, voltages, \ + mc13xxx_fixed_regulator_ops) + +#define MC13783_GPO_DEFINE(prefix, name, reg, voltages) \ + MC13xxx_GPO_DEFINE(MC13783_REG_, name, reg, voltages, \ + mc13783_gpo_regulator_ops) + +#define MC13783_DEFINE_SW(_name, _reg, _vsel_reg, _voltages) \ + MC13783_DEFINE(REG, _name, _reg, _vsel_reg, _voltages) +#define MC13783_DEFINE_REGU(_name, _reg, _vsel_reg, _voltages) \ + MC13783_DEFINE(REG, _name, _reg, _vsel_reg, _voltages) + +static struct mc13xxx_regulator mc13783_regulators[] = { + MC13783_DEFINE_SW(SW1A, SWITCHERS0, SWITCHERS0, mc13783_sw1x_val), + MC13783_DEFINE_SW(SW1B, SWITCHERS1, SWITCHERS1, mc13783_sw1x_val), + MC13783_DEFINE_SW(SW2A, SWITCHERS2, SWITCHERS2, mc13783_sw2x_val), + MC13783_DEFINE_SW(SW2B, SWITCHERS3, SWITCHERS3, mc13783_sw2x_val), + MC13783_DEFINE_SW(SW3, SWITCHERS5, SWITCHERS5, mc13783_sw3_val), + + MC13783_FIXED_DEFINE(REG, VAUDIO, REGULATORMODE0, mc13783_vaudio_val), + MC13783_FIXED_DEFINE(REG, VIOHI, REGULATORMODE0, mc13783_viohi_val), + MC13783_DEFINE_REGU(VIOLO, REGULATORMODE0, REGULATORSETTING0, + mc13783_violo_val), + MC13783_DEFINE_REGU(VDIG, REGULATORMODE0, REGULATORSETTING0, + mc13783_vdig_val), + MC13783_DEFINE_REGU(VGEN, REGULATORMODE0, REGULATORSETTING0, + mc13783_vgen_val), + MC13783_DEFINE_REGU(VRFDIG, REGULATORMODE0, REGULATORSETTING0, + mc13783_vrfdig_val), + MC13783_DEFINE_REGU(VRFREF, REGULATORMODE0, REGULATORSETTING0, + mc13783_vrfref_val), + MC13783_DEFINE_REGU(VRFCP, REGULATORMODE0, REGULATORSETTING0, + mc13783_vrfcp_val), + MC13783_DEFINE_REGU(VSIM, REGULATORMODE1, REGULATORSETTING0, + mc13783_vsim_val), + MC13783_DEFINE_REGU(VESIM, REGULATORMODE1, REGULATORSETTING0, + mc13783_vesim_val), + MC13783_DEFINE_REGU(VCAM, REGULATORMODE1, REGULATORSETTING0, + mc13783_vcam_val), + MC13783_FIXED_DEFINE(REG, VRFBG, REGULATORMODE1, mc13783_vrfbg_val), + MC13783_DEFINE_REGU(VVIB, REGULATORMODE1, REGULATORSETTING1, + mc13783_vvib_val), + MC13783_DEFINE_REGU(VRF1, REGULATORMODE1, REGULATORSETTING1, + mc13783_vrf_val), + MC13783_DEFINE_REGU(VRF2, REGULATORMODE1, REGULATORSETTING1, + mc13783_vrf_val), + MC13783_DEFINE_REGU(VMMC1, REGULATORMODE1, REGULATORSETTING1, + mc13783_vmmc_val), + MC13783_DEFINE_REGU(VMMC2, REGULATORMODE1, REGULATORSETTING1, + mc13783_vmmc_val), + MC13783_GPO_DEFINE(REG, GPO1, POWERMISC, mc13783_gpo_val), + MC13783_GPO_DEFINE(REG, GPO2, POWERMISC, mc13783_gpo_val), + MC13783_GPO_DEFINE(REG, GPO3, POWERMISC, mc13783_gpo_val), + MC13783_GPO_DEFINE(REG, GPO4, POWERMISC, mc13783_gpo_val), + MC13783_GPO_DEFINE(REG, PWGT1SPI, POWERMISC, mc13783_pwgtdrv_val), + MC13783_GPO_DEFINE(REG, PWGT2SPI, POWERMISC, mc13783_pwgtdrv_val), +}; + +static int mc13783_powermisc_rmw(struct mc13xxx_regulator_priv *priv, u32 mask, + u32 val) +{ + struct mc13xxx *mc13783 = priv->mc13xxx; + int ret; + u32 valread; + + BUG_ON(val & ~mask); + + mc13xxx_lock(priv->mc13xxx); + ret = mc13xxx_reg_read(mc13783, MC13783_REG_POWERMISC, &valread); + if (ret) + goto out; + + /* Update the stored state for Power Gates. */ + priv->powermisc_pwgt_state = + (priv->powermisc_pwgt_state & ~mask) | val; + priv->powermisc_pwgt_state &= MC13783_REG_POWERMISC_PWGTSPI_M; + + /* Construct the new register value */ + valread = (valread & ~mask) | val; + /* Overwrite the PWGTxEN with the stored version */ + valread = (valread & ~MC13783_REG_POWERMISC_PWGTSPI_M) | + priv->powermisc_pwgt_state; + + ret = mc13xxx_reg_write(mc13783, MC13783_REG_POWERMISC, valread); +out: + mc13xxx_unlock(priv->mc13xxx); + return ret; +} + +static int mc13783_gpo_regulator_enable(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + struct mc13xxx_regulator *mc13xxx_regulators = priv->mc13xxx_regulators; + int id = rdev_get_id(rdev); + u32 en_val = mc13xxx_regulators[id].enable_bit; + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + /* Power Gate enable value is 0 */ + if (id == MC13783_REG_PWGT1SPI || + id == MC13783_REG_PWGT2SPI) + en_val = 0; + + return mc13783_powermisc_rmw(priv, mc13xxx_regulators[id].enable_bit, + en_val); +} + +static int mc13783_gpo_regulator_disable(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + struct mc13xxx_regulator *mc13xxx_regulators = priv->mc13xxx_regulators; + int id = rdev_get_id(rdev); + u32 dis_val = 0; + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + /* Power Gate disable value is 1 */ + if (id == MC13783_REG_PWGT1SPI || + id == MC13783_REG_PWGT2SPI) + dis_val = mc13xxx_regulators[id].enable_bit; + + return mc13783_powermisc_rmw(priv, mc13xxx_regulators[id].enable_bit, + dis_val); +} + +static int mc13783_gpo_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + struct mc13xxx_regulator *mc13xxx_regulators = priv->mc13xxx_regulators; + int ret, id = rdev_get_id(rdev); + unsigned int val; + + mc13xxx_lock(priv->mc13xxx); + ret = mc13xxx_reg_read(priv->mc13xxx, mc13xxx_regulators[id].reg, &val); + mc13xxx_unlock(priv->mc13xxx); + + if (ret) + return ret; + + /* Power Gates state is stored in powermisc_pwgt_state + * where the meaning of bits is negated */ + val = (val & ~MC13783_REG_POWERMISC_PWGTSPI_M) | + (priv->powermisc_pwgt_state ^ MC13783_REG_POWERMISC_PWGTSPI_M); + + return (val & mc13xxx_regulators[id].enable_bit) != 0; +} + +static struct regulator_ops mc13783_gpo_regulator_ops = { + .enable = mc13783_gpo_regulator_enable, + .disable = mc13783_gpo_regulator_disable, + .is_enabled = mc13783_gpo_regulator_is_enabled, + .list_voltage = regulator_list_voltage_table, + .set_voltage = mc13xxx_fixed_regulator_set_voltage, +}; + +static int mc13783_regulator_probe(struct platform_device *pdev) +{ + struct mc13xxx_regulator_priv *priv; + struct mc13xxx *mc13783 = dev_get_drvdata(pdev->dev.parent); + struct mc13xxx_regulator_platform_data *pdata = + dev_get_platdata(&pdev->dev); + struct mc13xxx_regulator_init_data *mc13xxx_data; + struct regulator_config config = { }; + int i, num_regulators; + + num_regulators = mc13xxx_get_num_regulators_dt(pdev); + + if (num_regulators <= 0 && pdata) + num_regulators = pdata->num_regulators; + if (num_regulators <= 0) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, + struct_size(priv, regulators, num_regulators), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->num_regulators = num_regulators; + priv->mc13xxx_regulators = mc13783_regulators; + priv->mc13xxx = mc13783; + platform_set_drvdata(pdev, priv); + + mc13xxx_data = mc13xxx_parse_regulators_dt(pdev, mc13783_regulators, + ARRAY_SIZE(mc13783_regulators)); + + for (i = 0; i < priv->num_regulators; i++) { + struct regulator_init_data *init_data; + struct regulator_desc *desc; + struct device_node *node = NULL; + int id; + + if (mc13xxx_data) { + id = mc13xxx_data[i].id; + init_data = mc13xxx_data[i].init_data; + node = mc13xxx_data[i].node; + } else { + id = pdata->regulators[i].id; + init_data = pdata->regulators[i].init_data; + } + desc = &mc13783_regulators[id].desc; + + config.dev = &pdev->dev; + config.init_data = init_data; + config.driver_data = priv; + config.of_node = node; + + priv->regulators[i] = devm_regulator_register(&pdev->dev, desc, + &config); + if (IS_ERR(priv->regulators[i])) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + mc13783_regulators[i].desc.name); + return PTR_ERR(priv->regulators[i]); + } + } + + return 0; +} + +static struct platform_driver mc13783_regulator_driver = { + .driver = { + .name = "mc13783-regulator", + }, + .probe = mc13783_regulator_probe, +}; + +static int __init mc13783_regulator_init(void) +{ + return platform_driver_register(&mc13783_regulator_driver); +} +subsys_initcall(mc13783_regulator_init); + +static void __exit mc13783_regulator_exit(void) +{ + platform_driver_unregister(&mc13783_regulator_driver); +} +module_exit(mc13783_regulator_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_DESCRIPTION("Regulator Driver for Freescale MC13783 PMIC"); +MODULE_ALIAS("platform:mc13783-regulator"); diff --git a/drivers/regulator/mc13892-regulator.c b/drivers/regulator/mc13892-regulator.c new file mode 100644 index 000000000..f3fba1cc1 --- /dev/null +++ b/drivers/regulator/mc13892-regulator.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Regulator Driver for Freescale MC13892 PMIC +// +// Copyright 2010 Yong Shen <yong.shen@linaro.org> +// +// Based on draft driver from Arnaud Patard <arnaud.patard@rtp-net.org> + +#include <linux/mfd/mc13892.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include "mc13xxx.h" + +#define MC13892_REVISION 7 + +#define MC13892_POWERCTL0 13 +#define MC13892_POWERCTL0_USEROFFSPI 3 +#define MC13892_POWERCTL0_VCOINCELLVSEL 20 +#define MC13892_POWERCTL0_VCOINCELLVSEL_M (7<<20) +#define MC13892_POWERCTL0_VCOINCELLEN (1<<23) + +#define MC13892_SWITCHERS0_SWxHI (1<<23) + +#define MC13892_SWITCHERS0 24 +#define MC13892_SWITCHERS0_SW1VSEL 0 +#define MC13892_SWITCHERS0_SW1VSEL_M (0x1f<<0) +#define MC13892_SWITCHERS0_SW1HI (1<<23) +#define MC13892_SWITCHERS0_SW1EN 0 + +#define MC13892_SWITCHERS1 25 +#define MC13892_SWITCHERS1_SW2VSEL 0 +#define MC13892_SWITCHERS1_SW2VSEL_M (0x1f<<0) +#define MC13892_SWITCHERS1_SW2HI (1<<23) +#define MC13892_SWITCHERS1_SW2EN 0 + +#define MC13892_SWITCHERS2 26 +#define MC13892_SWITCHERS2_SW3VSEL 0 +#define MC13892_SWITCHERS2_SW3VSEL_M (0x1f<<0) +#define MC13892_SWITCHERS2_SW3HI (1<<23) +#define MC13892_SWITCHERS2_SW3EN 0 + +#define MC13892_SWITCHERS3 27 +#define MC13892_SWITCHERS3_SW4VSEL 0 +#define MC13892_SWITCHERS3_SW4VSEL_M (0x1f<<0) +#define MC13892_SWITCHERS3_SW4HI (1<<23) +#define MC13892_SWITCHERS3_SW4EN 0 + +#define MC13892_SWITCHERS4 28 +#define MC13892_SWITCHERS4_SW1MODE 0 +#define MC13892_SWITCHERS4_SW1MODE_AUTO (8<<0) +#define MC13892_SWITCHERS4_SW1MODE_M (0xf<<0) +#define MC13892_SWITCHERS4_SW2MODE 10 +#define MC13892_SWITCHERS4_SW2MODE_AUTO (8<<10) +#define MC13892_SWITCHERS4_SW2MODE_M (0xf<<10) + +#define MC13892_SWITCHERS5 29 +#define MC13892_SWITCHERS5_SW3MODE 0 +#define MC13892_SWITCHERS5_SW3MODE_AUTO (8<<0) +#define MC13892_SWITCHERS5_SW3MODE_M (0xf<<0) +#define MC13892_SWITCHERS5_SW4MODE 8 +#define MC13892_SWITCHERS5_SW4MODE_AUTO (8<<8) +#define MC13892_SWITCHERS5_SW4MODE_M (0xf<<8) +#define MC13892_SWITCHERS5_SWBSTEN (1<<20) + +#define MC13892_REGULATORSETTING0 30 +#define MC13892_REGULATORSETTING0_VGEN1VSEL 0 +#define MC13892_REGULATORSETTING0_VDIGVSEL 4 +#define MC13892_REGULATORSETTING0_VGEN2VSEL 6 +#define MC13892_REGULATORSETTING0_VPLLVSEL 9 +#define MC13892_REGULATORSETTING0_VUSB2VSEL 11 +#define MC13892_REGULATORSETTING0_VGEN3VSEL 14 +#define MC13892_REGULATORSETTING0_VCAMVSEL 16 + +#define MC13892_REGULATORSETTING0_VGEN1VSEL_M (3<<0) +#define MC13892_REGULATORSETTING0_VDIGVSEL_M (3<<4) +#define MC13892_REGULATORSETTING0_VGEN2VSEL_M (7<<6) +#define MC13892_REGULATORSETTING0_VPLLVSEL_M (3<<9) +#define MC13892_REGULATORSETTING0_VUSB2VSEL_M (3<<11) +#define MC13892_REGULATORSETTING0_VGEN3VSEL_M (1<<14) +#define MC13892_REGULATORSETTING0_VCAMVSEL_M (3<<16) + +#define MC13892_REGULATORSETTING1 31 +#define MC13892_REGULATORSETTING1_VVIDEOVSEL 2 +#define MC13892_REGULATORSETTING1_VAUDIOVSEL 4 +#define MC13892_REGULATORSETTING1_VSDVSEL 6 + +#define MC13892_REGULATORSETTING1_VVIDEOVSEL_M (3<<2) +#define MC13892_REGULATORSETTING1_VAUDIOVSEL_M (3<<4) +#define MC13892_REGULATORSETTING1_VSDVSEL_M (7<<6) + +#define MC13892_REGULATORMODE0 32 +#define MC13892_REGULATORMODE0_VGEN1EN (1<<0) +#define MC13892_REGULATORMODE0_VGEN1STDBY (1<<1) +#define MC13892_REGULATORMODE0_VGEN1MODE (1<<2) +#define MC13892_REGULATORMODE0_VIOHIEN (1<<3) +#define MC13892_REGULATORMODE0_VIOHISTDBY (1<<4) +#define MC13892_REGULATORMODE0_VIOHIMODE (1<<5) +#define MC13892_REGULATORMODE0_VDIGEN (1<<9) +#define MC13892_REGULATORMODE0_VDIGSTDBY (1<<10) +#define MC13892_REGULATORMODE0_VDIGMODE (1<<11) +#define MC13892_REGULATORMODE0_VGEN2EN (1<<12) +#define MC13892_REGULATORMODE0_VGEN2STDBY (1<<13) +#define MC13892_REGULATORMODE0_VGEN2MODE (1<<14) +#define MC13892_REGULATORMODE0_VPLLEN (1<<15) +#define MC13892_REGULATORMODE0_VPLLSTDBY (1<<16) +#define MC13892_REGULATORMODE0_VPLLMODE (1<<17) +#define MC13892_REGULATORMODE0_VUSB2EN (1<<18) +#define MC13892_REGULATORMODE0_VUSB2STDBY (1<<19) +#define MC13892_REGULATORMODE0_VUSB2MODE (1<<20) + +#define MC13892_REGULATORMODE1 33 +#define MC13892_REGULATORMODE1_VGEN3EN (1<<0) +#define MC13892_REGULATORMODE1_VGEN3STDBY (1<<1) +#define MC13892_REGULATORMODE1_VGEN3MODE (1<<2) +#define MC13892_REGULATORMODE1_VCAMEN (1<<6) +#define MC13892_REGULATORMODE1_VCAMSTDBY (1<<7) +#define MC13892_REGULATORMODE1_VCAMMODE (1<<8) +#define MC13892_REGULATORMODE1_VCAMCONFIGEN (1<<9) +#define MC13892_REGULATORMODE1_VVIDEOEN (1<<12) +#define MC13892_REGULATORMODE1_VVIDEOSTDBY (1<<13) +#define MC13892_REGULATORMODE1_VVIDEOMODE (1<<14) +#define MC13892_REGULATORMODE1_VAUDIOEN (1<<15) +#define MC13892_REGULATORMODE1_VAUDIOSTDBY (1<<16) +#define MC13892_REGULATORMODE1_VAUDIOMODE (1<<17) +#define MC13892_REGULATORMODE1_VSDEN (1<<18) +#define MC13892_REGULATORMODE1_VSDSTDBY (1<<19) +#define MC13892_REGULATORMODE1_VSDMODE (1<<20) + +#define MC13892_POWERMISC 34 +#define MC13892_POWERMISC_GPO1EN (1<<6) +#define MC13892_POWERMISC_GPO2EN (1<<8) +#define MC13892_POWERMISC_GPO3EN (1<<10) +#define MC13892_POWERMISC_GPO4EN (1<<12) +#define MC13892_POWERMISC_PWGT1SPIEN (1<<15) +#define MC13892_POWERMISC_PWGT2SPIEN (1<<16) +#define MC13892_POWERMISC_GPO4ADINEN (1<<21) + +#define MC13892_POWERMISC_PWGTSPI_M (3 << 15) + +#define MC13892_USB1 50 +#define MC13892_USB1_VUSBEN (1<<3) + +static const unsigned int mc13892_vcoincell[] = { + 2500000, 2700000, 2800000, 2900000, 3000000, 3100000, + 3200000, 3300000, +}; + +static const unsigned int mc13892_sw1[] = { + 600000, 625000, 650000, 675000, 700000, 725000, + 750000, 775000, 800000, 825000, 850000, 875000, + 900000, 925000, 950000, 975000, 1000000, 1025000, + 1050000, 1075000, 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, 1300000, 1325000, + 1350000, 1375000 +}; + +/* + * Note: this table is used to derive SWxVSEL by index into + * the array. Offset the values by the index of 1100000uV + * to get the actual register value for that voltage selector + * if the HI bit is to be set as well. + */ +#define MC13892_SWxHI_SEL_OFFSET 20 + +static const unsigned int mc13892_sw[] = { + 600000, 625000, 650000, 675000, 700000, 725000, + 750000, 775000, 800000, 825000, 850000, 875000, + 900000, 925000, 950000, 975000, 1000000, 1025000, + 1050000, 1075000, 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, 1300000, 1325000, + 1350000, 1375000, 1400000, 1425000, 1450000, 1475000, + 1500000, 1525000, 1550000, 1575000, 1600000, 1625000, + 1650000, 1675000, 1700000, 1725000, 1750000, 1775000, + 1800000, 1825000, 1850000, 1875000 +}; + +static const unsigned int mc13892_swbst[] = { + 5000000, +}; + +static const unsigned int mc13892_viohi[] = { + 2775000, +}; + +static const unsigned int mc13892_vpll[] = { + 1050000, 1250000, 1650000, 1800000, +}; + +static const unsigned int mc13892_vdig[] = { + 1050000, 1250000, 1650000, 1800000, +}; + +static const unsigned int mc13892_vsd[] = { + 1800000, 2000000, 2600000, 2700000, + 2800000, 2900000, 3000000, 3150000, +}; + +static const unsigned int mc13892_vusb2[] = { + 2400000, 2600000, 2700000, 2775000, +}; + +static const unsigned int mc13892_vvideo[] = { + 2700000, 2775000, 2500000, 2600000, +}; + +static const unsigned int mc13892_vaudio[] = { + 2300000, 2500000, 2775000, 3000000, +}; + +static const unsigned int mc13892_vcam[] = { + 2500000, 2600000, 2750000, 3000000, +}; + +static const unsigned int mc13892_vgen1[] = { + 1200000, 1500000, 2775000, 3150000, +}; + +static const unsigned int mc13892_vgen2[] = { + 1200000, 1500000, 1600000, 1800000, + 2700000, 2800000, 3000000, 3150000, +}; + +static const unsigned int mc13892_vgen3[] = { + 1800000, 2900000, +}; + +static const unsigned int mc13892_vusb[] = { + 3300000, +}; + +static const unsigned int mc13892_gpo[] = { + 2750000, +}; + +static const unsigned int mc13892_pwgtdrv[] = { + 5000000, +}; + +static struct regulator_ops mc13892_gpo_regulator_ops; +static struct regulator_ops mc13892_sw_regulator_ops; + + +#define MC13892_FIXED_DEFINE(name, reg, voltages) \ + MC13xxx_FIXED_DEFINE(MC13892_, name, reg, voltages, \ + mc13xxx_fixed_regulator_ops) + +#define MC13892_GPO_DEFINE(name, reg, voltages) \ + MC13xxx_GPO_DEFINE(MC13892_, name, reg, voltages, \ + mc13892_gpo_regulator_ops) + +#define MC13892_SW_DEFINE(name, reg, vsel_reg, voltages) \ + MC13xxx_DEFINE(MC13892_, name, reg, vsel_reg, voltages, \ + mc13892_sw_regulator_ops) + +#define MC13892_DEFINE_REGU(name, reg, vsel_reg, voltages) \ + MC13xxx_DEFINE(MC13892_, name, reg, vsel_reg, voltages, \ + mc13xxx_regulator_ops) + +static struct mc13xxx_regulator mc13892_regulators[] = { + MC13892_DEFINE_REGU(VCOINCELL, POWERCTL0, POWERCTL0, mc13892_vcoincell), + MC13892_SW_DEFINE(SW1, SWITCHERS0, SWITCHERS0, mc13892_sw1), + MC13892_SW_DEFINE(SW2, SWITCHERS1, SWITCHERS1, mc13892_sw), + MC13892_SW_DEFINE(SW3, SWITCHERS2, SWITCHERS2, mc13892_sw), + MC13892_SW_DEFINE(SW4, SWITCHERS3, SWITCHERS3, mc13892_sw), + MC13892_FIXED_DEFINE(SWBST, SWITCHERS5, mc13892_swbst), + MC13892_FIXED_DEFINE(VIOHI, REGULATORMODE0, mc13892_viohi), + MC13892_DEFINE_REGU(VPLL, REGULATORMODE0, REGULATORSETTING0, + mc13892_vpll), + MC13892_DEFINE_REGU(VDIG, REGULATORMODE0, REGULATORSETTING0, + mc13892_vdig), + MC13892_DEFINE_REGU(VSD, REGULATORMODE1, REGULATORSETTING1, + mc13892_vsd), + MC13892_DEFINE_REGU(VUSB2, REGULATORMODE0, REGULATORSETTING0, + mc13892_vusb2), + MC13892_DEFINE_REGU(VVIDEO, REGULATORMODE1, REGULATORSETTING1, + mc13892_vvideo), + MC13892_DEFINE_REGU(VAUDIO, REGULATORMODE1, REGULATORSETTING1, + mc13892_vaudio), + MC13892_DEFINE_REGU(VCAM, REGULATORMODE1, REGULATORSETTING0, + mc13892_vcam), + MC13892_DEFINE_REGU(VGEN1, REGULATORMODE0, REGULATORSETTING0, + mc13892_vgen1), + MC13892_DEFINE_REGU(VGEN2, REGULATORMODE0, REGULATORSETTING0, + mc13892_vgen2), + MC13892_DEFINE_REGU(VGEN3, REGULATORMODE1, REGULATORSETTING0, + mc13892_vgen3), + MC13892_FIXED_DEFINE(VUSB, USB1, mc13892_vusb), + MC13892_GPO_DEFINE(GPO1, POWERMISC, mc13892_gpo), + MC13892_GPO_DEFINE(GPO2, POWERMISC, mc13892_gpo), + MC13892_GPO_DEFINE(GPO3, POWERMISC, mc13892_gpo), + MC13892_GPO_DEFINE(GPO4, POWERMISC, mc13892_gpo), + MC13892_GPO_DEFINE(PWGT1SPI, POWERMISC, mc13892_pwgtdrv), + MC13892_GPO_DEFINE(PWGT2SPI, POWERMISC, mc13892_pwgtdrv), +}; + +static int mc13892_powermisc_rmw(struct mc13xxx_regulator_priv *priv, u32 mask, + u32 val) +{ + struct mc13xxx *mc13892 = priv->mc13xxx; + int ret; + u32 valread; + + BUG_ON(val & ~mask); + + mc13xxx_lock(priv->mc13xxx); + ret = mc13xxx_reg_read(mc13892, MC13892_POWERMISC, &valread); + if (ret) + goto out; + + /* Update the stored state for Power Gates. */ + priv->powermisc_pwgt_state = + (priv->powermisc_pwgt_state & ~mask) | val; + priv->powermisc_pwgt_state &= MC13892_POWERMISC_PWGTSPI_M; + + /* Construct the new register value */ + valread = (valread & ~mask) | val; + /* Overwrite the PWGTxEN with the stored version */ + valread = (valread & ~MC13892_POWERMISC_PWGTSPI_M) | + priv->powermisc_pwgt_state; + + ret = mc13xxx_reg_write(mc13892, MC13892_POWERMISC, valread); +out: + mc13xxx_unlock(priv->mc13xxx); + return ret; +} + +static int mc13892_gpo_regulator_enable(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + u32 en_val = mc13892_regulators[id].enable_bit; + u32 mask = mc13892_regulators[id].enable_bit; + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + /* Power Gate enable value is 0 */ + if (id == MC13892_PWGT1SPI || id == MC13892_PWGT2SPI) + en_val = 0; + + if (id == MC13892_GPO4) + mask |= MC13892_POWERMISC_GPO4ADINEN; + + return mc13892_powermisc_rmw(priv, mask, en_val); +} + +static int mc13892_gpo_regulator_disable(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + u32 dis_val = 0; + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + /* Power Gate disable value is 1 */ + if (id == MC13892_PWGT1SPI || id == MC13892_PWGT2SPI) + dis_val = mc13892_regulators[id].enable_bit; + + return mc13892_powermisc_rmw(priv, mc13892_regulators[id].enable_bit, + dis_val); +} + +static int mc13892_gpo_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + int ret, id = rdev_get_id(rdev); + unsigned int val; + + mc13xxx_lock(priv->mc13xxx); + ret = mc13xxx_reg_read(priv->mc13xxx, mc13892_regulators[id].reg, &val); + mc13xxx_unlock(priv->mc13xxx); + + if (ret) + return ret; + + /* Power Gates state is stored in powermisc_pwgt_state + * where the meaning of bits is negated */ + val = (val & ~MC13892_POWERMISC_PWGTSPI_M) | + (priv->powermisc_pwgt_state ^ MC13892_POWERMISC_PWGTSPI_M); + + return (val & mc13892_regulators[id].enable_bit) != 0; +} + + +static struct regulator_ops mc13892_gpo_regulator_ops = { + .enable = mc13892_gpo_regulator_enable, + .disable = mc13892_gpo_regulator_disable, + .is_enabled = mc13892_gpo_regulator_is_enabled, + .list_voltage = regulator_list_voltage_table, + .set_voltage = mc13xxx_fixed_regulator_set_voltage, +}; + +static int mc13892_sw_regulator_get_voltage_sel(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + int ret, id = rdev_get_id(rdev); + unsigned int val, selector; + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + mc13xxx_lock(priv->mc13xxx); + ret = mc13xxx_reg_read(priv->mc13xxx, + mc13892_regulators[id].vsel_reg, &val); + mc13xxx_unlock(priv->mc13xxx); + if (ret) + return ret; + + /* + * Figure out if the HI bit is set inside the switcher mode register + * since this means the selector value we return is at a different + * offset into the selector table. + * + * According to the MC13892 documentation note 59 (Table 47) the SW1 + * buck switcher does not support output range programming therefore + * the HI bit must always remain 0. So do not do anything strange if + * our register is MC13892_SWITCHERS0. + */ + + selector = val & mc13892_regulators[id].vsel_mask; + + if ((mc13892_regulators[id].vsel_reg != MC13892_SWITCHERS0) && + (val & MC13892_SWITCHERS0_SWxHI)) { + selector += MC13892_SWxHI_SEL_OFFSET; + } + + dev_dbg(rdev_get_dev(rdev), "%s id: %d val: 0x%08x selector: %d\n", + __func__, id, val, selector); + + return selector; +} + +static int mc13892_sw_regulator_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + int volt, mask, id = rdev_get_id(rdev); + u32 reg_value; + int ret; + + volt = rdev->desc->volt_table[selector]; + mask = mc13892_regulators[id].vsel_mask; + reg_value = selector; + + /* + * Don't mess with the HI bit or support HI voltage offsets for SW1. + * + * Since the get_voltage_sel callback has given a fudged value for + * the selector offset, we need to back out that offset if HI is + * to be set so we write the correct value to the register. + * + * The HI bit addition and selector offset handling COULD be more + * complicated by shifting and masking off the voltage selector part + * of the register then logical OR it back in, but since the selector + * is at bits 4:0 there is very little point. This makes the whole + * thing more readable and we do far less work. + */ + + if (mc13892_regulators[id].vsel_reg != MC13892_SWITCHERS0) { + mask |= MC13892_SWITCHERS0_SWxHI; + + if (volt > 1375000) { + reg_value -= MC13892_SWxHI_SEL_OFFSET; + reg_value |= MC13892_SWITCHERS0_SWxHI; + } else { + reg_value &= ~MC13892_SWITCHERS0_SWxHI; + } + } + + mc13xxx_lock(priv->mc13xxx); + ret = mc13xxx_reg_rmw(priv->mc13xxx, mc13892_regulators[id].vsel_reg, + mask, reg_value); + mc13xxx_unlock(priv->mc13xxx); + + return ret; +} + +static struct regulator_ops mc13892_sw_regulator_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_voltage_sel = mc13892_sw_regulator_set_voltage_sel, + .get_voltage_sel = mc13892_sw_regulator_get_voltage_sel, +}; + +static int mc13892_vcam_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + unsigned int en_val = 0; + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + int ret, id = rdev_get_id(rdev); + + if (mode == REGULATOR_MODE_FAST) + en_val = MC13892_REGULATORMODE1_VCAMCONFIGEN; + + mc13xxx_lock(priv->mc13xxx); + ret = mc13xxx_reg_rmw(priv->mc13xxx, mc13892_regulators[id].reg, + MC13892_REGULATORMODE1_VCAMCONFIGEN, en_val); + mc13xxx_unlock(priv->mc13xxx); + + return ret; +} + +static unsigned int mc13892_vcam_get_mode(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + int ret, id = rdev_get_id(rdev); + unsigned int val; + + mc13xxx_lock(priv->mc13xxx); + ret = mc13xxx_reg_read(priv->mc13xxx, mc13892_regulators[id].reg, &val); + mc13xxx_unlock(priv->mc13xxx); + + if (ret) + return ret; + + if (val & MC13892_REGULATORMODE1_VCAMCONFIGEN) + return REGULATOR_MODE_FAST; + + return REGULATOR_MODE_NORMAL; +} + +static struct regulator_ops mc13892_vcam_ops; + +static int mc13892_regulator_probe(struct platform_device *pdev) +{ + struct mc13xxx_regulator_priv *priv; + struct mc13xxx *mc13892 = dev_get_drvdata(pdev->dev.parent); + struct mc13xxx_regulator_platform_data *pdata = + dev_get_platdata(&pdev->dev); + struct mc13xxx_regulator_init_data *mc13xxx_data; + struct regulator_config config = { }; + int i, ret; + int num_regulators = 0; + u32 val; + + num_regulators = mc13xxx_get_num_regulators_dt(pdev); + + if (num_regulators <= 0 && pdata) + num_regulators = pdata->num_regulators; + if (num_regulators <= 0) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, + struct_size(priv, regulators, num_regulators), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->num_regulators = num_regulators; + priv->mc13xxx_regulators = mc13892_regulators; + priv->mc13xxx = mc13892; + platform_set_drvdata(pdev, priv); + + mc13xxx_lock(mc13892); + ret = mc13xxx_reg_read(mc13892, MC13892_REVISION, &val); + if (ret) + goto err_unlock; + + /* enable switch auto mode (on 2.0A silicon only) */ + if ((val & 0x0000FFFF) == 0x45d0) { + ret = mc13xxx_reg_rmw(mc13892, MC13892_SWITCHERS4, + MC13892_SWITCHERS4_SW1MODE_M | + MC13892_SWITCHERS4_SW2MODE_M, + MC13892_SWITCHERS4_SW1MODE_AUTO | + MC13892_SWITCHERS4_SW2MODE_AUTO); + if (ret) + goto err_unlock; + + ret = mc13xxx_reg_rmw(mc13892, MC13892_SWITCHERS5, + MC13892_SWITCHERS5_SW3MODE_M | + MC13892_SWITCHERS5_SW4MODE_M, + MC13892_SWITCHERS5_SW3MODE_AUTO | + MC13892_SWITCHERS5_SW4MODE_AUTO); + if (ret) + goto err_unlock; + } + mc13xxx_unlock(mc13892); + + /* update mc13892_vcam ops */ + memcpy(&mc13892_vcam_ops, mc13892_regulators[MC13892_VCAM].desc.ops, + sizeof(struct regulator_ops)); + mc13892_vcam_ops.set_mode = mc13892_vcam_set_mode, + mc13892_vcam_ops.get_mode = mc13892_vcam_get_mode, + mc13892_regulators[MC13892_VCAM].desc.ops = &mc13892_vcam_ops; + + mc13xxx_data = mc13xxx_parse_regulators_dt(pdev, mc13892_regulators, + ARRAY_SIZE(mc13892_regulators)); + + for (i = 0; i < priv->num_regulators; i++) { + struct regulator_init_data *init_data; + struct regulator_desc *desc; + struct device_node *node = NULL; + int id; + + if (mc13xxx_data) { + id = mc13xxx_data[i].id; + init_data = mc13xxx_data[i].init_data; + node = mc13xxx_data[i].node; + } else { + id = pdata->regulators[i].id; + init_data = pdata->regulators[i].init_data; + } + desc = &mc13892_regulators[id].desc; + + config.dev = &pdev->dev; + config.init_data = init_data; + config.driver_data = priv; + config.of_node = node; + + priv->regulators[i] = devm_regulator_register(&pdev->dev, desc, + &config); + if (IS_ERR(priv->regulators[i])) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + mc13892_regulators[i].desc.name); + return PTR_ERR(priv->regulators[i]); + } + } + + return 0; + +err_unlock: + mc13xxx_unlock(mc13892); + return ret; +} + +static struct platform_driver mc13892_regulator_driver = { + .driver = { + .name = "mc13892-regulator", + }, + .probe = mc13892_regulator_probe, +}; + +static int __init mc13892_regulator_init(void) +{ + return platform_driver_register(&mc13892_regulator_driver); +} +subsys_initcall(mc13892_regulator_init); + +static void __exit mc13892_regulator_exit(void) +{ + platform_driver_unregister(&mc13892_regulator_driver); +} +module_exit(mc13892_regulator_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yong Shen <yong.shen@linaro.org>"); +MODULE_DESCRIPTION("Regulator Driver for Freescale MC13892 PMIC"); +MODULE_ALIAS("platform:mc13892-regulator"); diff --git a/drivers/regulator/mc13xxx-regulator-core.c b/drivers/regulator/mc13xxx-regulator-core.c new file mode 100644 index 000000000..da4fb9824 --- /dev/null +++ b/drivers/regulator/mc13xxx-regulator-core.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Regulator Driver for Freescale MC13xxx PMIC +// +// Copyright 2010 Yong Shen <yong.shen@linaro.org> +// +// Based on mc13783 regulator driver : +// Copyright (C) 2008 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> +// Copyright 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com> +// +// Regs infos taken from mc13xxx drivers from freescale and mc13xxx.pdf file +// from freescale + +#include <linux/mfd/mc13xxx.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include "mc13xxx.h" + +static int mc13xxx_regulator_enable(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + struct mc13xxx_regulator *mc13xxx_regulators = priv->mc13xxx_regulators; + int id = rdev_get_id(rdev); + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + return mc13xxx_reg_rmw(priv->mc13xxx, mc13xxx_regulators[id].reg, + mc13xxx_regulators[id].enable_bit, + mc13xxx_regulators[id].enable_bit); +} + +static int mc13xxx_regulator_disable(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + struct mc13xxx_regulator *mc13xxx_regulators = priv->mc13xxx_regulators; + int id = rdev_get_id(rdev); + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + return mc13xxx_reg_rmw(priv->mc13xxx, mc13xxx_regulators[id].reg, + mc13xxx_regulators[id].enable_bit, 0); +} + +static int mc13xxx_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + struct mc13xxx_regulator *mc13xxx_regulators = priv->mc13xxx_regulators; + int ret, id = rdev_get_id(rdev); + unsigned int val; + + ret = mc13xxx_reg_read(priv->mc13xxx, mc13xxx_regulators[id].reg, &val); + if (ret) + return ret; + + return (val & mc13xxx_regulators[id].enable_bit) != 0; +} + +static int mc13xxx_regulator_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + struct mc13xxx_regulator *mc13xxx_regulators = priv->mc13xxx_regulators; + int id = rdev_get_id(rdev); + + return mc13xxx_reg_rmw(priv->mc13xxx, mc13xxx_regulators[id].vsel_reg, + mc13xxx_regulators[id].vsel_mask, + selector << mc13xxx_regulators[id].vsel_shift); +} + +static int mc13xxx_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); + struct mc13xxx_regulator *mc13xxx_regulators = priv->mc13xxx_regulators; + int ret, id = rdev_get_id(rdev); + unsigned int val; + + dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); + + ret = mc13xxx_reg_read(priv->mc13xxx, + mc13xxx_regulators[id].vsel_reg, &val); + if (ret) + return ret; + + val = (val & mc13xxx_regulators[id].vsel_mask) + >> mc13xxx_regulators[id].vsel_shift; + + dev_dbg(rdev_get_dev(rdev), "%s id: %d val: %d\n", __func__, id, val); + + BUG_ON(val >= mc13xxx_regulators[id].desc.n_voltages); + + return rdev->desc->volt_table[val]; +} + +struct regulator_ops mc13xxx_regulator_ops = { + .enable = mc13xxx_regulator_enable, + .disable = mc13xxx_regulator_disable, + .is_enabled = mc13xxx_regulator_is_enabled, + .list_voltage = regulator_list_voltage_table, + .set_voltage_sel = mc13xxx_regulator_set_voltage_sel, + .get_voltage = mc13xxx_regulator_get_voltage, +}; +EXPORT_SYMBOL_GPL(mc13xxx_regulator_ops); + +int mc13xxx_fixed_regulator_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + int id = rdev_get_id(rdev); + + dev_dbg(rdev_get_dev(rdev), "%s id: %d min_uV: %d max_uV: %d\n", + __func__, id, min_uV, max_uV); + + if (min_uV <= rdev->desc->volt_table[0] && + rdev->desc->volt_table[0] <= max_uV) { + *selector = 0; + return 0; + } else { + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(mc13xxx_fixed_regulator_set_voltage); + +struct regulator_ops mc13xxx_fixed_regulator_ops = { + .enable = mc13xxx_regulator_enable, + .disable = mc13xxx_regulator_disable, + .is_enabled = mc13xxx_regulator_is_enabled, + .list_voltage = regulator_list_voltage_table, + .set_voltage = mc13xxx_fixed_regulator_set_voltage, +}; +EXPORT_SYMBOL_GPL(mc13xxx_fixed_regulator_ops); + +#ifdef CONFIG_OF +int mc13xxx_get_num_regulators_dt(struct platform_device *pdev) +{ + struct device_node *parent; + int num; + + if (!pdev->dev.parent->of_node) + return -ENODEV; + + parent = of_get_child_by_name(pdev->dev.parent->of_node, "regulators"); + if (!parent) + return -ENODEV; + + num = of_get_child_count(parent); + of_node_put(parent); + return num; +} +EXPORT_SYMBOL_GPL(mc13xxx_get_num_regulators_dt); + +struct mc13xxx_regulator_init_data *mc13xxx_parse_regulators_dt( + struct platform_device *pdev, struct mc13xxx_regulator *regulators, + int num_regulators) +{ + struct mc13xxx_regulator_priv *priv = platform_get_drvdata(pdev); + struct mc13xxx_regulator_init_data *data, *p; + struct device_node *parent, *child; + int i, parsed = 0; + + if (!pdev->dev.parent->of_node) + return NULL; + + parent = of_get_child_by_name(pdev->dev.parent->of_node, "regulators"); + if (!parent) + return NULL; + + data = devm_kcalloc(&pdev->dev, priv->num_regulators, sizeof(*data), + GFP_KERNEL); + if (!data) { + of_node_put(parent); + return NULL; + } + + p = data; + + for_each_child_of_node(parent, child) { + int found = 0; + + for (i = 0; i < num_regulators; i++) { + if (!regulators[i].desc.name) + continue; + if (!of_node_cmp(child->name, + regulators[i].desc.name)) { + p->id = i; + p->init_data = of_get_regulator_init_data( + &pdev->dev, child, + ®ulators[i].desc); + p->node = child; + p++; + + parsed++; + found = 1; + break; + } + } + + if (!found) + dev_warn(&pdev->dev, + "Unknown regulator: %s\n", child->name); + } + of_node_put(parent); + + priv->num_regulators = parsed; + + return data; +} +EXPORT_SYMBOL_GPL(mc13xxx_parse_regulators_dt); +#endif + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yong Shen <yong.shen@linaro.org>"); +MODULE_DESCRIPTION("Regulator Driver for Freescale MC13xxx PMIC"); +MODULE_ALIAS("mc13xxx-regulator-core"); diff --git a/drivers/regulator/mc13xxx.h b/drivers/regulator/mc13xxx.h new file mode 100644 index 000000000..2ab9bfd93 --- /dev/null +++ b/drivers/regulator/mc13xxx.h @@ -0,0 +1,112 @@ +/* + * mc13xxx.h - regulators for the Freescale mc13xxx PMIC + * + * Copyright (C) 2010 Yong Shen <yong.shen@linaro.org> + * + * 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. + */ + +#ifndef __LINUX_REGULATOR_MC13XXX_H +#define __LINUX_REGULATOR_MC13XXX_H + +#include <linux/regulator/driver.h> + +struct mc13xxx_regulator { + struct regulator_desc desc; + int reg; + int enable_bit; + int vsel_reg; + int vsel_shift; + int vsel_mask; +}; + +struct mc13xxx_regulator_priv { + struct mc13xxx *mc13xxx; + u32 powermisc_pwgt_state; + struct mc13xxx_regulator *mc13xxx_regulators; + int num_regulators; + struct regulator_dev *regulators[]; +}; + +extern int mc13xxx_fixed_regulator_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector); + +#ifdef CONFIG_OF +extern int mc13xxx_get_num_regulators_dt(struct platform_device *pdev); +extern struct mc13xxx_regulator_init_data *mc13xxx_parse_regulators_dt( + struct platform_device *pdev, struct mc13xxx_regulator *regulators, + int num_regulators); +#else +static inline int mc13xxx_get_num_regulators_dt(struct platform_device *pdev) +{ + return -ENODEV; +} + +static inline struct mc13xxx_regulator_init_data *mc13xxx_parse_regulators_dt( + struct platform_device *pdev, struct mc13xxx_regulator *regulators, + int num_regulators) +{ + return NULL; +} +#endif + +extern struct regulator_ops mc13xxx_regulator_ops; +extern struct regulator_ops mc13xxx_fixed_regulator_ops; + +#define MC13xxx_DEFINE(prefix, _name, _reg, _vsel_reg, _voltages, _ops) \ + [prefix ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = ARRAY_SIZE(_voltages), \ + .volt_table = _voltages, \ + .ops = &_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = prefix ## _name, \ + .owner = THIS_MODULE, \ + }, \ + .reg = prefix ## _reg, \ + .enable_bit = prefix ## _reg ## _ ## _name ## EN, \ + .vsel_reg = prefix ## _vsel_reg, \ + .vsel_shift = prefix ## _vsel_reg ## _ ## _name ## VSEL,\ + .vsel_mask = prefix ## _vsel_reg ## _ ## _name ## VSEL_M,\ + } + +#define MC13xxx_FIXED_DEFINE(prefix, _name, _reg, _voltages, _ops) \ + [prefix ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = ARRAY_SIZE(_voltages), \ + .volt_table = _voltages, \ + .ops = &_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = prefix ## _name, \ + .owner = THIS_MODULE, \ + }, \ + .reg = prefix ## _reg, \ + .enable_bit = prefix ## _reg ## _ ## _name ## EN, \ + } + +#define MC13xxx_GPO_DEFINE(prefix, _name, _reg, _voltages, _ops) \ + [prefix ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = ARRAY_SIZE(_voltages), \ + .volt_table = _voltages, \ + .ops = &_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = prefix ## _name, \ + .owner = THIS_MODULE, \ + }, \ + .reg = prefix ## _reg, \ + .enable_bit = prefix ## _reg ## _ ## _name ## EN, \ + } + +#define MC13xxx_DEFINE_SW(_name, _reg, _vsel_reg, _voltages, ops) \ + MC13xxx_DEFINE(SW, _name, _reg, _vsel_reg, _voltages, ops) +#define MC13xxx_DEFINE_REGU(_name, _reg, _vsel_reg, _voltages, ops) \ + MC13xxx_DEFINE(REGU, _name, _reg, _vsel_reg, _voltages, ops) + +#endif diff --git a/drivers/regulator/mt6311-regulator.c b/drivers/regulator/mt6311-regulator.c new file mode 100644 index 000000000..0495716fd --- /dev/null +++ b/drivers/regulator/mt6311-regulator.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: Henry Chen <henryc.chen@mediatek.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/mt6311.h> +#include <linux/slab.h> +#include "mt6311-regulator.h" + +static const struct regmap_config mt6311_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MT6311_FQMTR_CON4, + .cache_type = REGCACHE_RBTREE, +}; + +/* Default limits measured in millivolts and milliamps */ +#define MT6311_MIN_UV 600000 +#define MT6311_MAX_UV 1393750 +#define MT6311_STEP_UV 6250 + +static const struct regulator_linear_range buck_volt_range[] = { + REGULATOR_LINEAR_RANGE(MT6311_MIN_UV, 0, 0x7f, MT6311_STEP_UV), +}; + +static const struct regulator_ops mt6311_buck_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct regulator_ops mt6311_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +#define MT6311_BUCK(_id) \ +{\ + .name = #_id,\ + .ops = &mt6311_buck_ops,\ + .of_match = of_match_ptr(#_id),\ + .regulators_node = of_match_ptr("regulators"),\ + .type = REGULATOR_VOLTAGE,\ + .id = MT6311_ID_##_id,\ + .n_voltages = (MT6311_MAX_UV - MT6311_MIN_UV) / MT6311_STEP_UV + 1,\ + .min_uV = MT6311_MIN_UV,\ + .uV_step = MT6311_STEP_UV,\ + .owner = THIS_MODULE,\ + .linear_ranges = buck_volt_range, \ + .n_linear_ranges = ARRAY_SIZE(buck_volt_range), \ + .enable_reg = MT6311_VDVFS11_CON9,\ + .enable_mask = MT6311_PMIC_VDVFS11_EN_MASK,\ + .vsel_reg = MT6311_VDVFS11_CON12,\ + .vsel_mask = MT6311_PMIC_VDVFS11_VOSEL_MASK,\ +} + +#define MT6311_LDO(_id) \ +{\ + .name = #_id,\ + .ops = &mt6311_ldo_ops,\ + .of_match = of_match_ptr(#_id),\ + .regulators_node = of_match_ptr("regulators"),\ + .type = REGULATOR_VOLTAGE,\ + .id = MT6311_ID_##_id,\ + .owner = THIS_MODULE,\ + .enable_reg = MT6311_LDO_CON3,\ + .enable_mask = MT6311_PMIC_RG_VBIASN_EN_MASK,\ +} + +static const struct regulator_desc mt6311_regulators[] = { + MT6311_BUCK(VDVFS), + MT6311_LDO(VBIASN), +}; + +/* + * I2C driver interface functions + */ +static int mt6311_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct regmap *regmap; + int i, ret; + unsigned int data; + + regmap = devm_regmap_init_i2c(i2c, &mt6311_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = regmap_read(regmap, MT6311_SWCID, &data); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read DEVICE_ID reg: %d\n", ret); + return ret; + } + + switch (data) { + case MT6311_E1_CID_CODE: + case MT6311_E2_CID_CODE: + case MT6311_E3_CID_CODE: + break; + default: + dev_err(&i2c->dev, "Unsupported device id = 0x%x.\n", data); + return -ENODEV; + } + + for (i = 0; i < MT6311_MAX_REGULATORS; i++) { + config.dev = &i2c->dev; + config.regmap = regmap; + + rdev = devm_regulator_register(&i2c->dev, + &mt6311_regulators[i], &config); + if (IS_ERR(rdev)) { + dev_err(&i2c->dev, + "Failed to register MT6311 regulator\n"); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct i2c_device_id mt6311_i2c_id[] = { + {"mt6311", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mt6311_i2c_id); + +#ifdef CONFIG_OF +static const struct of_device_id mt6311_dt_ids[] = { + { .compatible = "mediatek,mt6311-regulator", + .data = &mt6311_i2c_id[0] }, + {}, +}; +MODULE_DEVICE_TABLE(of, mt6311_dt_ids); +#endif + +static struct i2c_driver mt6311_regulator_driver = { + .driver = { + .name = "mt6311", + .of_match_table = of_match_ptr(mt6311_dt_ids), + }, + .probe = mt6311_i2c_probe, + .id_table = mt6311_i2c_id, +}; + +module_i2c_driver(mt6311_regulator_driver); + +MODULE_AUTHOR("Henry Chen <henryc.chen@mediatek.com>"); +MODULE_DESCRIPTION("Regulator device driver for Mediatek MT6311"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/mt6311-regulator.h b/drivers/regulator/mt6311-regulator.h new file mode 100644 index 000000000..5218db46a --- /dev/null +++ b/drivers/regulator/mt6311-regulator.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: Henry Chen <henryc.chen@mediatek.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MT6311_REGULATOR_H__ +#define __MT6311_REGULATOR_H__ + +#define MT6311_SWCID 0x01 + +#define MT6311_TOP_INT_CON 0x18 +#define MT6311_TOP_INT_MON 0x19 + +#define MT6311_VDVFS11_CON0 0x87 +#define MT6311_VDVFS11_CON7 0x88 +#define MT6311_VDVFS11_CON8 0x89 +#define MT6311_VDVFS11_CON9 0x8A +#define MT6311_VDVFS11_CON10 0x8B +#define MT6311_VDVFS11_CON11 0x8C +#define MT6311_VDVFS11_CON12 0x8D +#define MT6311_VDVFS11_CON13 0x8E +#define MT6311_VDVFS11_CON14 0x8F +#define MT6311_VDVFS11_CON15 0x90 +#define MT6311_VDVFS11_CON16 0x91 +#define MT6311_VDVFS11_CON17 0x92 +#define MT6311_VDVFS11_CON18 0x93 +#define MT6311_VDVFS11_CON19 0x94 + +#define MT6311_LDO_CON0 0xCC +#define MT6311_LDO_OCFB0 0xCD +#define MT6311_LDO_CON2 0xCE +#define MT6311_LDO_CON3 0xCF +#define MT6311_LDO_CON4 0xD0 +#define MT6311_FQMTR_CON0 0xD1 +#define MT6311_FQMTR_CON1 0xD2 +#define MT6311_FQMTR_CON2 0xD3 +#define MT6311_FQMTR_CON3 0xD4 +#define MT6311_FQMTR_CON4 0xD5 + +#define MT6311_PMIC_RG_INT_POL_MASK 0x1 +#define MT6311_PMIC_RG_INT_EN_MASK 0x2 +#define MT6311_PMIC_RG_BUCK_OC_INT_STATUS_MASK 0x10 + +#define MT6311_PMIC_VDVFS11_EN_CTRL_MASK 0x1 +#define MT6311_PMIC_VDVFS11_VOSEL_CTRL_MASK 0x2 +#define MT6311_PMIC_VDVFS11_EN_SEL_MASK 0x3 +#define MT6311_PMIC_VDVFS11_VOSEL_SEL_MASK 0xc +#define MT6311_PMIC_VDVFS11_EN_MASK 0x1 +#define MT6311_PMIC_VDVFS11_VOSEL_MASK 0x7F +#define MT6311_PMIC_VDVFS11_VOSEL_ON_MASK 0x7F +#define MT6311_PMIC_VDVFS11_VOSEL_SLEEP_MASK 0x7F +#define MT6311_PMIC_NI_VDVFS11_VOSEL_MASK 0x7F + +#define MT6311_PMIC_RG_VBIASN_EN_MASK 0x1 + +#endif diff --git a/drivers/regulator/mt6323-regulator.c b/drivers/regulator/mt6323-regulator.c new file mode 100644 index 000000000..b7b9670f0 --- /dev/null +++ b/drivers/regulator/mt6323-regulator.c @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Chen Zhong <chen.zhong@mediatek.com> + * + * 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/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/mt6397/core.h> +#include <linux/mfd/mt6323/registers.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/mt6323-regulator.h> +#include <linux/regulator/of_regulator.h> + +#define MT6323_LDO_MODE_NORMAL 0 +#define MT6323_LDO_MODE_LP 1 + +/* + * MT6323 regulators' information + * + * @desc: standard fields of regulator description. + * @qi: Mask for query enable signal status of regulators + * @vselon_reg: Register sections for hardware control mode of bucks + * @vselctrl_reg: Register for controlling the buck control mode. + * @vselctrl_mask: Mask for query buck's voltage control mode. + */ +struct mt6323_regulator_info { + struct regulator_desc desc; + u32 qi; + u32 vselon_reg; + u32 vselctrl_reg; + u32 vselctrl_mask; + u32 modeset_reg; + u32 modeset_mask; +}; + +#define MT6323_BUCK(match, vreg, min, max, step, volt_ranges, enreg, \ + vosel, vosel_mask, voselon, vosel_ctrl) \ +[MT6323_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6323_volt_range_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6323_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = (max - min)/step + 1, \ + .linear_ranges = volt_ranges, \ + .n_linear_ranges = ARRAY_SIZE(volt_ranges), \ + .vsel_reg = vosel, \ + .vsel_mask = vosel_mask, \ + .enable_reg = enreg, \ + .enable_mask = BIT(0), \ + }, \ + .qi = BIT(13), \ + .vselon_reg = voselon, \ + .vselctrl_reg = vosel_ctrl, \ + .vselctrl_mask = BIT(1), \ +} + +#define MT6323_LDO(match, vreg, ldo_volt_table, enreg, enbit, vosel, \ + vosel_mask, _modeset_reg, _modeset_mask) \ +[MT6323_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6323_volt_table_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6323_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(ldo_volt_table), \ + .volt_table = ldo_volt_table, \ + .vsel_reg = vosel, \ + .vsel_mask = vosel_mask, \ + .enable_reg = enreg, \ + .enable_mask = BIT(enbit), \ + }, \ + .qi = BIT(15), \ + .modeset_reg = _modeset_reg, \ + .modeset_mask = _modeset_mask, \ +} + +#define MT6323_REG_FIXED(match, vreg, enreg, enbit, volt, \ + _modeset_reg, _modeset_mask) \ +[MT6323_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6323_volt_fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6323_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = 1, \ + .enable_reg = enreg, \ + .enable_mask = BIT(enbit), \ + .min_uV = volt, \ + }, \ + .qi = BIT(15), \ + .modeset_reg = _modeset_reg, \ + .modeset_mask = _modeset_mask, \ +} + +static const struct regulator_linear_range buck_volt_range1[] = { + REGULATOR_LINEAR_RANGE(700000, 0, 0x7f, 6250), +}; + +static const struct regulator_linear_range buck_volt_range2[] = { + REGULATOR_LINEAR_RANGE(1400000, 0, 0x7f, 12500), +}; + +static const struct regulator_linear_range buck_volt_range3[] = { + REGULATOR_LINEAR_RANGE(500000, 0, 0x3f, 50000), +}; + +static const u32 ldo_volt_table1[] = { + 3300000, 3400000, 3500000, 3600000, +}; + +static const u32 ldo_volt_table2[] = { + 1500000, 1800000, 2500000, 2800000, +}; + +static const u32 ldo_volt_table3[] = { + 1800000, 3300000, +}; + +static const u32 ldo_volt_table4[] = { + 3000000, 3300000, +}; + +static const u32 ldo_volt_table5[] = { + 1200000, 1300000, 1500000, 1800000, 2000000, 2800000, 3000000, 3300000, +}; + +static const u32 ldo_volt_table6[] = { + 1200000, 1300000, 1500000, 1800000, 2500000, 2800000, 3000000, 2000000, +}; + +static const u32 ldo_volt_table7[] = { + 1200000, 1300000, 1500000, 1800000, +}; + +static const u32 ldo_volt_table8[] = { + 1800000, 3000000, +}; + +static const u32 ldo_volt_table9[] = { + 1200000, 1350000, 1500000, 1800000, +}; + +static const u32 ldo_volt_table10[] = { + 1200000, 1300000, 1500000, 1800000, +}; + +static int mt6323_get_status(struct regulator_dev *rdev) +{ + int ret; + u32 regval; + struct mt6323_regulator_info *info = rdev_get_drvdata(rdev); + + ret = regmap_read(rdev->regmap, info->desc.enable_reg, ®val); + if (ret != 0) { + dev_err(&rdev->dev, "Failed to get enable reg: %d\n", ret); + return ret; + } + + return (regval & info->qi) ? REGULATOR_STATUS_ON : REGULATOR_STATUS_OFF; +} + +static int mt6323_ldo_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + int ret, val = 0; + struct mt6323_regulator_info *info = rdev_get_drvdata(rdev); + + if (!info->modeset_mask) { + dev_err(&rdev->dev, "regulator %s doesn't support set_mode\n", + info->desc.name); + return -EINVAL; + } + + switch (mode) { + case REGULATOR_MODE_STANDBY: + val = MT6323_LDO_MODE_LP; + break; + case REGULATOR_MODE_NORMAL: + val = MT6323_LDO_MODE_NORMAL; + break; + default: + return -EINVAL; + } + + val <<= ffs(info->modeset_mask) - 1; + + ret = regmap_update_bits(rdev->regmap, info->modeset_reg, + info->modeset_mask, val); + + return ret; +} + +static unsigned int mt6323_ldo_get_mode(struct regulator_dev *rdev) +{ + unsigned int val; + unsigned int mode; + int ret; + struct mt6323_regulator_info *info = rdev_get_drvdata(rdev); + + if (!info->modeset_mask) { + dev_err(&rdev->dev, "regulator %s doesn't support get_mode\n", + info->desc.name); + return -EINVAL; + } + + ret = regmap_read(rdev->regmap, info->modeset_reg, &val); + if (ret < 0) + return ret; + + val &= info->modeset_mask; + val >>= ffs(info->modeset_mask) - 1; + + if (val & 0x1) + mode = REGULATOR_MODE_STANDBY; + else + mode = REGULATOR_MODE_NORMAL; + + return mode; +} + +static const struct regulator_ops mt6323_volt_range_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_status = mt6323_get_status, +}; + +static const struct regulator_ops mt6323_volt_table_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_status = mt6323_get_status, + .set_mode = mt6323_ldo_set_mode, + .get_mode = mt6323_ldo_get_mode, +}; + +static const struct regulator_ops mt6323_volt_fixed_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_status = mt6323_get_status, + .set_mode = mt6323_ldo_set_mode, + .get_mode = mt6323_ldo_get_mode, +}; + +/* The array is indexed by id(MT6323_ID_XXX) */ +static struct mt6323_regulator_info mt6323_regulators[] = { + MT6323_BUCK("buck_vproc", VPROC, 700000, 1493750, 6250, + buck_volt_range1, MT6323_VPROC_CON7, MT6323_VPROC_CON9, 0x7f, + MT6323_VPROC_CON10, MT6323_VPROC_CON5), + MT6323_BUCK("buck_vsys", VSYS, 1400000, 2987500, 12500, + buck_volt_range2, MT6323_VSYS_CON7, MT6323_VSYS_CON9, 0x7f, + MT6323_VSYS_CON10, MT6323_VSYS_CON5), + MT6323_BUCK("buck_vpa", VPA, 500000, 3650000, 50000, + buck_volt_range3, MT6323_VPA_CON7, MT6323_VPA_CON9, + 0x3f, MT6323_VPA_CON10, MT6323_VPA_CON5), + MT6323_REG_FIXED("ldo_vtcxo", VTCXO, MT6323_ANALDO_CON1, 10, 2800000, + MT6323_ANALDO_CON1, 0x2), + MT6323_REG_FIXED("ldo_vcn28", VCN28, MT6323_ANALDO_CON19, 12, 2800000, + MT6323_ANALDO_CON20, 0x2), + MT6323_LDO("ldo_vcn33_bt", VCN33_BT, ldo_volt_table1, + MT6323_ANALDO_CON16, 7, MT6323_ANALDO_CON16, 0xC, + MT6323_ANALDO_CON21, 0x2), + MT6323_LDO("ldo_vcn33_wifi", VCN33_WIFI, ldo_volt_table1, + MT6323_ANALDO_CON17, 12, MT6323_ANALDO_CON16, 0xC, + MT6323_ANALDO_CON21, 0x2), + MT6323_REG_FIXED("ldo_va", VA, MT6323_ANALDO_CON2, 14, 2800000, + MT6323_ANALDO_CON2, 0x2), + MT6323_LDO("ldo_vcama", VCAMA, ldo_volt_table2, + MT6323_ANALDO_CON4, 15, MT6323_ANALDO_CON10, 0x60, -1, 0), + MT6323_REG_FIXED("ldo_vio28", VIO28, MT6323_DIGLDO_CON0, 14, 2800000, + MT6323_DIGLDO_CON0, 0x2), + MT6323_REG_FIXED("ldo_vusb", VUSB, MT6323_DIGLDO_CON2, 14, 3300000, + MT6323_DIGLDO_CON2, 0x2), + MT6323_LDO("ldo_vmc", VMC, ldo_volt_table3, + MT6323_DIGLDO_CON3, 12, MT6323_DIGLDO_CON24, 0x10, + MT6323_DIGLDO_CON3, 0x2), + MT6323_LDO("ldo_vmch", VMCH, ldo_volt_table4, + MT6323_DIGLDO_CON5, 14, MT6323_DIGLDO_CON26, 0x80, + MT6323_DIGLDO_CON5, 0x2), + MT6323_LDO("ldo_vemc3v3", VEMC3V3, ldo_volt_table4, + MT6323_DIGLDO_CON6, 14, MT6323_DIGLDO_CON27, 0x80, + MT6323_DIGLDO_CON6, 0x2), + MT6323_LDO("ldo_vgp1", VGP1, ldo_volt_table5, + MT6323_DIGLDO_CON7, 15, MT6323_DIGLDO_CON28, 0xE0, + MT6323_DIGLDO_CON7, 0x2), + MT6323_LDO("ldo_vgp2", VGP2, ldo_volt_table6, + MT6323_DIGLDO_CON8, 15, MT6323_DIGLDO_CON29, 0xE0, + MT6323_DIGLDO_CON8, 0x2), + MT6323_LDO("ldo_vgp3", VGP3, ldo_volt_table7, + MT6323_DIGLDO_CON9, 15, MT6323_DIGLDO_CON30, 0x60, + MT6323_DIGLDO_CON9, 0x2), + MT6323_REG_FIXED("ldo_vcn18", VCN18, MT6323_DIGLDO_CON11, 14, 1800000, + MT6323_DIGLDO_CON11, 0x2), + MT6323_LDO("ldo_vsim1", VSIM1, ldo_volt_table8, + MT6323_DIGLDO_CON13, 15, MT6323_DIGLDO_CON34, 0x20, + MT6323_DIGLDO_CON13, 0x2), + MT6323_LDO("ldo_vsim2", VSIM2, ldo_volt_table8, + MT6323_DIGLDO_CON14, 15, MT6323_DIGLDO_CON35, 0x20, + MT6323_DIGLDO_CON14, 0x2), + MT6323_REG_FIXED("ldo_vrtc", VRTC, MT6323_DIGLDO_CON15, 8, 2800000, + -1, 0), + MT6323_LDO("ldo_vcamaf", VCAMAF, ldo_volt_table5, + MT6323_DIGLDO_CON31, 15, MT6323_DIGLDO_CON32, 0xE0, + MT6323_DIGLDO_CON31, 0x2), + MT6323_LDO("ldo_vibr", VIBR, ldo_volt_table5, + MT6323_DIGLDO_CON39, 15, MT6323_DIGLDO_CON40, 0xE0, + MT6323_DIGLDO_CON39, 0x2), + MT6323_REG_FIXED("ldo_vrf18", VRF18, MT6323_DIGLDO_CON45, 15, 1825000, + MT6323_DIGLDO_CON45, 0x2), + MT6323_LDO("ldo_vm", VM, ldo_volt_table9, + MT6323_DIGLDO_CON47, 14, MT6323_DIGLDO_CON48, 0x30, + MT6323_DIGLDO_CON47, 0x2), + MT6323_REG_FIXED("ldo_vio18", VIO18, MT6323_DIGLDO_CON49, 14, 1800000, + MT6323_DIGLDO_CON49, 0x2), + MT6323_LDO("ldo_vcamd", VCAMD, ldo_volt_table10, + MT6323_DIGLDO_CON51, 14, MT6323_DIGLDO_CON52, 0x60, + MT6323_DIGLDO_CON51, 0x2), + MT6323_REG_FIXED("ldo_vcamio", VCAMIO, MT6323_DIGLDO_CON53, 14, 1800000, + MT6323_DIGLDO_CON53, 0x2), +}; + +static int mt6323_set_buck_vosel_reg(struct platform_device *pdev) +{ + struct mt6397_chip *mt6323 = dev_get_drvdata(pdev->dev.parent); + int i; + u32 regval; + + for (i = 0; i < MT6323_MAX_REGULATOR; i++) { + if (mt6323_regulators[i].vselctrl_reg) { + if (regmap_read(mt6323->regmap, + mt6323_regulators[i].vselctrl_reg, + ®val) < 0) { + dev_err(&pdev->dev, + "Failed to read buck ctrl\n"); + return -EIO; + } + + if (regval & mt6323_regulators[i].vselctrl_mask) { + mt6323_regulators[i].desc.vsel_reg = + mt6323_regulators[i].vselon_reg; + } + } + } + + return 0; +} + +static int mt6323_regulator_probe(struct platform_device *pdev) +{ + struct mt6397_chip *mt6323 = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = {}; + struct regulator_dev *rdev; + int i; + u32 reg_value; + + /* Query buck controller to select activated voltage register part */ + if (mt6323_set_buck_vosel_reg(pdev)) + return -EIO; + + /* Read PMIC chip revision to update constraints and voltage table */ + if (regmap_read(mt6323->regmap, MT6323_CID, ®_value) < 0) { + dev_err(&pdev->dev, "Failed to read Chip ID\n"); + return -EIO; + } + dev_info(&pdev->dev, "Chip ID = 0x%x\n", reg_value); + + for (i = 0; i < MT6323_MAX_REGULATOR; i++) { + config.dev = &pdev->dev; + config.driver_data = &mt6323_regulators[i]; + config.regmap = mt6323->regmap; + rdev = devm_regulator_register(&pdev->dev, + &mt6323_regulators[i].desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", + mt6323_regulators[i].desc.name); + return PTR_ERR(rdev); + } + } + return 0; +} + +static const struct platform_device_id mt6323_platform_ids[] = { + {"mt6323-regulator", 0}, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, mt6323_platform_ids); + +static struct platform_driver mt6323_regulator_driver = { + .driver = { + .name = "mt6323-regulator", + }, + .probe = mt6323_regulator_probe, + .id_table = mt6323_platform_ids, +}; + +module_platform_driver(mt6323_regulator_driver); + +MODULE_AUTHOR("Chen Zhong <chen.zhong@mediatek.com>"); +MODULE_DESCRIPTION("Regulator Driver for MediaTek MT6323 PMIC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/mt6380-regulator.c b/drivers/regulator/mt6380-regulator.c new file mode 100644 index 000000000..127dd720c --- /dev/null +++ b/drivers/regulator/mt6380-regulator.c @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2017 MediaTek Inc. + * Author: Chenglin Xu <chenglin.xu@mediatek.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/mt6380-regulator.h> +#include <linux/regulator/of_regulator.h> + +/* PMIC Registers */ +#define MT6380_ALDO_CON_0 0x0000 +#define MT6380_BTLDO_CON_0 0x0004 +#define MT6380_COMP_CON_0 0x0008 +#define MT6380_CPUBUCK_CON_0 0x000C +#define MT6380_CPUBUCK_CON_1 0x0010 +#define MT6380_CPUBUCK_CON_2 0x0014 +#define MT6380_DDRLDO_CON_0 0x0018 +#define MT6380_MLDO_CON_0 0x001C +#define MT6380_PALDO_CON_0 0x0020 +#define MT6380_PHYLDO_CON_0 0x0024 +#define MT6380_SIDO_CON_0 0x0028 +#define MT6380_SIDO_CON_1 0x002C +#define MT6380_SIDO_CON_2 0x0030 +#define MT6380_SLDO_CON_0 0x0034 +#define MT6380_TLDO_CON_0 0x0038 +#define MT6380_STARTUP_CON_0 0x003C +#define MT6380_STARTUP_CON_1 0x0040 +#define MT6380_SMPS_TOP_CON_0 0x0044 +#define MT6380_SMPS_TOP_CON_1 0x0048 +#define MT6380_ANA_CTRL_0 0x0050 +#define MT6380_ANA_CTRL_1 0x0054 +#define MT6380_ANA_CTRL_2 0x0058 +#define MT6380_ANA_CTRL_3 0x005C +#define MT6380_ANA_CTRL_4 0x0060 +#define MT6380_SPK_CON9 0x0064 +#define MT6380_SPK_CON11 0x0068 +#define MT6380_SPK_CON12 0x006A +#define MT6380_CLK_CTRL 0x0070 +#define MT6380_PINMUX_CTRL 0x0074 +#define MT6380_IO_CTRL 0x0078 +#define MT6380_SLP_MODE_CTRL_0 0x007C +#define MT6380_SLP_MODE_CTRL_1 0x0080 +#define MT6380_SLP_MODE_CTRL_2 0x0084 +#define MT6380_SLP_MODE_CTRL_3 0x0088 +#define MT6380_SLP_MODE_CTRL_4 0x008C +#define MT6380_SLP_MODE_CTRL_5 0x0090 +#define MT6380_SLP_MODE_CTRL_6 0x0094 +#define MT6380_SLP_MODE_CTRL_7 0x0098 +#define MT6380_SLP_MODE_CTRL_8 0x009C +#define MT6380_FCAL_CTRL_0 0x00A0 +#define MT6380_FCAL_CTRL_1 0x00A4 +#define MT6380_LDO_CTRL_0 0x00A8 +#define MT6380_LDO_CTRL_1 0x00AC +#define MT6380_LDO_CTRL_2 0x00B0 +#define MT6380_LDO_CTRL_3 0x00B4 +#define MT6380_LDO_CTRL_4 0x00B8 +#define MT6380_DEBUG_CTRL_0 0x00BC +#define MT6380_EFU_CTRL_0 0x0200 +#define MT6380_EFU_CTRL_1 0x0201 +#define MT6380_EFU_CTRL_2 0x0202 +#define MT6380_EFU_CTRL_3 0x0203 +#define MT6380_EFU_CTRL_4 0x0204 +#define MT6380_EFU_CTRL_5 0x0205 +#define MT6380_EFU_CTRL_6 0x0206 +#define MT6380_EFU_CTRL_7 0x0207 +#define MT6380_EFU_CTRL_8 0x0208 + +#define MT6380_REGULATOR_MODE_AUTO 0 +#define MT6380_REGULATOR_MODE_FORCE_PWM 1 + +/* + * mt6380 regulators' information + * + * @desc: standard fields of regulator description + * @vselon_reg: Register sections for hardware control mode of bucks + * @modeset_reg: Register for controlling the buck/LDO control mode + * @modeset_mask: Mask for controlling the buck/LDO control mode + */ +struct mt6380_regulator_info { + struct regulator_desc desc; + u32 vselon_reg; + u32 modeset_reg; + u32 modeset_mask; +}; + +#define MT6380_BUCK(match, vreg, min, max, step, volt_ranges, enreg, \ + vosel, vosel_mask, enbit, voselon, _modeset_reg, \ + _modeset_mask) \ +[MT6380_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6380_volt_range_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6380_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .linear_ranges = volt_ranges, \ + .n_linear_ranges = ARRAY_SIZE(volt_ranges), \ + .vsel_reg = vosel, \ + .vsel_mask = vosel_mask, \ + .enable_reg = enreg, \ + .enable_mask = BIT(enbit), \ + }, \ + .vselon_reg = voselon, \ + .modeset_reg = _modeset_reg, \ + .modeset_mask = _modeset_mask, \ +} + +#define MT6380_LDO(match, vreg, ldo_volt_table, enreg, enbit, vosel, \ + vosel_mask, _modeset_reg, _modeset_mask) \ +[MT6380_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6380_volt_table_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6380_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(ldo_volt_table), \ + .volt_table = ldo_volt_table, \ + .vsel_reg = vosel, \ + .vsel_mask = vosel_mask, \ + .enable_reg = enreg, \ + .enable_mask = BIT(enbit), \ + }, \ + .modeset_reg = _modeset_reg, \ + .modeset_mask = _modeset_mask, \ +} + +#define MT6380_REG_FIXED(match, vreg, enreg, enbit, volt, \ + _modeset_reg, _modeset_mask) \ +[MT6380_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6380_volt_fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6380_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = 1, \ + .enable_reg = enreg, \ + .enable_mask = BIT(enbit), \ + .min_uV = volt, \ + }, \ + .modeset_reg = _modeset_reg, \ + .modeset_mask = _modeset_mask, \ +} + +static const struct regulator_linear_range buck_volt_range1[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 0xfe, 6250), +}; + +static const struct regulator_linear_range buck_volt_range2[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 0xfe, 6250), +}; + +static const struct regulator_linear_range buck_volt_range3[] = { + REGULATOR_LINEAR_RANGE(1200000, 0, 0x3c, 25000), +}; + +static const u32 ldo_volt_table1[] = { + 1400000, 1350000, 1300000, 1250000, 1200000, 1150000, 1100000, 1050000, +}; + +static const u32 ldo_volt_table2[] = { + 2200000, 3300000, +}; + +static const u32 ldo_volt_table3[] = { + 1240000, 1390000, 1540000, 1840000, +}; + +static const u32 ldo_volt_table4[] = { + 2200000, 3300000, +}; + +static int mt6380_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + int ret, val = 0; + struct mt6380_regulator_info *info = rdev_get_drvdata(rdev); + + switch (mode) { + case REGULATOR_MODE_NORMAL: + val = MT6380_REGULATOR_MODE_AUTO; + break; + case REGULATOR_MODE_FAST: + val = MT6380_REGULATOR_MODE_FORCE_PWM; + break; + default: + return -EINVAL; + } + + val <<= ffs(info->modeset_mask) - 1; + + ret = regmap_update_bits(rdev->regmap, info->modeset_reg, + info->modeset_mask, val); + + return ret; +} + +static unsigned int mt6380_regulator_get_mode(struct regulator_dev *rdev) +{ + unsigned int val; + unsigned int mode; + int ret; + struct mt6380_regulator_info *info = rdev_get_drvdata(rdev); + + ret = regmap_read(rdev->regmap, info->modeset_reg, &val); + if (ret < 0) + return ret; + + val &= info->modeset_mask; + val >>= ffs(info->modeset_mask) - 1; + + switch (val) { + case MT6380_REGULATOR_MODE_AUTO: + mode = REGULATOR_MODE_NORMAL; + break; + case MT6380_REGULATOR_MODE_FORCE_PWM: + mode = REGULATOR_MODE_FAST; + break; + default: + return -EINVAL; + } + + return mode; +} + +static const struct regulator_ops mt6380_volt_range_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_mode = mt6380_regulator_set_mode, + .get_mode = mt6380_regulator_get_mode, +}; + +static const struct regulator_ops mt6380_volt_table_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_mode = mt6380_regulator_set_mode, + .get_mode = mt6380_regulator_get_mode, +}; + +static const struct regulator_ops mt6380_volt_fixed_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_mode = mt6380_regulator_set_mode, + .get_mode = mt6380_regulator_get_mode, +}; + +/* The array is indexed by id(MT6380_ID_XXX) */ +static struct mt6380_regulator_info mt6380_regulators[] = { + MT6380_BUCK("buck-vcore1", VCPU, 600000, 1393750, 6250, + buck_volt_range1, MT6380_ANA_CTRL_3, MT6380_ANA_CTRL_1, + 0xfe, 3, MT6380_ANA_CTRL_1, + MT6380_CPUBUCK_CON_0, 0x8000000), + MT6380_BUCK("buck-vcore", VCORE, 600000, 1393750, 6250, + buck_volt_range2, MT6380_ANA_CTRL_3, MT6380_ANA_CTRL_2, + 0xfe, 2, MT6380_ANA_CTRL_2, MT6380_SIDO_CON_0, 0x1000000), + MT6380_BUCK("buck-vrf", VRF, 1200000, 1575000, 25000, + buck_volt_range3, MT6380_ANA_CTRL_3, MT6380_SIDO_CON_0, + 0x78, 1, MT6380_SIDO_CON_0, MT6380_SIDO_CON_0, 0x8000), + MT6380_LDO("ldo-vm", VMLDO, ldo_volt_table1, MT6380_LDO_CTRL_0, + 1, MT6380_MLDO_CON_0, 0xE000, MT6380_ANA_CTRL_1, 0x4000000), + MT6380_LDO("ldo-va", VALDO, ldo_volt_table2, MT6380_LDO_CTRL_0, + 2, MT6380_ALDO_CON_0, 0x400, MT6380_ALDO_CON_0, 0x20), + MT6380_REG_FIXED("ldo-vphy", VPHYLDO, MT6380_LDO_CTRL_0, 7, 1800000, + MT6380_PHYLDO_CON_0, 0x80), + MT6380_LDO("ldo-vddr", VDDRLDO, ldo_volt_table3, MT6380_LDO_CTRL_0, + 8, MT6380_DDRLDO_CON_0, 0x3000, MT6380_DDRLDO_CON_0, 0x80), + MT6380_LDO("ldo-vt", VTLDO, ldo_volt_table4, MT6380_LDO_CTRL_0, 3, + MT6380_TLDO_CON_0, 0x400, MT6380_TLDO_CON_0, 0x20), +}; + +static int mt6380_regulator_probe(struct platform_device *pdev) +{ + struct regmap *regmap = dev_get_regmap(pdev->dev.parent, NULL); + struct regulator_config config = {}; + struct regulator_dev *rdev; + int i; + + for (i = 0; i < MT6380_MAX_REGULATOR; i++) { + config.dev = &pdev->dev; + config.driver_data = &mt6380_regulators[i]; + config.regmap = regmap; + rdev = devm_regulator_register(&pdev->dev, + &mt6380_regulators[i].desc, + &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", + mt6380_regulators[i].desc.name); + return PTR_ERR(rdev); + } + } + return 0; +} + +static const struct platform_device_id mt6380_platform_ids[] = { + {"mt6380-regulator", 0}, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, mt6380_platform_ids); + +static const struct of_device_id mt6380_of_match[] = { + { .compatible = "mediatek,mt6380-regulator", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mt6380_of_match); + +static struct platform_driver mt6380_regulator_driver = { + .driver = { + .name = "mt6380-regulator", + .of_match_table = of_match_ptr(mt6380_of_match), + }, + .probe = mt6380_regulator_probe, + .id_table = mt6380_platform_ids, +}; + +module_platform_driver(mt6380_regulator_driver); + +MODULE_AUTHOR("Chenglin Xu <chenglin.xu@mediatek.com>"); +MODULE_DESCRIPTION("Regulator Driver for MediaTek MT6380 PMIC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/mt6397-regulator.c b/drivers/regulator/mt6397-regulator.c new file mode 100644 index 000000000..c6c6aa85e --- /dev/null +++ b/drivers/regulator/mt6397-regulator.c @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Flora Fu <flora.fu@mediatek.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/mt6397/core.h> +#include <linux/mfd/mt6397/registers.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/mt6397-regulator.h> +#include <linux/regulator/of_regulator.h> + +#define MT6397_BUCK_MODE_AUTO 0 +#define MT6397_BUCK_MODE_FORCE_PWM 1 + +/* + * MT6397 regulators' information + * + * @desc: standard fields of regulator description. + * @qi: Mask for query enable signal status of regulators + * @vselon_reg: Register sections for hardware control mode of bucks + * @vselctrl_reg: Register for controlling the buck control mode. + * @vselctrl_mask: Mask for query buck's voltage control mode. + */ +struct mt6397_regulator_info { + struct regulator_desc desc; + u32 qi; + u32 vselon_reg; + u32 vselctrl_reg; + u32 vselctrl_mask; + u32 modeset_reg; + u32 modeset_mask; + u32 modeset_shift; +}; + +#define MT6397_BUCK(match, vreg, min, max, step, volt_ranges, enreg, \ + vosel, vosel_mask, voselon, vosel_ctrl, _modeset_reg, \ + _modeset_shift) \ +[MT6397_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6397_volt_range_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6397_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = (max - min)/step + 1, \ + .linear_ranges = volt_ranges, \ + .n_linear_ranges = ARRAY_SIZE(volt_ranges), \ + .vsel_reg = vosel, \ + .vsel_mask = vosel_mask, \ + .enable_reg = enreg, \ + .enable_mask = BIT(0), \ + }, \ + .qi = BIT(13), \ + .vselon_reg = voselon, \ + .vselctrl_reg = vosel_ctrl, \ + .vselctrl_mask = BIT(1), \ + .modeset_reg = _modeset_reg, \ + .modeset_mask = BIT(_modeset_shift), \ + .modeset_shift = _modeset_shift \ +} + +#define MT6397_LDO(match, vreg, ldo_volt_table, enreg, enbit, vosel, \ + vosel_mask) \ +[MT6397_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6397_volt_table_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6397_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(ldo_volt_table), \ + .volt_table = ldo_volt_table, \ + .vsel_reg = vosel, \ + .vsel_mask = vosel_mask, \ + .enable_reg = enreg, \ + .enable_mask = BIT(enbit), \ + }, \ + .qi = BIT(15), \ +} + +#define MT6397_REG_FIXED(match, vreg, enreg, enbit, volt) \ +[MT6397_ID_##vreg] = { \ + .desc = { \ + .name = #vreg, \ + .of_match = of_match_ptr(match), \ + .ops = &mt6397_volt_fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = MT6397_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = 1, \ + .enable_reg = enreg, \ + .enable_mask = BIT(enbit), \ + .min_uV = volt, \ + }, \ + .qi = BIT(15), \ +} + +static const struct regulator_linear_range buck_volt_range1[] = { + REGULATOR_LINEAR_RANGE(700000, 0, 0x7f, 6250), +}; + +static const struct regulator_linear_range buck_volt_range2[] = { + REGULATOR_LINEAR_RANGE(800000, 0, 0x7f, 6250), +}; + +static const struct regulator_linear_range buck_volt_range3[] = { + REGULATOR_LINEAR_RANGE(1500000, 0, 0x1f, 20000), +}; + +static const u32 ldo_volt_table1[] = { + 1500000, 1800000, 2500000, 2800000, +}; + +static const u32 ldo_volt_table2[] = { + 1800000, 3300000, +}; + +static const u32 ldo_volt_table3[] = { + 3000000, 3300000, +}; + +static const u32 ldo_volt_table4[] = { + 1220000, 1300000, 1500000, 1800000, 2500000, 2800000, 3000000, 3300000, +}; + +static const u32 ldo_volt_table5[] = { + 1200000, 1300000, 1500000, 1800000, 2500000, 2800000, 3000000, 3300000, +}; + +static const u32 ldo_volt_table5_v2[] = { + 1200000, 1000000, 1500000, 1800000, 2500000, 2800000, 3000000, 3300000, +}; + +static const u32 ldo_volt_table6[] = { + 1200000, 1300000, 1500000, 1800000, 2500000, 2800000, 3000000, 2000000, +}; + +static const u32 ldo_volt_table7[] = { + 1300000, 1500000, 1800000, 2000000, 2500000, 2800000, 3000000, 3300000, +}; + +static int mt6397_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct mt6397_regulator_info *info = rdev_get_drvdata(rdev); + int ret, val; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = MT6397_BUCK_MODE_FORCE_PWM; + break; + case REGULATOR_MODE_NORMAL: + val = MT6397_BUCK_MODE_AUTO; + break; + default: + ret = -EINVAL; + goto err_mode; + } + + dev_dbg(&rdev->dev, "mt6397 buck set_mode %#x, %#x, %#x, %#x\n", + info->modeset_reg, info->modeset_mask, + info->modeset_shift, val); + + val <<= info->modeset_shift; + ret = regmap_update_bits(rdev->regmap, info->modeset_reg, + info->modeset_mask, val); +err_mode: + if (ret != 0) { + dev_err(&rdev->dev, + "Failed to set mt6397 buck mode: %d\n", ret); + return ret; + } + + return 0; +} + +static unsigned int mt6397_regulator_get_mode(struct regulator_dev *rdev) +{ + struct mt6397_regulator_info *info = rdev_get_drvdata(rdev); + int ret, regval; + + ret = regmap_read(rdev->regmap, info->modeset_reg, ®val); + if (ret != 0) { + dev_err(&rdev->dev, + "Failed to get mt6397 buck mode: %d\n", ret); + return ret; + } + + switch ((regval & info->modeset_mask) >> info->modeset_shift) { + case MT6397_BUCK_MODE_AUTO: + return REGULATOR_MODE_NORMAL; + case MT6397_BUCK_MODE_FORCE_PWM: + return REGULATOR_MODE_FAST; + default: + return -EINVAL; + } +} + +static int mt6397_get_status(struct regulator_dev *rdev) +{ + int ret; + u32 regval; + struct mt6397_regulator_info *info = rdev_get_drvdata(rdev); + + ret = regmap_read(rdev->regmap, info->desc.enable_reg, ®val); + if (ret != 0) { + dev_err(&rdev->dev, "Failed to get enable reg: %d\n", ret); + return ret; + } + + return (regval & info->qi) ? REGULATOR_STATUS_ON : REGULATOR_STATUS_OFF; +} + +static const struct regulator_ops mt6397_volt_range_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_status = mt6397_get_status, + .set_mode = mt6397_regulator_set_mode, + .get_mode = mt6397_regulator_get_mode, +}; + +static const struct regulator_ops mt6397_volt_table_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_status = mt6397_get_status, +}; + +static const struct regulator_ops mt6397_volt_fixed_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_status = mt6397_get_status, +}; + +/* The array is indexed by id(MT6397_ID_XXX) */ +static struct mt6397_regulator_info mt6397_regulators[] = { + MT6397_BUCK("buck_vpca15", VPCA15, 700000, 1493750, 6250, + buck_volt_range1, MT6397_VCA15_CON7, MT6397_VCA15_CON9, 0x7f, + MT6397_VCA15_CON10, MT6397_VCA15_CON5, MT6397_VCA15_CON2, 11), + MT6397_BUCK("buck_vpca7", VPCA7, 700000, 1493750, 6250, + buck_volt_range1, MT6397_VPCA7_CON7, MT6397_VPCA7_CON9, 0x7f, + MT6397_VPCA7_CON10, MT6397_VPCA7_CON5, MT6397_VPCA7_CON2, 8), + MT6397_BUCK("buck_vsramca15", VSRAMCA15, 700000, 1493750, 6250, + buck_volt_range1, MT6397_VSRMCA15_CON7, MT6397_VSRMCA15_CON9, + 0x7f, MT6397_VSRMCA15_CON10, MT6397_VSRMCA15_CON5, + MT6397_VSRMCA15_CON2, 8), + MT6397_BUCK("buck_vsramca7", VSRAMCA7, 700000, 1493750, 6250, + buck_volt_range1, MT6397_VSRMCA7_CON7, MT6397_VSRMCA7_CON9, + 0x7f, MT6397_VSRMCA7_CON10, MT6397_VSRMCA7_CON5, + MT6397_VSRMCA7_CON2, 8), + MT6397_BUCK("buck_vcore", VCORE, 700000, 1493750, 6250, + buck_volt_range1, MT6397_VCORE_CON7, MT6397_VCORE_CON9, 0x7f, + MT6397_VCORE_CON10, MT6397_VCORE_CON5, MT6397_VCORE_CON2, 8), + MT6397_BUCK("buck_vgpu", VGPU, 700000, 1493750, 6250, buck_volt_range1, + MT6397_VGPU_CON7, MT6397_VGPU_CON9, 0x7f, + MT6397_VGPU_CON10, MT6397_VGPU_CON5, MT6397_VGPU_CON2, 8), + MT6397_BUCK("buck_vdrm", VDRM, 800000, 1593750, 6250, buck_volt_range2, + MT6397_VDRM_CON7, MT6397_VDRM_CON9, 0x7f, + MT6397_VDRM_CON10, MT6397_VDRM_CON5, MT6397_VDRM_CON2, 8), + MT6397_BUCK("buck_vio18", VIO18, 1500000, 2120000, 20000, + buck_volt_range3, MT6397_VIO18_CON7, MT6397_VIO18_CON9, 0x1f, + MT6397_VIO18_CON10, MT6397_VIO18_CON5, MT6397_VIO18_CON2, 8), + MT6397_REG_FIXED("ldo_vtcxo", VTCXO, MT6397_ANALDO_CON0, 10, 2800000), + MT6397_REG_FIXED("ldo_va28", VA28, MT6397_ANALDO_CON1, 14, 2800000), + MT6397_LDO("ldo_vcama", VCAMA, ldo_volt_table1, + MT6397_ANALDO_CON2, 15, MT6397_ANALDO_CON6, 0xC0), + MT6397_REG_FIXED("ldo_vio28", VIO28, MT6397_DIGLDO_CON0, 14, 2800000), + MT6397_REG_FIXED("ldo_vusb", VUSB, MT6397_DIGLDO_CON1, 14, 3300000), + MT6397_LDO("ldo_vmc", VMC, ldo_volt_table2, + MT6397_DIGLDO_CON2, 12, MT6397_DIGLDO_CON29, 0x10), + MT6397_LDO("ldo_vmch", VMCH, ldo_volt_table3, + MT6397_DIGLDO_CON3, 14, MT6397_DIGLDO_CON17, 0x80), + MT6397_LDO("ldo_vemc3v3", VEMC3V3, ldo_volt_table3, + MT6397_DIGLDO_CON4, 14, MT6397_DIGLDO_CON18, 0x10), + MT6397_LDO("ldo_vgp1", VGP1, ldo_volt_table4, + MT6397_DIGLDO_CON5, 15, MT6397_DIGLDO_CON19, 0xE0), + MT6397_LDO("ldo_vgp2", VGP2, ldo_volt_table5, + MT6397_DIGLDO_CON6, 15, MT6397_DIGLDO_CON20, 0xE0), + MT6397_LDO("ldo_vgp3", VGP3, ldo_volt_table5, + MT6397_DIGLDO_CON7, 15, MT6397_DIGLDO_CON21, 0xE0), + MT6397_LDO("ldo_vgp4", VGP4, ldo_volt_table5, + MT6397_DIGLDO_CON8, 15, MT6397_DIGLDO_CON22, 0xE0), + MT6397_LDO("ldo_vgp5", VGP5, ldo_volt_table6, + MT6397_DIGLDO_CON9, 15, MT6397_DIGLDO_CON23, 0xE0), + MT6397_LDO("ldo_vgp6", VGP6, ldo_volt_table5, + MT6397_DIGLDO_CON10, 15, MT6397_DIGLDO_CON33, 0xE0), + MT6397_LDO("ldo_vibr", VIBR, ldo_volt_table7, + MT6397_DIGLDO_CON24, 15, MT6397_DIGLDO_CON25, 0xE00), +}; + +static int mt6397_set_buck_vosel_reg(struct platform_device *pdev) +{ + struct mt6397_chip *mt6397 = dev_get_drvdata(pdev->dev.parent); + int i; + u32 regval; + + for (i = 0; i < MT6397_MAX_REGULATOR; i++) { + if (mt6397_regulators[i].vselctrl_reg) { + if (regmap_read(mt6397->regmap, + mt6397_regulators[i].vselctrl_reg, + ®val) < 0) { + dev_err(&pdev->dev, + "Failed to read buck ctrl\n"); + return -EIO; + } + + if (regval & mt6397_regulators[i].vselctrl_mask) { + mt6397_regulators[i].desc.vsel_reg = + mt6397_regulators[i].vselon_reg; + } + } + } + + return 0; +} + +static int mt6397_regulator_probe(struct platform_device *pdev) +{ + struct mt6397_chip *mt6397 = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = {}; + struct regulator_dev *rdev; + int i; + u32 reg_value, version; + + /* Query buck controller to select activated voltage register part */ + if (mt6397_set_buck_vosel_reg(pdev)) + return -EIO; + + /* Read PMIC chip revision to update constraints and voltage table */ + if (regmap_read(mt6397->regmap, MT6397_CID, ®_value) < 0) { + dev_err(&pdev->dev, "Failed to read Chip ID\n"); + return -EIO; + } + dev_info(&pdev->dev, "Chip ID = 0x%x\n", reg_value); + + version = (reg_value & 0xFF); + switch (version) { + case MT6397_REGULATOR_ID91: + mt6397_regulators[MT6397_ID_VGP2].desc.volt_table = + ldo_volt_table5_v2; + break; + default: + break; + } + + for (i = 0; i < MT6397_MAX_REGULATOR; i++) { + config.dev = &pdev->dev; + config.driver_data = &mt6397_regulators[i]; + config.regmap = mt6397->regmap; + rdev = devm_regulator_register(&pdev->dev, + &mt6397_regulators[i].desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", + mt6397_regulators[i].desc.name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct platform_device_id mt6397_platform_ids[] = { + {"mt6397-regulator", 0}, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, mt6397_platform_ids); + +static const struct of_device_id mt6397_of_match[] = { + { .compatible = "mediatek,mt6397-regulator", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mt6397_of_match); + +static struct platform_driver mt6397_regulator_driver = { + .driver = { + .name = "mt6397-regulator", + .of_match_table = of_match_ptr(mt6397_of_match), + }, + .probe = mt6397_regulator_probe, + .id_table = mt6397_platform_ids, +}; + +module_platform_driver(mt6397_regulator_driver); + +MODULE_AUTHOR("Flora Fu <flora.fu@mediatek.com>"); +MODULE_DESCRIPTION("Regulator Driver for MediaTek MT6397 PMIC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c new file mode 100644 index 000000000..b255590ae --- /dev/null +++ b/drivers/regulator/of_regulator.c @@ -0,0 +1,586 @@ +/* + * OF helpers for regulator framework + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Rajendra Nayak <rnayak@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; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +#include "internal.h" + +static const char *const regulator_states[PM_SUSPEND_MAX + 1] = { + [PM_SUSPEND_MEM] = "regulator-state-mem", + [PM_SUSPEND_MAX] = "regulator-state-disk", +}; + +static void of_get_regulation_constraints(struct device_node *np, + struct regulator_init_data **init_data, + const struct regulator_desc *desc) +{ + struct regulation_constraints *constraints = &(*init_data)->constraints; + struct regulator_state *suspend_state; + struct device_node *suspend_np; + unsigned int mode; + int ret, i, len; + u32 pval; + + constraints->name = of_get_property(np, "regulator-name", NULL); + + if (!of_property_read_u32(np, "regulator-min-microvolt", &pval)) + constraints->min_uV = pval; + + if (!of_property_read_u32(np, "regulator-max-microvolt", &pval)) + constraints->max_uV = pval; + + /* Voltage change possible? */ + if (constraints->min_uV != constraints->max_uV) + constraints->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE; + + /* Do we have a voltage range, if so try to apply it? */ + if (constraints->min_uV && constraints->max_uV) + constraints->apply_uV = true; + + if (!of_property_read_u32(np, "regulator-microvolt-offset", &pval)) + constraints->uV_offset = pval; + if (!of_property_read_u32(np, "regulator-min-microamp", &pval)) + constraints->min_uA = pval; + if (!of_property_read_u32(np, "regulator-max-microamp", &pval)) + constraints->max_uA = pval; + + if (!of_property_read_u32(np, "regulator-input-current-limit-microamp", + &pval)) + constraints->ilim_uA = pval; + + /* Current change possible? */ + if (constraints->min_uA != constraints->max_uA) + constraints->valid_ops_mask |= REGULATOR_CHANGE_CURRENT; + + constraints->boot_on = of_property_read_bool(np, "regulator-boot-on"); + constraints->always_on = of_property_read_bool(np, "regulator-always-on"); + if (!constraints->always_on) /* status change should be possible. */ + constraints->valid_ops_mask |= REGULATOR_CHANGE_STATUS; + + constraints->pull_down = of_property_read_bool(np, "regulator-pull-down"); + + if (of_property_read_bool(np, "regulator-allow-bypass")) + constraints->valid_ops_mask |= REGULATOR_CHANGE_BYPASS; + + if (of_property_read_bool(np, "regulator-allow-set-load")) + constraints->valid_ops_mask |= REGULATOR_CHANGE_DRMS; + + ret = of_property_read_u32(np, "regulator-ramp-delay", &pval); + if (!ret) { + if (pval) + constraints->ramp_delay = pval; + else + constraints->ramp_disable = true; + } + + ret = of_property_read_u32(np, "regulator-settling-time-us", &pval); + if (!ret) + constraints->settling_time = pval; + + ret = of_property_read_u32(np, "regulator-settling-time-up-us", &pval); + if (!ret) + constraints->settling_time_up = pval; + if (constraints->settling_time_up && constraints->settling_time) { + pr_warn("%s: ambiguous configuration for settling time, ignoring 'regulator-settling-time-up-us'\n", + np->name); + constraints->settling_time_up = 0; + } + + ret = of_property_read_u32(np, "regulator-settling-time-down-us", + &pval); + if (!ret) + constraints->settling_time_down = pval; + if (constraints->settling_time_down && constraints->settling_time) { + pr_warn("%s: ambiguous configuration for settling time, ignoring 'regulator-settling-time-down-us'\n", + np->name); + constraints->settling_time_down = 0; + } + + ret = of_property_read_u32(np, "regulator-enable-ramp-delay", &pval); + if (!ret) + constraints->enable_time = pval; + + constraints->soft_start = of_property_read_bool(np, + "regulator-soft-start"); + ret = of_property_read_u32(np, "regulator-active-discharge", &pval); + if (!ret) { + constraints->active_discharge = + (pval) ? REGULATOR_ACTIVE_DISCHARGE_ENABLE : + REGULATOR_ACTIVE_DISCHARGE_DISABLE; + } + + if (!of_property_read_u32(np, "regulator-initial-mode", &pval)) { + if (desc && desc->of_map_mode) { + mode = desc->of_map_mode(pval); + if (mode == REGULATOR_MODE_INVALID) + pr_err("%s: invalid mode %u\n", np->name, pval); + else + constraints->initial_mode = mode; + } else { + pr_warn("%s: mapping for mode %d not defined\n", + np->name, pval); + } + } + + len = of_property_count_elems_of_size(np, "regulator-allowed-modes", + sizeof(u32)); + if (len > 0) { + if (desc && desc->of_map_mode) { + for (i = 0; i < len; i++) { + ret = of_property_read_u32_index(np, + "regulator-allowed-modes", i, &pval); + if (ret) { + pr_err("%s: couldn't read allowed modes index %d, ret=%d\n", + np->name, i, ret); + break; + } + mode = desc->of_map_mode(pval); + if (mode == REGULATOR_MODE_INVALID) + pr_err("%s: invalid regulator-allowed-modes element %u\n", + np->name, pval); + else + constraints->valid_modes_mask |= mode; + } + if (constraints->valid_modes_mask) + constraints->valid_ops_mask + |= REGULATOR_CHANGE_MODE; + } else { + pr_warn("%s: mode mapping not defined\n", np->name); + } + } + + if (!of_property_read_u32(np, "regulator-system-load", &pval)) + constraints->system_load = pval; + + if (!of_property_read_u32(np, "regulator-coupled-max-spread", + &pval)) + constraints->max_spread = pval; + + constraints->over_current_protection = of_property_read_bool(np, + "regulator-over-current-protection"); + + for (i = 0; i < ARRAY_SIZE(regulator_states); i++) { + switch (i) { + case PM_SUSPEND_MEM: + suspend_state = &constraints->state_mem; + break; + case PM_SUSPEND_MAX: + suspend_state = &constraints->state_disk; + break; + case PM_SUSPEND_ON: + case PM_SUSPEND_TO_IDLE: + case PM_SUSPEND_STANDBY: + default: + continue; + } + + suspend_np = of_get_child_by_name(np, regulator_states[i]); + if (!suspend_np || !suspend_state) + continue; + + if (!of_property_read_u32(suspend_np, "regulator-mode", + &pval)) { + if (desc && desc->of_map_mode) { + mode = desc->of_map_mode(pval); + if (mode == REGULATOR_MODE_INVALID) + pr_err("%s: invalid mode %u\n", + np->name, pval); + else + suspend_state->mode = mode; + } else { + pr_warn("%s: mapping for mode %d not defined\n", + np->name, pval); + } + } + + if (of_property_read_bool(suspend_np, + "regulator-on-in-suspend")) + suspend_state->enabled = ENABLE_IN_SUSPEND; + else if (of_property_read_bool(suspend_np, + "regulator-off-in-suspend")) + suspend_state->enabled = DISABLE_IN_SUSPEND; + + if (!of_property_read_u32(suspend_np, + "regulator-suspend-min-microvolt", &pval)) + suspend_state->min_uV = pval; + + if (!of_property_read_u32(suspend_np, + "regulator-suspend-max-microvolt", &pval)) + suspend_state->max_uV = pval; + + if (!of_property_read_u32(suspend_np, + "regulator-suspend-microvolt", &pval)) + suspend_state->uV = pval; + else /* otherwise use min_uV as default suspend voltage */ + suspend_state->uV = suspend_state->min_uV; + + if (of_property_read_bool(suspend_np, + "regulator-changeable-in-suspend")) + suspend_state->changeable = true; + + if (i == PM_SUSPEND_MEM) + constraints->initial_state = PM_SUSPEND_MEM; + + of_node_put(suspend_np); + suspend_state = NULL; + suspend_np = NULL; + } +} + +/** + * of_get_regulator_init_data - extract regulator_init_data structure info + * @dev: device requesting for regulator_init_data + * @node: regulator device node + * @desc: regulator description + * + * Populates regulator_init_data structure by extracting data from device + * tree node, returns a pointer to the populated struture or NULL if memory + * alloc fails. + */ +struct regulator_init_data *of_get_regulator_init_data(struct device *dev, + struct device_node *node, + const struct regulator_desc *desc) +{ + struct regulator_init_data *init_data; + + if (!node) + return NULL; + + init_data = devm_kzalloc(dev, sizeof(*init_data), GFP_KERNEL); + if (!init_data) + return NULL; /* Out of memory? */ + + of_get_regulation_constraints(node, &init_data, desc); + return init_data; +} +EXPORT_SYMBOL_GPL(of_get_regulator_init_data); + +struct devm_of_regulator_matches { + struct of_regulator_match *matches; + unsigned int num_matches; +}; + +static void devm_of_regulator_put_matches(struct device *dev, void *res) +{ + struct devm_of_regulator_matches *devm_matches = res; + int i; + + for (i = 0; i < devm_matches->num_matches; i++) + of_node_put(devm_matches->matches[i].of_node); +} + +/** + * of_regulator_match - extract multiple regulator init data from device tree. + * @dev: device requesting the data + * @node: parent device node of the regulators + * @matches: match table for the regulators + * @num_matches: number of entries in match table + * + * This function uses a match table specified by the regulator driver to + * parse regulator init data from the device tree. @node is expected to + * contain a set of child nodes, each providing the init data for one + * regulator. The data parsed from a child node will be matched to a regulator + * based on either the deprecated property regulator-compatible if present, + * or otherwise the child node's name. Note that the match table is modified + * in place and an additional of_node reference is taken for each matched + * regulator. + * + * Returns the number of matches found or a negative error code on failure. + */ +int of_regulator_match(struct device *dev, struct device_node *node, + struct of_regulator_match *matches, + unsigned int num_matches) +{ + unsigned int count = 0; + unsigned int i; + const char *name; + struct device_node *child; + struct devm_of_regulator_matches *devm_matches; + + if (!dev || !node) + return -EINVAL; + + devm_matches = devres_alloc(devm_of_regulator_put_matches, + sizeof(struct devm_of_regulator_matches), + GFP_KERNEL); + if (!devm_matches) + return -ENOMEM; + + devm_matches->matches = matches; + devm_matches->num_matches = num_matches; + + devres_add(dev, devm_matches); + + for (i = 0; i < num_matches; i++) { + struct of_regulator_match *match = &matches[i]; + match->init_data = NULL; + match->of_node = NULL; + } + + for_each_child_of_node(node, child) { + name = of_get_property(child, + "regulator-compatible", NULL); + if (!name) + name = child->name; + for (i = 0; i < num_matches; i++) { + struct of_regulator_match *match = &matches[i]; + if (match->of_node) + continue; + + if (strcmp(match->name, name)) + continue; + + match->init_data = + of_get_regulator_init_data(dev, child, + match->desc); + if (!match->init_data) { + dev_err(dev, + "failed to parse DT for regulator %s\n", + child->name); + of_node_put(child); + return -EINVAL; + } + match->of_node = of_node_get(child); + count++; + break; + } + } + + return count; +} +EXPORT_SYMBOL_GPL(of_regulator_match); + +struct regulator_init_data *regulator_of_get_init_data(struct device *dev, + const struct regulator_desc *desc, + struct regulator_config *config, + struct device_node **node) +{ + struct device_node *search, *child; + struct regulator_init_data *init_data = NULL; + const char *name; + + if (!dev->of_node || !desc->of_match) + return NULL; + + if (desc->regulators_node) + search = of_get_child_by_name(dev->of_node, + desc->regulators_node); + else + search = of_node_get(dev->of_node); + + if (!search) { + dev_dbg(dev, "Failed to find regulator container node '%s'\n", + desc->regulators_node); + return NULL; + } + + for_each_available_child_of_node(search, child) { + name = of_get_property(child, "regulator-compatible", NULL); + if (!name) + name = child->name; + + if (strcmp(desc->of_match, name)) + continue; + + init_data = of_get_regulator_init_data(dev, child, desc); + if (!init_data) { + dev_err(dev, + "failed to parse DT for regulator %s\n", + child->name); + break; + } + + if (desc->of_parse_cb) { + if (desc->of_parse_cb(child, desc, config)) { + dev_err(dev, + "driver callback failed to parse DT for regulator %s\n", + child->name); + init_data = NULL; + break; + } + } + + of_node_get(child); + *node = child; + break; + } + + of_node_put(search); + + return init_data; +} + +static int of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +struct regulator_dev *of_find_regulator_by_node(struct device_node *np) +{ + struct device *dev; + + dev = class_find_device(®ulator_class, NULL, np, of_node_match); + + return dev ? dev_to_rdev(dev) : NULL; +} + +/* + * Returns number of regulators coupled with rdev. + */ +int of_get_n_coupled(struct regulator_dev *rdev) +{ + struct device_node *node = rdev->dev.of_node; + int n_phandles; + + n_phandles = of_count_phandle_with_args(node, + "regulator-coupled-with", + NULL); + + return (n_phandles > 0) ? n_phandles : 0; +} + +/* Looks for "to_find" device_node in src's "regulator-coupled-with" property */ +static bool of_coupling_find_node(struct device_node *src, + struct device_node *to_find) +{ + int n_phandles, i; + bool found = false; + + n_phandles = of_count_phandle_with_args(src, + "regulator-coupled-with", + NULL); + + for (i = 0; i < n_phandles; i++) { + struct device_node *tmp = of_parse_phandle(src, + "regulator-coupled-with", i); + + if (!tmp) + break; + + /* found */ + if (tmp == to_find) + found = true; + + of_node_put(tmp); + + if (found) + break; + } + + return found; +} + +/** + * of_check_coupling_data - Parse rdev's coupling properties and check data + * consistency + * @rdev - pointer to regulator_dev whose data is checked + * + * Function checks if all the following conditions are met: + * - rdev's max_spread is greater than 0 + * - all coupled regulators have the same max_spread + * - all coupled regulators have the same number of regulator_dev phandles + * - all regulators are linked to each other + * + * Returns true if all conditions are met. + */ +bool of_check_coupling_data(struct regulator_dev *rdev) +{ + int max_spread = rdev->constraints->max_spread; + struct device_node *node = rdev->dev.of_node; + int n_phandles = of_get_n_coupled(rdev); + struct device_node *c_node; + int i; + bool ret = true; + + if (max_spread <= 0) { + dev_err(&rdev->dev, "max_spread value invalid\n"); + return false; + } + + /* iterate over rdev's phandles */ + for (i = 0; i < n_phandles; i++) { + int c_max_spread, c_n_phandles; + + c_node = of_parse_phandle(node, + "regulator-coupled-with", i); + + if (!c_node) + ret = false; + + c_n_phandles = of_count_phandle_with_args(c_node, + "regulator-coupled-with", + NULL); + + if (c_n_phandles != n_phandles) { + dev_err(&rdev->dev, "number of couped reg phandles mismatch\n"); + ret = false; + goto clean; + } + + if (of_property_read_u32(c_node, "regulator-coupled-max-spread", + &c_max_spread)) { + ret = false; + goto clean; + } + + if (c_max_spread != max_spread) { + dev_err(&rdev->dev, + "coupled regulators max_spread mismatch\n"); + ret = false; + goto clean; + } + + if (!of_coupling_find_node(c_node, node)) { + dev_err(&rdev->dev, "missing 2-way linking for coupled regulators\n"); + ret = false; + } + +clean: + of_node_put(c_node); + if (!ret) + break; + } + + return ret; +} + +/** + * of_parse_coupled regulator - Get regulator_dev pointer from rdev's property + * @rdev: Pointer to regulator_dev, whose DTS is used as a source to parse + * "regulator-coupled-with" property + * @index: Index in phandles array + * + * Returns the regulator_dev pointer parsed from DTS. If it has not been yet + * registered, returns NULL + */ +struct regulator_dev *of_parse_coupled_regulator(struct regulator_dev *rdev, + int index) +{ + struct device_node *node = rdev->dev.of_node; + struct device_node *c_node; + struct regulator_dev *c_rdev; + + c_node = of_parse_phandle(node, "regulator-coupled-with", index); + if (!c_node) + return NULL; + + c_rdev = of_find_regulator_by_node(c_node); + + of_node_put(c_node); + + return c_rdev; +} diff --git a/drivers/regulator/palmas-regulator.c b/drivers/regulator/palmas-regulator.c new file mode 100644 index 000000000..c2cc392a2 --- /dev/null +++ b/drivers/regulator/palmas-regulator.c @@ -0,0 +1,1707 @@ +/* + * Driver for Regulator part of Palmas PMIC Chips + * + * Copyright 2011-2013 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Ian Lartey <ian@slimlogic.co.uk> + * + * 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/slab.h> +#include <linux/regmap.h> +#include <linux/mfd/palmas.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/regulator/of_regulator.h> + +static const struct regulator_linear_range smps_low_ranges[] = { + REGULATOR_LINEAR_RANGE(0, 0x0, 0x0, 0), + REGULATOR_LINEAR_RANGE(500000, 0x1, 0x6, 0), + REGULATOR_LINEAR_RANGE(510000, 0x7, 0x79, 10000), + REGULATOR_LINEAR_RANGE(1650000, 0x7A, 0x7f, 0), +}; + +static const struct regulator_linear_range smps_high_ranges[] = { + REGULATOR_LINEAR_RANGE(0, 0x0, 0x0, 0), + REGULATOR_LINEAR_RANGE(1000000, 0x1, 0x6, 0), + REGULATOR_LINEAR_RANGE(1020000, 0x7, 0x79, 20000), + REGULATOR_LINEAR_RANGE(3300000, 0x7A, 0x7f, 0), +}; + +static struct palmas_regs_info palmas_generic_regs_info[] = { + { + .name = "SMPS12", + .sname = "smps1-in", + .vsel_addr = PALMAS_SMPS12_VOLTAGE, + .ctrl_addr = PALMAS_SMPS12_CTRL, + .tstep_addr = PALMAS_SMPS12_TSTEP, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS12, + }, + { + .name = "SMPS123", + .sname = "smps1-in", + .vsel_addr = PALMAS_SMPS12_VOLTAGE, + .ctrl_addr = PALMAS_SMPS12_CTRL, + .tstep_addr = PALMAS_SMPS12_TSTEP, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS12, + }, + { + .name = "SMPS3", + .sname = "smps3-in", + .vsel_addr = PALMAS_SMPS3_VOLTAGE, + .ctrl_addr = PALMAS_SMPS3_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS3, + }, + { + .name = "SMPS45", + .sname = "smps4-in", + .vsel_addr = PALMAS_SMPS45_VOLTAGE, + .ctrl_addr = PALMAS_SMPS45_CTRL, + .tstep_addr = PALMAS_SMPS45_TSTEP, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS45, + }, + { + .name = "SMPS457", + .sname = "smps4-in", + .vsel_addr = PALMAS_SMPS45_VOLTAGE, + .ctrl_addr = PALMAS_SMPS45_CTRL, + .tstep_addr = PALMAS_SMPS45_TSTEP, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS45, + }, + { + .name = "SMPS6", + .sname = "smps6-in", + .vsel_addr = PALMAS_SMPS6_VOLTAGE, + .ctrl_addr = PALMAS_SMPS6_CTRL, + .tstep_addr = PALMAS_SMPS6_TSTEP, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS6, + }, + { + .name = "SMPS7", + .sname = "smps7-in", + .vsel_addr = PALMAS_SMPS7_VOLTAGE, + .ctrl_addr = PALMAS_SMPS7_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS7, + }, + { + .name = "SMPS8", + .sname = "smps8-in", + .vsel_addr = PALMAS_SMPS8_VOLTAGE, + .ctrl_addr = PALMAS_SMPS8_CTRL, + .tstep_addr = PALMAS_SMPS8_TSTEP, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS8, + }, + { + .name = "SMPS9", + .sname = "smps9-in", + .vsel_addr = PALMAS_SMPS9_VOLTAGE, + .ctrl_addr = PALMAS_SMPS9_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS9, + }, + { + .name = "SMPS10_OUT2", + .sname = "smps10-in", + .ctrl_addr = PALMAS_SMPS10_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS10, + }, + { + .name = "SMPS10_OUT1", + .sname = "smps10-out2", + .ctrl_addr = PALMAS_SMPS10_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS10, + }, + { + .name = "LDO1", + .sname = "ldo1-in", + .vsel_addr = PALMAS_LDO1_VOLTAGE, + .ctrl_addr = PALMAS_LDO1_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO1, + }, + { + .name = "LDO2", + .sname = "ldo2-in", + .vsel_addr = PALMAS_LDO2_VOLTAGE, + .ctrl_addr = PALMAS_LDO2_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO2, + }, + { + .name = "LDO3", + .sname = "ldo3-in", + .vsel_addr = PALMAS_LDO3_VOLTAGE, + .ctrl_addr = PALMAS_LDO3_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO3, + }, + { + .name = "LDO4", + .sname = "ldo4-in", + .vsel_addr = PALMAS_LDO4_VOLTAGE, + .ctrl_addr = PALMAS_LDO4_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO4, + }, + { + .name = "LDO5", + .sname = "ldo5-in", + .vsel_addr = PALMAS_LDO5_VOLTAGE, + .ctrl_addr = PALMAS_LDO5_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO5, + }, + { + .name = "LDO6", + .sname = "ldo6-in", + .vsel_addr = PALMAS_LDO6_VOLTAGE, + .ctrl_addr = PALMAS_LDO6_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO6, + }, + { + .name = "LDO7", + .sname = "ldo7-in", + .vsel_addr = PALMAS_LDO7_VOLTAGE, + .ctrl_addr = PALMAS_LDO7_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO7, + }, + { + .name = "LDO8", + .sname = "ldo8-in", + .vsel_addr = PALMAS_LDO8_VOLTAGE, + .ctrl_addr = PALMAS_LDO8_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO8, + }, + { + .name = "LDO9", + .sname = "ldo9-in", + .vsel_addr = PALMAS_LDO9_VOLTAGE, + .ctrl_addr = PALMAS_LDO9_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO9, + }, + { + .name = "LDOLN", + .sname = "ldoln-in", + .vsel_addr = PALMAS_LDOLN_VOLTAGE, + .ctrl_addr = PALMAS_LDOLN_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDOLN, + }, + { + .name = "LDOUSB", + .sname = "ldousb-in", + .vsel_addr = PALMAS_LDOUSB_VOLTAGE, + .ctrl_addr = PALMAS_LDOUSB_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDOUSB, + }, + { + .name = "REGEN1", + .ctrl_addr = PALMAS_REGEN1_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_REGEN1, + }, + { + .name = "REGEN2", + .ctrl_addr = PALMAS_REGEN2_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_REGEN2, + }, + { + .name = "REGEN3", + .ctrl_addr = PALMAS_REGEN3_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_REGEN3, + }, + { + .name = "SYSEN1", + .ctrl_addr = PALMAS_SYSEN1_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SYSEN1, + }, + { + .name = "SYSEN2", + .ctrl_addr = PALMAS_SYSEN2_CTRL, + .sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SYSEN2, + }, +}; + +static struct palmas_regs_info tps65917_regs_info[] = { + { + .name = "SMPS1", + .sname = "smps1-in", + .vsel_addr = TPS65917_SMPS1_VOLTAGE, + .ctrl_addr = TPS65917_SMPS1_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_SMPS1, + }, + { + .name = "SMPS2", + .sname = "smps2-in", + .vsel_addr = TPS65917_SMPS2_VOLTAGE, + .ctrl_addr = TPS65917_SMPS2_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_SMPS2, + }, + { + .name = "SMPS3", + .sname = "smps3-in", + .vsel_addr = TPS65917_SMPS3_VOLTAGE, + .ctrl_addr = TPS65917_SMPS3_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_SMPS3, + }, + { + .name = "SMPS4", + .sname = "smps4-in", + .vsel_addr = TPS65917_SMPS4_VOLTAGE, + .ctrl_addr = TPS65917_SMPS4_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_SMPS4, + }, + { + .name = "SMPS5", + .sname = "smps5-in", + .vsel_addr = TPS65917_SMPS5_VOLTAGE, + .ctrl_addr = TPS65917_SMPS5_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_SMPS5, + }, + { + .name = "SMPS12", + .sname = "smps1-in", + .vsel_addr = TPS65917_SMPS1_VOLTAGE, + .ctrl_addr = TPS65917_SMPS1_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_SMPS12, + }, + { + .name = "LDO1", + .sname = "ldo1-in", + .vsel_addr = TPS65917_LDO1_VOLTAGE, + .ctrl_addr = TPS65917_LDO1_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_LDO1, + }, + { + .name = "LDO2", + .sname = "ldo2-in", + .vsel_addr = TPS65917_LDO2_VOLTAGE, + .ctrl_addr = TPS65917_LDO2_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_LDO2, + }, + { + .name = "LDO3", + .sname = "ldo3-in", + .vsel_addr = TPS65917_LDO3_VOLTAGE, + .ctrl_addr = TPS65917_LDO3_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_LDO3, + }, + { + .name = "LDO4", + .sname = "ldo4-in", + .vsel_addr = TPS65917_LDO4_VOLTAGE, + .ctrl_addr = TPS65917_LDO4_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_LDO4, + }, + { + .name = "LDO5", + .sname = "ldo5-in", + .vsel_addr = TPS65917_LDO5_VOLTAGE, + .ctrl_addr = TPS65917_LDO5_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_LDO5, + }, + { + .name = "REGEN1", + .ctrl_addr = TPS65917_REGEN1_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_REGEN1, + }, + { + .name = "REGEN2", + .ctrl_addr = TPS65917_REGEN2_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_REGEN2, + }, + { + .name = "REGEN3", + .ctrl_addr = TPS65917_REGEN3_CTRL, + .sleep_id = TPS65917_EXTERNAL_REQSTR_ID_REGEN3, + }, +}; + +#define EXTERNAL_REQUESTOR(_id, _offset, _pos) \ + [PALMAS_EXTERNAL_REQSTR_ID_##_id] = { \ + .id = PALMAS_EXTERNAL_REQSTR_ID_##_id, \ + .reg_offset = _offset, \ + .bit_pos = _pos, \ + } + +static struct palmas_sleep_requestor_info palma_sleep_req_info[] = { + EXTERNAL_REQUESTOR(REGEN1, 0, 0), + EXTERNAL_REQUESTOR(REGEN2, 0, 1), + EXTERNAL_REQUESTOR(SYSEN1, 0, 2), + EXTERNAL_REQUESTOR(SYSEN2, 0, 3), + EXTERNAL_REQUESTOR(CLK32KG, 0, 4), + EXTERNAL_REQUESTOR(CLK32KGAUDIO, 0, 5), + EXTERNAL_REQUESTOR(REGEN3, 0, 6), + EXTERNAL_REQUESTOR(SMPS12, 1, 0), + EXTERNAL_REQUESTOR(SMPS3, 1, 1), + EXTERNAL_REQUESTOR(SMPS45, 1, 2), + EXTERNAL_REQUESTOR(SMPS6, 1, 3), + EXTERNAL_REQUESTOR(SMPS7, 1, 4), + EXTERNAL_REQUESTOR(SMPS8, 1, 5), + EXTERNAL_REQUESTOR(SMPS9, 1, 6), + EXTERNAL_REQUESTOR(SMPS10, 1, 7), + EXTERNAL_REQUESTOR(LDO1, 2, 0), + EXTERNAL_REQUESTOR(LDO2, 2, 1), + EXTERNAL_REQUESTOR(LDO3, 2, 2), + EXTERNAL_REQUESTOR(LDO4, 2, 3), + EXTERNAL_REQUESTOR(LDO5, 2, 4), + EXTERNAL_REQUESTOR(LDO6, 2, 5), + EXTERNAL_REQUESTOR(LDO7, 2, 6), + EXTERNAL_REQUESTOR(LDO8, 2, 7), + EXTERNAL_REQUESTOR(LDO9, 3, 0), + EXTERNAL_REQUESTOR(LDOLN, 3, 1), + EXTERNAL_REQUESTOR(LDOUSB, 3, 2), +}; + +#define EXTERNAL_REQUESTOR_TPS65917(_id, _offset, _pos) \ + [TPS65917_EXTERNAL_REQSTR_ID_##_id] = { \ + .id = TPS65917_EXTERNAL_REQSTR_ID_##_id, \ + .reg_offset = _offset, \ + .bit_pos = _pos, \ + } + +static struct palmas_sleep_requestor_info tps65917_sleep_req_info[] = { + EXTERNAL_REQUESTOR_TPS65917(REGEN1, 0, 0), + EXTERNAL_REQUESTOR_TPS65917(REGEN2, 0, 1), + EXTERNAL_REQUESTOR_TPS65917(REGEN3, 0, 6), + EXTERNAL_REQUESTOR_TPS65917(SMPS1, 1, 0), + EXTERNAL_REQUESTOR_TPS65917(SMPS2, 1, 1), + EXTERNAL_REQUESTOR_TPS65917(SMPS3, 1, 2), + EXTERNAL_REQUESTOR_TPS65917(SMPS4, 1, 3), + EXTERNAL_REQUESTOR_TPS65917(SMPS5, 1, 4), + EXTERNAL_REQUESTOR_TPS65917(SMPS12, 1, 5), + EXTERNAL_REQUESTOR_TPS65917(LDO1, 2, 0), + EXTERNAL_REQUESTOR_TPS65917(LDO2, 2, 1), + EXTERNAL_REQUESTOR_TPS65917(LDO3, 2, 2), + EXTERNAL_REQUESTOR_TPS65917(LDO4, 2, 3), + EXTERNAL_REQUESTOR_TPS65917(LDO5, 2, 4), +}; + +static unsigned int palmas_smps_ramp_delay[4] = {0, 10000, 5000, 2500}; + +#define SMPS_CTRL_MODE_OFF 0x00 +#define SMPS_CTRL_MODE_ON 0x01 +#define SMPS_CTRL_MODE_ECO 0x02 +#define SMPS_CTRL_MODE_PWM 0x03 + +#define PALMAS_SMPS_NUM_VOLTAGES 122 +#define PALMAS_SMPS10_NUM_VOLTAGES 2 +#define PALMAS_LDO_NUM_VOLTAGES 50 + +#define SMPS10_VSEL (1<<3) +#define SMPS10_BOOST_EN (1<<2) +#define SMPS10_BYPASS_EN (1<<1) +#define SMPS10_SWITCH_EN (1<<0) + +#define REGULATOR_SLAVE 0 + +static int palmas_smps_read(struct palmas *palmas, unsigned int reg, + unsigned int *dest) +{ + unsigned int addr; + + addr = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, reg); + + return regmap_read(palmas->regmap[REGULATOR_SLAVE], addr, dest); +} + +static int palmas_smps_write(struct palmas *palmas, unsigned int reg, + unsigned int value) +{ + unsigned int addr; + + addr = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, reg); + + return regmap_write(palmas->regmap[REGULATOR_SLAVE], addr, value); +} + +static int palmas_ldo_read(struct palmas *palmas, unsigned int reg, + unsigned int *dest) +{ + unsigned int addr; + + addr = PALMAS_BASE_TO_REG(PALMAS_LDO_BASE, reg); + + return regmap_read(palmas->regmap[REGULATOR_SLAVE], addr, dest); +} + +static int palmas_ldo_write(struct palmas *palmas, unsigned int reg, + unsigned int value) +{ + unsigned int addr; + + addr = PALMAS_BASE_TO_REG(PALMAS_LDO_BASE, reg); + + return regmap_write(palmas->regmap[REGULATOR_SLAVE], addr, value); +} + +static int palmas_set_mode_smps(struct regulator_dev *dev, unsigned int mode) +{ + int id = rdev_get_id(dev); + int ret; + struct palmas_pmic *pmic = rdev_get_drvdata(dev); + struct palmas_pmic_driver_data *ddata = pmic->palmas->pmic_ddata; + struct palmas_regs_info *rinfo = &ddata->palmas_regs_info[id]; + unsigned int reg; + bool rail_enable = true; + + ret = palmas_smps_read(pmic->palmas, rinfo->ctrl_addr, ®); + if (ret) + return ret; + + reg &= ~PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK; + + if (reg == SMPS_CTRL_MODE_OFF) + rail_enable = false; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + reg |= SMPS_CTRL_MODE_ON; + break; + case REGULATOR_MODE_IDLE: + reg |= SMPS_CTRL_MODE_ECO; + break; + case REGULATOR_MODE_FAST: + reg |= SMPS_CTRL_MODE_PWM; + break; + default: + return -EINVAL; + } + + pmic->current_reg_mode[id] = reg & PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK; + if (rail_enable) + palmas_smps_write(pmic->palmas, rinfo->ctrl_addr, reg); + + /* Switch the enable value to ensure this is used for enable */ + pmic->desc[id].enable_val = pmic->current_reg_mode[id]; + + return 0; +} + +static unsigned int palmas_get_mode_smps(struct regulator_dev *dev) +{ + struct palmas_pmic *pmic = rdev_get_drvdata(dev); + int id = rdev_get_id(dev); + unsigned int reg; + + reg = pmic->current_reg_mode[id] & PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK; + + switch (reg) { + case SMPS_CTRL_MODE_ON: + return REGULATOR_MODE_NORMAL; + case SMPS_CTRL_MODE_ECO: + return REGULATOR_MODE_IDLE; + case SMPS_CTRL_MODE_PWM: + return REGULATOR_MODE_FAST; + } + + return 0; +} + +static int palmas_smps_set_ramp_delay(struct regulator_dev *rdev, + int ramp_delay) +{ + int id = rdev_get_id(rdev); + struct palmas_pmic *pmic = rdev_get_drvdata(rdev); + struct palmas_pmic_driver_data *ddata = pmic->palmas->pmic_ddata; + struct palmas_regs_info *rinfo = &ddata->palmas_regs_info[id]; + unsigned int reg = 0; + int ret; + + /* SMPS3 and SMPS7 do not have tstep_addr setting */ + switch (id) { + case PALMAS_REG_SMPS3: + case PALMAS_REG_SMPS7: + return 0; + } + + if (ramp_delay <= 0) + reg = 0; + else if (ramp_delay <= 2500) + reg = 3; + else if (ramp_delay <= 5000) + reg = 2; + else + reg = 1; + + ret = palmas_smps_write(pmic->palmas, rinfo->tstep_addr, reg); + if (ret < 0) { + dev_err(pmic->palmas->dev, "TSTEP write failed: %d\n", ret); + return ret; + } + + pmic->ramp_delay[id] = palmas_smps_ramp_delay[reg]; + return ret; +} + +static const struct regulator_ops palmas_ops_smps = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .set_mode = palmas_set_mode_smps, + .get_mode = palmas_get_mode_smps, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = palmas_smps_set_ramp_delay, +}; + +static const struct regulator_ops palmas_ops_ext_control_smps = { + .set_mode = palmas_set_mode_smps, + .get_mode = palmas_get_mode_smps, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = palmas_smps_set_ramp_delay, +}; + +static const struct regulator_ops palmas_ops_smps10 = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_bypass = regulator_set_bypass_regmap, + .get_bypass = regulator_get_bypass_regmap, +}; + +static const struct regulator_ops tps65917_ops_smps = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .set_mode = palmas_set_mode_smps, + .get_mode = palmas_get_mode_smps, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_time_sel = regulator_set_voltage_time_sel, +}; + +static const struct regulator_ops tps65917_ops_ext_control_smps = { + .set_mode = palmas_set_mode_smps, + .get_mode = palmas_get_mode_smps, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, +}; + +static int palmas_is_enabled_ldo(struct regulator_dev *dev) +{ + int id = rdev_get_id(dev); + struct palmas_pmic *pmic = rdev_get_drvdata(dev); + struct palmas_pmic_driver_data *ddata = pmic->palmas->pmic_ddata; + struct palmas_regs_info *rinfo = &ddata->palmas_regs_info[id]; + unsigned int reg; + + palmas_ldo_read(pmic->palmas, rinfo->ctrl_addr, ®); + + reg &= PALMAS_LDO1_CTRL_STATUS; + + return !!(reg); +} + +static const struct regulator_ops palmas_ops_ldo = { + .is_enabled = palmas_is_enabled_ldo, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, +}; + +static const struct regulator_ops palmas_ops_ldo9 = { + .is_enabled = palmas_is_enabled_ldo, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_bypass = regulator_set_bypass_regmap, + .get_bypass = regulator_get_bypass_regmap, +}; + +static const struct regulator_ops palmas_ops_ext_control_ldo = { + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, +}; + +static const struct regulator_ops palmas_ops_extreg = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static const struct regulator_ops palmas_ops_ext_control_extreg = { +}; + +static const struct regulator_ops tps65917_ops_ldo = { + .is_enabled = palmas_is_enabled_ldo, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_voltage_time_sel = regulator_set_voltage_time_sel, +}; + +static const struct regulator_ops tps65917_ops_ldo_1_2 = { + .is_enabled = palmas_is_enabled_ldo, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_bypass = regulator_set_bypass_regmap, + .get_bypass = regulator_get_bypass_regmap, +}; + +static int palmas_regulator_config_external(struct palmas *palmas, int id, + struct palmas_reg_init *reg_init) +{ + struct palmas_pmic_driver_data *ddata = palmas->pmic_ddata; + struct palmas_regs_info *rinfo = &ddata->palmas_regs_info[id]; + int ret; + + ret = palmas_ext_control_req_config(palmas, rinfo->sleep_id, + reg_init->roof_floor, true); + if (ret < 0) + dev_err(palmas->dev, + "Ext control config for regulator %d failed %d\n", + id, ret); + return ret; +} + +/* + * setup the hardware based sleep configuration of the SMPS/LDO regulators + * from the platform data. This is different to the software based control + * supported by the regulator framework as it is controlled by toggling + * pins on the PMIC such as PREQ, SYSEN, ... + */ +static int palmas_smps_init(struct palmas *palmas, int id, + struct palmas_reg_init *reg_init) +{ + unsigned int reg; + int ret; + struct palmas_pmic_driver_data *ddata = palmas->pmic_ddata; + struct palmas_regs_info *rinfo = &ddata->palmas_regs_info[id]; + unsigned int addr = rinfo->ctrl_addr; + + ret = palmas_smps_read(palmas, addr, ®); + if (ret) + return ret; + + switch (id) { + case PALMAS_REG_SMPS10_OUT1: + case PALMAS_REG_SMPS10_OUT2: + reg &= ~PALMAS_SMPS10_CTRL_MODE_SLEEP_MASK; + if (reg_init->mode_sleep) + reg |= reg_init->mode_sleep << + PALMAS_SMPS10_CTRL_MODE_SLEEP_SHIFT; + break; + default: + if (reg_init->warm_reset) + reg |= PALMAS_SMPS12_CTRL_WR_S; + else + reg &= ~PALMAS_SMPS12_CTRL_WR_S; + + if (reg_init->roof_floor) + reg |= PALMAS_SMPS12_CTRL_ROOF_FLOOR_EN; + else + reg &= ~PALMAS_SMPS12_CTRL_ROOF_FLOOR_EN; + + reg &= ~PALMAS_SMPS12_CTRL_MODE_SLEEP_MASK; + if (reg_init->mode_sleep) + reg |= reg_init->mode_sleep << + PALMAS_SMPS12_CTRL_MODE_SLEEP_SHIFT; + } + + ret = palmas_smps_write(palmas, addr, reg); + if (ret) + return ret; + + if (rinfo->vsel_addr && reg_init->vsel) { + + reg = reg_init->vsel; + + ret = palmas_smps_write(palmas, rinfo->vsel_addr, reg); + if (ret) + return ret; + } + + if (reg_init->roof_floor && (id != PALMAS_REG_SMPS10_OUT1) && + (id != PALMAS_REG_SMPS10_OUT2)) { + /* Enable externally controlled regulator */ + ret = palmas_smps_read(palmas, addr, ®); + if (ret < 0) + return ret; + + if (!(reg & PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK)) { + reg |= SMPS_CTRL_MODE_ON; + ret = palmas_smps_write(palmas, addr, reg); + if (ret < 0) + return ret; + } + return palmas_regulator_config_external(palmas, id, reg_init); + } + return 0; +} + +static int palmas_ldo_init(struct palmas *palmas, int id, + struct palmas_reg_init *reg_init) +{ + unsigned int reg; + unsigned int addr; + int ret; + struct palmas_pmic_driver_data *ddata = palmas->pmic_ddata; + struct palmas_regs_info *rinfo = &ddata->palmas_regs_info[id]; + + addr = rinfo->ctrl_addr; + + ret = palmas_ldo_read(palmas, addr, ®); + if (ret) + return ret; + + if (reg_init->warm_reset) + reg |= PALMAS_LDO1_CTRL_WR_S; + else + reg &= ~PALMAS_LDO1_CTRL_WR_S; + + if (reg_init->mode_sleep) + reg |= PALMAS_LDO1_CTRL_MODE_SLEEP; + else + reg &= ~PALMAS_LDO1_CTRL_MODE_SLEEP; + + ret = palmas_ldo_write(palmas, addr, reg); + if (ret) + return ret; + + if (reg_init->roof_floor) { + /* Enable externally controlled regulator */ + ret = palmas_update_bits(palmas, PALMAS_LDO_BASE, + addr, PALMAS_LDO1_CTRL_MODE_ACTIVE, + PALMAS_LDO1_CTRL_MODE_ACTIVE); + if (ret < 0) { + dev_err(palmas->dev, + "LDO Register 0x%02x update failed %d\n", + addr, ret); + return ret; + } + return palmas_regulator_config_external(palmas, id, reg_init); + } + return 0; +} + +static int palmas_extreg_init(struct palmas *palmas, int id, + struct palmas_reg_init *reg_init) +{ + unsigned int addr; + int ret; + unsigned int val = 0; + struct palmas_pmic_driver_data *ddata = palmas->pmic_ddata; + struct palmas_regs_info *rinfo = &ddata->palmas_regs_info[id]; + + addr = rinfo->ctrl_addr; + + if (reg_init->mode_sleep) + val = PALMAS_REGEN1_CTRL_MODE_SLEEP; + + ret = palmas_update_bits(palmas, PALMAS_RESOURCE_BASE, + addr, PALMAS_REGEN1_CTRL_MODE_SLEEP, val); + if (ret < 0) { + dev_err(palmas->dev, "Resource reg 0x%02x update failed %d\n", + addr, ret); + return ret; + } + + if (reg_init->roof_floor) { + /* Enable externally controlled regulator */ + ret = palmas_update_bits(palmas, PALMAS_RESOURCE_BASE, + addr, PALMAS_REGEN1_CTRL_MODE_ACTIVE, + PALMAS_REGEN1_CTRL_MODE_ACTIVE); + if (ret < 0) { + dev_err(palmas->dev, + "Resource Register 0x%02x update failed %d\n", + addr, ret); + return ret; + } + return palmas_regulator_config_external(palmas, id, reg_init); + } + return 0; +} + +static void palmas_enable_ldo8_track(struct palmas *palmas) +{ + unsigned int reg; + unsigned int addr; + int ret; + struct palmas_pmic_driver_data *ddata = palmas->pmic_ddata; + struct palmas_regs_info *rinfo; + + rinfo = &ddata->palmas_regs_info[PALMAS_REG_LDO8]; + addr = rinfo->ctrl_addr; + + ret = palmas_ldo_read(palmas, addr, ®); + if (ret) { + dev_err(palmas->dev, "Error in reading ldo8 control reg\n"); + return; + } + + reg |= PALMAS_LDO8_CTRL_LDO_TRACKING_EN; + ret = palmas_ldo_write(palmas, addr, reg); + if (ret < 0) { + dev_err(palmas->dev, "Error in enabling tracking mode\n"); + return; + } + /* + * When SMPS45 is set to off and LDO8 tracking is enabled, the LDO8 + * output is defined by the LDO8_VOLTAGE.VSEL register divided by two, + * and can be set from 0.45 to 1.65 V. + */ + addr = rinfo->vsel_addr; + ret = palmas_ldo_read(palmas, addr, ®); + if (ret) { + dev_err(palmas->dev, "Error in reading ldo8 voltage reg\n"); + return; + } + + reg = (reg << 1) & PALMAS_LDO8_VOLTAGE_VSEL_MASK; + ret = palmas_ldo_write(palmas, addr, reg); + if (ret < 0) + dev_err(palmas->dev, "Error in setting ldo8 voltage reg\n"); + + return; +} + +static int palmas_ldo_registration(struct palmas_pmic *pmic, + struct palmas_pmic_driver_data *ddata, + struct palmas_pmic_platform_data *pdata, + const char *pdev_name, + struct regulator_config config) +{ + int id, ret; + struct regulator_dev *rdev; + struct palmas_reg_init *reg_init; + struct palmas_regs_info *rinfo; + struct regulator_desc *desc; + + for (id = ddata->ldo_begin; id < ddata->max_reg; id++) { + if (pdata && pdata->reg_init[id]) + reg_init = pdata->reg_init[id]; + else + reg_init = NULL; + + rinfo = &ddata->palmas_regs_info[id]; + /* Miss out regulators which are not available due + * to alternate functions. + */ + + /* Register the regulators */ + desc = &pmic->desc[id]; + desc->name = rinfo->name; + desc->id = id; + desc->type = REGULATOR_VOLTAGE; + desc->owner = THIS_MODULE; + + if (id < PALMAS_REG_REGEN1) { + desc->n_voltages = PALMAS_LDO_NUM_VOLTAGES; + if (reg_init && reg_init->roof_floor) + desc->ops = &palmas_ops_ext_control_ldo; + else + desc->ops = &palmas_ops_ldo; + desc->min_uV = 900000; + desc->uV_step = 50000; + desc->linear_min_sel = 1; + desc->enable_time = 500; + desc->vsel_reg = PALMAS_BASE_TO_REG(PALMAS_LDO_BASE, + rinfo->vsel_addr); + desc->vsel_mask = PALMAS_LDO1_VOLTAGE_VSEL_MASK; + desc->enable_reg = PALMAS_BASE_TO_REG(PALMAS_LDO_BASE, + rinfo->ctrl_addr); + desc->enable_mask = PALMAS_LDO1_CTRL_MODE_ACTIVE; + + /* Check if LDO8 is in tracking mode or not */ + if (pdata && (id == PALMAS_REG_LDO8) && + pdata->enable_ldo8_tracking) { + palmas_enable_ldo8_track(pmic->palmas); + desc->min_uV = 450000; + desc->uV_step = 25000; + } + + /* LOD6 in vibrator mode will have enable time 2000us */ + if (pdata && pdata->ldo6_vibrator && + (id == PALMAS_REG_LDO6)) + desc->enable_time = 2000; + + if (id == PALMAS_REG_LDO9) { + desc->ops = &palmas_ops_ldo9; + desc->bypass_reg = desc->enable_reg; + desc->bypass_val_on = + PALMAS_LDO9_CTRL_LDO_BYPASS_EN; + desc->bypass_mask = + PALMAS_LDO9_CTRL_LDO_BYPASS_EN; + } + } else { + if (!ddata->has_regen3 && id == PALMAS_REG_REGEN3) + continue; + + desc->n_voltages = 1; + if (reg_init && reg_init->roof_floor) + desc->ops = &palmas_ops_ext_control_extreg; + else + desc->ops = &palmas_ops_extreg; + desc->enable_reg = + PALMAS_BASE_TO_REG(PALMAS_RESOURCE_BASE, + rinfo->ctrl_addr); + desc->enable_mask = PALMAS_REGEN1_CTRL_MODE_ACTIVE; + } + + if (pdata) + config.init_data = pdata->reg_data[id]; + else + config.init_data = NULL; + + desc->supply_name = rinfo->sname; + config.of_node = ddata->palmas_matches[id].of_node; + + rdev = devm_regulator_register(pmic->dev, desc, &config); + if (IS_ERR(rdev)) { + dev_err(pmic->dev, + "failed to register %s regulator\n", + pdev_name); + return PTR_ERR(rdev); + } + + /* Save regulator for cleanup */ + pmic->rdev[id] = rdev; + + /* Initialise sleep/init values from platform data */ + if (pdata) { + reg_init = pdata->reg_init[id]; + if (reg_init) { + if (id <= ddata->ldo_end) + ret = palmas_ldo_init(pmic->palmas, id, + reg_init); + else + ret = palmas_extreg_init(pmic->palmas, + id, reg_init); + if (ret) + return ret; + } + } + } + + return 0; +} + +static int tps65917_ldo_registration(struct palmas_pmic *pmic, + struct palmas_pmic_driver_data *ddata, + struct palmas_pmic_platform_data *pdata, + const char *pdev_name, + struct regulator_config config) +{ + int id, ret; + struct regulator_dev *rdev; + struct palmas_reg_init *reg_init; + struct palmas_regs_info *rinfo; + struct regulator_desc *desc; + + for (id = ddata->ldo_begin; id < ddata->max_reg; id++) { + if (pdata && pdata->reg_init[id]) + reg_init = pdata->reg_init[id]; + else + reg_init = NULL; + + /* Miss out regulators which are not available due + * to alternate functions. + */ + rinfo = &ddata->palmas_regs_info[id]; + + /* Register the regulators */ + desc = &pmic->desc[id]; + desc->name = rinfo->name; + desc->id = id; + desc->type = REGULATOR_VOLTAGE; + desc->owner = THIS_MODULE; + + if (id < TPS65917_REG_REGEN1) { + desc->n_voltages = PALMAS_LDO_NUM_VOLTAGES; + if (reg_init && reg_init->roof_floor) + desc->ops = &palmas_ops_ext_control_ldo; + else + desc->ops = &tps65917_ops_ldo; + desc->min_uV = 900000; + desc->uV_step = 50000; + desc->linear_min_sel = 1; + desc->enable_time = 500; + desc->vsel_reg = PALMAS_BASE_TO_REG(PALMAS_LDO_BASE, + rinfo->vsel_addr); + desc->vsel_mask = PALMAS_LDO1_VOLTAGE_VSEL_MASK; + desc->enable_reg = PALMAS_BASE_TO_REG(PALMAS_LDO_BASE, + rinfo->ctrl_addr); + desc->enable_mask = PALMAS_LDO1_CTRL_MODE_ACTIVE; + /* + * To be confirmed. Discussion on going with PMIC Team. + * It is of the order of ~60mV/uS. + */ + desc->ramp_delay = 2500; + if (id == TPS65917_REG_LDO1 || + id == TPS65917_REG_LDO2) { + desc->ops = &tps65917_ops_ldo_1_2; + desc->bypass_reg = desc->enable_reg; + desc->bypass_val_on = + TPS65917_LDO1_CTRL_BYPASS_EN; + desc->bypass_mask = + TPS65917_LDO1_CTRL_BYPASS_EN; + } + } else { + desc->n_voltages = 1; + if (reg_init && reg_init->roof_floor) + desc->ops = &palmas_ops_ext_control_extreg; + else + desc->ops = &palmas_ops_extreg; + desc->enable_reg = + PALMAS_BASE_TO_REG(PALMAS_RESOURCE_BASE, + rinfo->ctrl_addr); + desc->enable_mask = PALMAS_REGEN1_CTRL_MODE_ACTIVE; + } + + if (pdata) + config.init_data = pdata->reg_data[id]; + else + config.init_data = NULL; + + desc->supply_name = rinfo->sname; + config.of_node = ddata->palmas_matches[id].of_node; + + rdev = devm_regulator_register(pmic->dev, desc, &config); + if (IS_ERR(rdev)) { + dev_err(pmic->dev, + "failed to register %s regulator\n", + pdev_name); + return PTR_ERR(rdev); + } + + /* Save regulator for cleanup */ + pmic->rdev[id] = rdev; + + /* Initialise sleep/init values from platform data */ + if (pdata) { + reg_init = pdata->reg_init[id]; + if (reg_init) { + if (id < TPS65917_REG_REGEN1) + ret = palmas_ldo_init(pmic->palmas, + id, reg_init); + else + ret = palmas_extreg_init(pmic->palmas, + id, reg_init); + if (ret) + return ret; + } + } + } + + return 0; +} + +static int palmas_smps_registration(struct palmas_pmic *pmic, + struct palmas_pmic_driver_data *ddata, + struct palmas_pmic_platform_data *pdata, + const char *pdev_name, + struct regulator_config config) +{ + int id, ret; + unsigned int addr, reg; + struct regulator_dev *rdev; + struct palmas_reg_init *reg_init; + struct palmas_regs_info *rinfo; + struct regulator_desc *desc; + + for (id = ddata->smps_start; id <= ddata->smps_end; id++) { + bool ramp_delay_support = false; + + /* + * Miss out regulators which are not available due + * to slaving configurations. + */ + switch (id) { + case PALMAS_REG_SMPS12: + case PALMAS_REG_SMPS3: + if (pmic->smps123) + continue; + if (id == PALMAS_REG_SMPS12) + ramp_delay_support = true; + break; + case PALMAS_REG_SMPS123: + if (!pmic->smps123) + continue; + ramp_delay_support = true; + break; + case PALMAS_REG_SMPS45: + case PALMAS_REG_SMPS7: + if (pmic->smps457) + continue; + if (id == PALMAS_REG_SMPS45) + ramp_delay_support = true; + break; + case PALMAS_REG_SMPS457: + if (!pmic->smps457) + continue; + ramp_delay_support = true; + break; + case PALMAS_REG_SMPS10_OUT1: + case PALMAS_REG_SMPS10_OUT2: + if (!PALMAS_PMIC_HAS(pmic->palmas, SMPS10_BOOST)) + continue; + } + rinfo = &ddata->palmas_regs_info[id]; + desc = &pmic->desc[id]; + + if ((id == PALMAS_REG_SMPS6) || (id == PALMAS_REG_SMPS8)) + ramp_delay_support = true; + + if (ramp_delay_support) { + addr = rinfo->tstep_addr; + ret = palmas_smps_read(pmic->palmas, addr, ®); + if (ret < 0) { + dev_err(pmic->dev, + "reading TSTEP reg failed: %d\n", ret); + return ret; + } + desc->ramp_delay = palmas_smps_ramp_delay[reg & 0x3]; + pmic->ramp_delay[id] = desc->ramp_delay; + } + + /* Initialise sleep/init values from platform data */ + if (pdata && pdata->reg_init[id]) { + reg_init = pdata->reg_init[id]; + ret = palmas_smps_init(pmic->palmas, id, reg_init); + if (ret) + return ret; + } else { + reg_init = NULL; + } + + /* Register the regulators */ + desc->name = rinfo->name; + desc->id = id; + + switch (id) { + case PALMAS_REG_SMPS10_OUT1: + case PALMAS_REG_SMPS10_OUT2: + desc->n_voltages = PALMAS_SMPS10_NUM_VOLTAGES; + desc->ops = &palmas_ops_smps10; + desc->vsel_reg = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, + PALMAS_SMPS10_CTRL); + desc->vsel_mask = SMPS10_VSEL; + desc->enable_reg = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, + PALMAS_SMPS10_CTRL); + if (id == PALMAS_REG_SMPS10_OUT1) + desc->enable_mask = SMPS10_SWITCH_EN; + else + desc->enable_mask = SMPS10_BOOST_EN; + desc->bypass_reg = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, + PALMAS_SMPS10_CTRL); + desc->bypass_val_on = SMPS10_BYPASS_EN; + desc->bypass_mask = SMPS10_BYPASS_EN; + desc->min_uV = 3750000; + desc->uV_step = 1250000; + break; + default: + /* + * Read and store the RANGE bit for later use + * This must be done before regulator is probed, + * otherwise we error in probe with unsupportable + * ranges. Read the current smps mode for later use. + */ + addr = rinfo->vsel_addr; + desc->n_linear_ranges = 3; + + ret = palmas_smps_read(pmic->palmas, addr, ®); + if (ret) + return ret; + if (reg & PALMAS_SMPS12_VOLTAGE_RANGE) + pmic->range[id] = 1; + if (pmic->range[id]) + desc->linear_ranges = smps_high_ranges; + else + desc->linear_ranges = smps_low_ranges; + + if (reg_init && reg_init->roof_floor) + desc->ops = &palmas_ops_ext_control_smps; + else + desc->ops = &palmas_ops_smps; + desc->n_voltages = PALMAS_SMPS_NUM_VOLTAGES; + desc->vsel_reg = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, + rinfo->vsel_addr); + desc->vsel_mask = PALMAS_SMPS12_VOLTAGE_VSEL_MASK; + + /* Read the smps mode for later use. */ + addr = rinfo->ctrl_addr; + ret = palmas_smps_read(pmic->palmas, addr, ®); + if (ret) + return ret; + pmic->current_reg_mode[id] = reg & + PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK; + + desc->enable_reg = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, + rinfo->ctrl_addr); + desc->enable_mask = PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK; + /* set_mode overrides this value */ + desc->enable_val = SMPS_CTRL_MODE_ON; + } + + desc->type = REGULATOR_VOLTAGE; + desc->owner = THIS_MODULE; + + if (pdata) + config.init_data = pdata->reg_data[id]; + else + config.init_data = NULL; + + desc->supply_name = rinfo->sname; + config.of_node = ddata->palmas_matches[id].of_node; + + rdev = devm_regulator_register(pmic->dev, desc, &config); + if (IS_ERR(rdev)) { + dev_err(pmic->dev, + "failed to register %s regulator\n", + pdev_name); + return PTR_ERR(rdev); + } + + /* Save regulator for cleanup */ + pmic->rdev[id] = rdev; + } + + return 0; +} + +static int tps65917_smps_registration(struct palmas_pmic *pmic, + struct palmas_pmic_driver_data *ddata, + struct palmas_pmic_platform_data *pdata, + const char *pdev_name, + struct regulator_config config) +{ + int id, ret; + unsigned int addr, reg; + struct regulator_dev *rdev; + struct palmas_reg_init *reg_init; + struct palmas_regs_info *rinfo; + struct regulator_desc *desc; + + for (id = ddata->smps_start; id <= ddata->smps_end; id++) { + /* + * Miss out regulators which are not available due + * to slaving configurations. + */ + desc = &pmic->desc[id]; + desc->n_linear_ranges = 3; + if ((id == TPS65917_REG_SMPS2 || id == TPS65917_REG_SMPS1) && + pmic->smps12) + continue; + + /* Initialise sleep/init values from platform data */ + if (pdata && pdata->reg_init[id]) { + reg_init = pdata->reg_init[id]; + ret = palmas_smps_init(pmic->palmas, id, reg_init); + if (ret) + return ret; + } else { + reg_init = NULL; + } + rinfo = &ddata->palmas_regs_info[id]; + + /* Register the regulators */ + desc->name = rinfo->name; + desc->id = id; + + /* + * Read and store the RANGE bit for later use + * This must be done before regulator is probed, + * otherwise we error in probe with unsupportable + * ranges. Read the current smps mode for later use. + */ + addr = rinfo->vsel_addr; + + ret = palmas_smps_read(pmic->palmas, addr, ®); + if (ret) + return ret; + if (reg & TPS65917_SMPS1_VOLTAGE_RANGE) + pmic->range[id] = 1; + + if (pmic->range[id]) + desc->linear_ranges = smps_high_ranges; + else + desc->linear_ranges = smps_low_ranges; + + if (reg_init && reg_init->roof_floor) + desc->ops = &tps65917_ops_ext_control_smps; + else + desc->ops = &tps65917_ops_smps; + desc->n_voltages = PALMAS_SMPS_NUM_VOLTAGES; + desc->vsel_reg = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, + rinfo->vsel_addr); + desc->vsel_mask = PALMAS_SMPS12_VOLTAGE_VSEL_MASK; + desc->ramp_delay = 2500; + + /* Read the smps mode for later use. */ + addr = rinfo->ctrl_addr; + ret = palmas_smps_read(pmic->palmas, addr, ®); + if (ret) + return ret; + pmic->current_reg_mode[id] = reg & + PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK; + desc->enable_reg = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, + rinfo->ctrl_addr); + desc->enable_mask = PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK; + /* set_mode overrides this value */ + desc->enable_val = SMPS_CTRL_MODE_ON; + + desc->type = REGULATOR_VOLTAGE; + desc->owner = THIS_MODULE; + + if (pdata) + config.init_data = pdata->reg_data[id]; + else + config.init_data = NULL; + + desc->supply_name = rinfo->sname; + config.of_node = ddata->palmas_matches[id].of_node; + + rdev = devm_regulator_register(pmic->dev, desc, &config); + if (IS_ERR(rdev)) { + dev_err(pmic->dev, + "failed to register %s regulator\n", + pdev_name); + return PTR_ERR(rdev); + } + + /* Save regulator for cleanup */ + pmic->rdev[id] = rdev; + } + + return 0; +} + +static struct of_regulator_match palmas_matches[] = { + { .name = "smps12", }, + { .name = "smps123", }, + { .name = "smps3", }, + { .name = "smps45", }, + { .name = "smps457", }, + { .name = "smps6", }, + { .name = "smps7", }, + { .name = "smps8", }, + { .name = "smps9", }, + { .name = "smps10_out2", }, + { .name = "smps10_out1", }, + { .name = "ldo1", }, + { .name = "ldo2", }, + { .name = "ldo3", }, + { .name = "ldo4", }, + { .name = "ldo5", }, + { .name = "ldo6", }, + { .name = "ldo7", }, + { .name = "ldo8", }, + { .name = "ldo9", }, + { .name = "ldoln", }, + { .name = "ldousb", }, + { .name = "regen1", }, + { .name = "regen2", }, + { .name = "regen3", }, + { .name = "sysen1", }, + { .name = "sysen2", }, +}; + +static struct of_regulator_match tps65917_matches[] = { + { .name = "smps1", }, + { .name = "smps2", }, + { .name = "smps3", }, + { .name = "smps4", }, + { .name = "smps5", }, + { .name = "smps12",}, + { .name = "ldo1", }, + { .name = "ldo2", }, + { .name = "ldo3", }, + { .name = "ldo4", }, + { .name = "ldo5", }, + { .name = "regen1", }, + { .name = "regen2", }, + { .name = "regen3", }, + { .name = "sysen1", }, + { .name = "sysen2", }, +}; + +static struct palmas_pmic_driver_data palmas_ddata = { + .smps_start = PALMAS_REG_SMPS12, + .smps_end = PALMAS_REG_SMPS10_OUT1, + .ldo_begin = PALMAS_REG_LDO1, + .ldo_end = PALMAS_REG_LDOUSB, + .max_reg = PALMAS_NUM_REGS, + .has_regen3 = true, + .palmas_regs_info = palmas_generic_regs_info, + .palmas_matches = palmas_matches, + .sleep_req_info = palma_sleep_req_info, + .smps_register = palmas_smps_registration, + .ldo_register = palmas_ldo_registration, +}; + +static struct palmas_pmic_driver_data tps65917_ddata = { + .smps_start = TPS65917_REG_SMPS1, + .smps_end = TPS65917_REG_SMPS12, + .ldo_begin = TPS65917_REG_LDO1, + .ldo_end = TPS65917_REG_LDO5, + .max_reg = TPS65917_NUM_REGS, + .has_regen3 = true, + .palmas_regs_info = tps65917_regs_info, + .palmas_matches = tps65917_matches, + .sleep_req_info = tps65917_sleep_req_info, + .smps_register = tps65917_smps_registration, + .ldo_register = tps65917_ldo_registration, +}; + +static int palmas_dt_to_pdata(struct device *dev, + struct device_node *node, + struct palmas_pmic_platform_data *pdata, + struct palmas_pmic_driver_data *ddata) +{ + struct device_node *regulators; + u32 prop; + int idx, ret; + + regulators = of_get_child_by_name(node, "regulators"); + if (!regulators) { + dev_info(dev, "regulator node not found\n"); + return 0; + } + + ret = of_regulator_match(dev, regulators, ddata->palmas_matches, + ddata->max_reg); + of_node_put(regulators); + if (ret < 0) { + dev_err(dev, "Error parsing regulator init data: %d\n", ret); + return 0; + } + + for (idx = 0; idx < ddata->max_reg; idx++) { + struct of_regulator_match *match; + struct palmas_reg_init *rinit; + struct device_node *np; + + match = &ddata->palmas_matches[idx]; + np = match->of_node; + + if (!match->init_data || !np) + continue; + + rinit = devm_kzalloc(dev, sizeof(*rinit), GFP_KERNEL); + if (!rinit) + return -ENOMEM; + + pdata->reg_data[idx] = match->init_data; + pdata->reg_init[idx] = rinit; + + rinit->warm_reset = of_property_read_bool(np, "ti,warm-reset"); + ret = of_property_read_u32(np, "ti,roof-floor", &prop); + /* EINVAL: Property not found */ + if (ret != -EINVAL) { + int econtrol; + + /* use default value, when no value is specified */ + econtrol = PALMAS_EXT_CONTROL_NSLEEP; + if (!ret) { + switch (prop) { + case 1: + econtrol = PALMAS_EXT_CONTROL_ENABLE1; + break; + case 2: + econtrol = PALMAS_EXT_CONTROL_ENABLE2; + break; + case 3: + econtrol = PALMAS_EXT_CONTROL_NSLEEP; + break; + default: + WARN_ON(1); + dev_warn(dev, + "%s: Invalid roof-floor option: %u\n", + match->name, prop); + break; + } + } + rinit->roof_floor = econtrol; + } + + ret = of_property_read_u32(np, "ti,mode-sleep", &prop); + if (!ret) + rinit->mode_sleep = prop; + + ret = of_property_read_bool(np, "ti,smps-range"); + if (ret) + rinit->vsel = PALMAS_SMPS12_VOLTAGE_RANGE; + + if (idx == PALMAS_REG_LDO8) + pdata->enable_ldo8_tracking = of_property_read_bool( + np, "ti,enable-ldo8-tracking"); + } + + pdata->ldo6_vibrator = of_property_read_bool(node, "ti,ldo6-vibrator"); + + return 0; +} + +static const struct of_device_id of_palmas_match_tbl[] = { + { + .compatible = "ti,palmas-pmic", + .data = &palmas_ddata, + }, + { + .compatible = "ti,twl6035-pmic", + .data = &palmas_ddata, + }, + { + .compatible = "ti,twl6036-pmic", + .data = &palmas_ddata, + }, + { + .compatible = "ti,twl6037-pmic", + .data = &palmas_ddata, + }, + { + .compatible = "ti,tps65913-pmic", + .data = &palmas_ddata, + }, + { + .compatible = "ti,tps65914-pmic", + .data = &palmas_ddata, + }, + { + .compatible = "ti,tps80036-pmic", + .data = &palmas_ddata, + }, + { + .compatible = "ti,tps659038-pmic", + .data = &palmas_ddata, + }, + { + .compatible = "ti,tps65917-pmic", + .data = &tps65917_ddata, + }, + { /* end */ } +}; + +static int palmas_regulators_probe(struct platform_device *pdev) +{ + struct palmas *palmas = dev_get_drvdata(pdev->dev.parent); + struct palmas_pmic_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *node = pdev->dev.of_node; + struct palmas_pmic_driver_data *driver_data; + struct regulator_config config = { }; + struct palmas_pmic *pmic; + const char *pdev_name; + const struct of_device_id *match; + int ret = 0; + unsigned int reg; + + match = of_match_device(of_match_ptr(of_palmas_match_tbl), &pdev->dev); + + if (!match) + return -ENODATA; + + driver_data = (struct palmas_pmic_driver_data *)match->data; + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pmic = devm_kzalloc(&pdev->dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + if (of_device_is_compatible(node, "ti,tps659038-pmic")) { + palmas_generic_regs_info[PALMAS_REG_REGEN2].ctrl_addr = + TPS659038_REGEN2_CTRL; + palmas_ddata.has_regen3 = false; + } + + pmic->dev = &pdev->dev; + pmic->palmas = palmas; + palmas->pmic = pmic; + platform_set_drvdata(pdev, pmic); + pmic->palmas->pmic_ddata = driver_data; + + ret = palmas_dt_to_pdata(&pdev->dev, node, pdata, driver_data); + if (ret) + return ret; + + ret = palmas_smps_read(palmas, PALMAS_SMPS_CTRL, ®); + if (ret) + return ret; + + if (reg & PALMAS_SMPS_CTRL_SMPS12_SMPS123_EN) { + pmic->smps123 = 1; + pmic->smps12 = 1; + } + + if (reg & PALMAS_SMPS_CTRL_SMPS45_SMPS457_EN) + pmic->smps457 = 1; + + config.regmap = palmas->regmap[REGULATOR_SLAVE]; + config.dev = &pdev->dev; + config.driver_data = pmic; + pdev_name = pdev->name; + + ret = driver_data->smps_register(pmic, driver_data, pdata, pdev_name, + config); + if (ret) + return ret; + + ret = driver_data->ldo_register(pmic, driver_data, pdata, pdev_name, + config); + + return ret; +} + +static struct platform_driver palmas_driver = { + .driver = { + .name = "palmas-pmic", + .of_match_table = of_palmas_match_tbl, + }, + .probe = palmas_regulators_probe, +}; + +static int __init palmas_init(void) +{ + return platform_driver_register(&palmas_driver); +} +subsys_initcall(palmas_init); + +static void __exit palmas_exit(void) +{ + platform_driver_unregister(&palmas_driver); +} +module_exit(palmas_exit); + +MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("Palmas voltage regulator driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:palmas-pmic"); +MODULE_DEVICE_TABLE(of, of_palmas_match_tbl); diff --git a/drivers/regulator/pbias-regulator.c b/drivers/regulator/pbias-regulator.c new file mode 100644 index 000000000..92b41a6a4 --- /dev/null +++ b/drivers/regulator/pbias-regulator.c @@ -0,0 +1,262 @@ +/* + * pbias-regulator.c + * + * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Balaji T K <balajitk@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/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mfd/syscon.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> + +struct pbias_reg_info { + u32 enable; + u32 enable_mask; + u32 disable_val; + u32 vmode; + unsigned int enable_time; + char *name; + const unsigned int *pbias_volt_table; + int n_voltages; +}; + +struct pbias_regulator_data { + struct regulator_desc desc; + void __iomem *pbias_addr; + struct regulator_dev *dev; + struct regmap *syscon; + const struct pbias_reg_info *info; + int voltage; +}; + +struct pbias_of_data { + unsigned int offset; +}; + +static const unsigned int pbias_volt_table_3_0V[] = { + 1800000, + 3000000 +}; + +static const unsigned int pbias_volt_table_3_3V[] = { + 1800000, + 3300000 +}; + +static const struct regulator_ops pbias_regulator_voltage_ops = { + .list_voltage = regulator_list_voltage_table, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct pbias_reg_info pbias_mmc_omap2430 = { + .enable = BIT(1), + .enable_mask = BIT(1), + .vmode = BIT(0), + .disable_val = 0, + .enable_time = 100, + .pbias_volt_table = pbias_volt_table_3_0V, + .n_voltages = 2, + .name = "pbias_mmc_omap2430" +}; + +static const struct pbias_reg_info pbias_sim_omap3 = { + .enable = BIT(9), + .enable_mask = BIT(9), + .vmode = BIT(8), + .enable_time = 100, + .pbias_volt_table = pbias_volt_table_3_0V, + .n_voltages = 2, + .name = "pbias_sim_omap3" +}; + +static const struct pbias_reg_info pbias_mmc_omap4 = { + .enable = BIT(26) | BIT(22), + .enable_mask = BIT(26) | BIT(25) | BIT(22), + .disable_val = BIT(25), + .vmode = BIT(21), + .enable_time = 100, + .pbias_volt_table = pbias_volt_table_3_0V, + .n_voltages = 2, + .name = "pbias_mmc_omap4" +}; + +static const struct pbias_reg_info pbias_mmc_omap5 = { + .enable = BIT(27) | BIT(26), + .enable_mask = BIT(27) | BIT(25) | BIT(26), + .disable_val = BIT(25), + .vmode = BIT(21), + .enable_time = 100, + .pbias_volt_table = pbias_volt_table_3_3V, + .n_voltages = 2, + .name = "pbias_mmc_omap5" +}; + +static struct of_regulator_match pbias_matches[] = { + { .name = "pbias_mmc_omap2430", .driver_data = (void *)&pbias_mmc_omap2430}, + { .name = "pbias_sim_omap3", .driver_data = (void *)&pbias_sim_omap3}, + { .name = "pbias_mmc_omap4", .driver_data = (void *)&pbias_mmc_omap4}, + { .name = "pbias_mmc_omap5", .driver_data = (void *)&pbias_mmc_omap5}, +}; +#define PBIAS_NUM_REGS ARRAY_SIZE(pbias_matches) + +/* Offset from SCM general area (and syscon) base */ + +static const struct pbias_of_data pbias_of_data_omap2 = { + .offset = 0x230, +}; + +static const struct pbias_of_data pbias_of_data_omap3 = { + .offset = 0x2b0, +}; + +static const struct pbias_of_data pbias_of_data_omap4 = { + .offset = 0x60, +}; + +static const struct pbias_of_data pbias_of_data_omap5 = { + .offset = 0x60, +}; + +static const struct pbias_of_data pbias_of_data_dra7 = { + .offset = 0xe00, +}; + +static const struct of_device_id pbias_of_match[] = { + { .compatible = "ti,pbias-omap", }, + { .compatible = "ti,pbias-omap2", .data = &pbias_of_data_omap2, }, + { .compatible = "ti,pbias-omap3", .data = &pbias_of_data_omap3, }, + { .compatible = "ti,pbias-omap4", .data = &pbias_of_data_omap4, }, + { .compatible = "ti,pbias-omap5", .data = &pbias_of_data_omap5, }, + { .compatible = "ti,pbias-dra7", .data = &pbias_of_data_dra7, }, + {}, +}; +MODULE_DEVICE_TABLE(of, pbias_of_match); + +static int pbias_regulator_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct pbias_regulator_data *drvdata; + struct resource *res; + struct regulator_config cfg = { }; + struct regmap *syscon; + const struct pbias_reg_info *info; + int ret = 0; + int count, idx, data_idx = 0; + const struct of_device_id *match; + const struct pbias_of_data *data; + unsigned int offset; + + count = of_regulator_match(&pdev->dev, np, pbias_matches, + PBIAS_NUM_REGS); + if (count < 0) + return count; + + drvdata = devm_kcalloc(&pdev->dev, + count, sizeof(struct pbias_regulator_data), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + syscon = syscon_regmap_lookup_by_phandle(np, "syscon"); + if (IS_ERR(syscon)) + return PTR_ERR(syscon); + + match = of_match_device(of_match_ptr(pbias_of_match), &pdev->dev); + if (match && match->data) { + data = match->data; + offset = data->offset; + } else { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + offset = res->start; + dev_WARN(&pdev->dev, + "using legacy dt data for pbias offset\n"); + } + + cfg.regmap = syscon; + cfg.dev = &pdev->dev; + + for (idx = 0; idx < PBIAS_NUM_REGS && data_idx < count; idx++) { + if (!pbias_matches[idx].init_data || + !pbias_matches[idx].of_node) + continue; + + info = pbias_matches[idx].driver_data; + if (!info) + return -ENODEV; + + drvdata[data_idx].syscon = syscon; + drvdata[data_idx].info = info; + drvdata[data_idx].desc.name = info->name; + drvdata[data_idx].desc.owner = THIS_MODULE; + drvdata[data_idx].desc.type = REGULATOR_VOLTAGE; + drvdata[data_idx].desc.ops = &pbias_regulator_voltage_ops; + drvdata[data_idx].desc.volt_table = info->pbias_volt_table; + drvdata[data_idx].desc.n_voltages = info->n_voltages; + drvdata[data_idx].desc.enable_time = info->enable_time; + drvdata[data_idx].desc.vsel_reg = offset; + drvdata[data_idx].desc.vsel_mask = info->vmode; + drvdata[data_idx].desc.enable_reg = offset; + drvdata[data_idx].desc.enable_mask = info->enable_mask; + drvdata[data_idx].desc.enable_val = info->enable; + drvdata[data_idx].desc.disable_val = info->disable_val; + + cfg.init_data = pbias_matches[idx].init_data; + cfg.driver_data = &drvdata[data_idx]; + cfg.of_node = pbias_matches[idx].of_node; + + drvdata[data_idx].dev = devm_regulator_register(&pdev->dev, + &drvdata[data_idx].desc, &cfg); + if (IS_ERR(drvdata[data_idx].dev)) { + ret = PTR_ERR(drvdata[data_idx].dev); + dev_err(&pdev->dev, + "Failed to register regulator: %d\n", ret); + goto err_regulator; + } + data_idx++; + } + + platform_set_drvdata(pdev, drvdata); + +err_regulator: + return ret; +} + +static struct platform_driver pbias_regulator_driver = { + .probe = pbias_regulator_probe, + .driver = { + .name = "pbias-regulator", + .of_match_table = of_match_ptr(pbias_of_match), + }, +}; + +module_platform_driver(pbias_regulator_driver); + +MODULE_AUTHOR("Balaji T K <balajitk@ti.com>"); +MODULE_DESCRIPTION("pbias voltage regulator"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pbias-regulator"); diff --git a/drivers/regulator/pcap-regulator.c b/drivers/regulator/pcap-regulator.c new file mode 100644 index 000000000..79cb971a6 --- /dev/null +++ b/drivers/regulator/pcap-regulator.c @@ -0,0 +1,281 @@ +/* + * PCAP2 Regulator Driver + * + * Copyright (c) 2009 Daniel Ribeiro <drwyrm@gmail.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; 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/mfd/ezx-pcap.h> + +static const unsigned int V1_table[] = { + 2775000, 1275000, 1600000, 1725000, 1825000, 1925000, 2075000, 2275000, +}; + +static const unsigned int V2_table[] = { + 2500000, 2775000, +}; + +static const unsigned int V3_table[] = { + 1075000, 1275000, 1550000, 1725000, 1876000, 1950000, 2075000, 2275000, +}; + +static const unsigned int V4_table[] = { + 1275000, 1550000, 1725000, 1875000, 1950000, 2075000, 2275000, 2775000, +}; + +static const unsigned int V5_table[] = { + 1875000, 2275000, 2475000, 2775000, +}; + +static const unsigned int V6_table[] = { + 2475000, 2775000, +}; + +static const unsigned int V7_table[] = { + 1875000, 2775000, +}; + +#define V8_table V4_table + +static const unsigned int V9_table[] = { + 1575000, 1875000, 2475000, 2775000, +}; + +static const unsigned int V10_table[] = { + 5000000, +}; + +static const unsigned int VAUX1_table[] = { + 1875000, 2475000, 2775000, 3000000, +}; + +#define VAUX2_table VAUX1_table + +static const unsigned int VAUX3_table[] = { + 1200000, 1200000, 1200000, 1200000, 1400000, 1600000, 1800000, 2000000, + 2200000, 2400000, 2600000, 2800000, 3000000, 3200000, 3400000, 3600000, +}; + +static const unsigned int VAUX4_table[] = { + 1800000, 1800000, 3000000, 5000000, +}; + +static const unsigned int VSIM_table[] = { + 1875000, 3000000, +}; + +static const unsigned int VSIM2_table[] = { + 1875000, +}; + +static const unsigned int VVIB_table[] = { + 1300000, 1800000, 2000000, 3000000, +}; + +static const unsigned int SW1_table[] = { + 900000, 950000, 1000000, 1050000, 1100000, 1150000, 1200000, 1250000, + 1300000, 1350000, 1400000, 1450000, 1500000, 1600000, 1875000, 2250000, +}; + +#define SW2_table SW1_table + +static const unsigned int SW3_table[] = { + 4000000, 4500000, 5000000, 5500000, +}; + +struct pcap_regulator { + const u8 reg; + const u8 en; + const u8 index; + const u8 stby; + const u8 lowpwr; +}; + +#define NA 0xff + +#define VREG_INFO(_vreg, _reg, _en, _index, _stby, _lowpwr) \ + [_vreg] = { \ + .reg = _reg, \ + .en = _en, \ + .index = _index, \ + .stby = _stby, \ + .lowpwr = _lowpwr, \ + } + +static struct pcap_regulator vreg_table[] = { + VREG_INFO(V1, PCAP_REG_VREG1, 1, 2, 18, 0), + VREG_INFO(V2, PCAP_REG_VREG1, 5, 6, 19, 22), + VREG_INFO(V3, PCAP_REG_VREG1, 7, 8, 20, 23), + VREG_INFO(V4, PCAP_REG_VREG1, 11, 12, 21, 24), + /* V5 STBY and LOWPWR are on PCAP_REG_VREG2 */ + VREG_INFO(V5, PCAP_REG_VREG1, 15, 16, 12, 19), + + VREG_INFO(V6, PCAP_REG_VREG2, 1, 2, 14, 20), + VREG_INFO(V7, PCAP_REG_VREG2, 3, 4, 15, 21), + VREG_INFO(V8, PCAP_REG_VREG2, 5, 6, 16, 22), + VREG_INFO(V9, PCAP_REG_VREG2, 9, 10, 17, 23), + VREG_INFO(V10, PCAP_REG_VREG2, 10, NA, 18, 24), + + VREG_INFO(VAUX1, PCAP_REG_AUXVREG, 1, 2, 22, 23), + /* VAUX2 ... VSIM2 STBY and LOWPWR are on PCAP_REG_LOWPWR */ + VREG_INFO(VAUX2, PCAP_REG_AUXVREG, 4, 5, 0, 1), + VREG_INFO(VAUX3, PCAP_REG_AUXVREG, 7, 8, 2, 3), + VREG_INFO(VAUX4, PCAP_REG_AUXVREG, 12, 13, 4, 5), + VREG_INFO(VSIM, PCAP_REG_AUXVREG, 17, 18, NA, 6), + VREG_INFO(VSIM2, PCAP_REG_AUXVREG, 16, NA, NA, 7), + VREG_INFO(VVIB, PCAP_REG_AUXVREG, 19, 20, NA, NA), + + VREG_INFO(SW1, PCAP_REG_SWCTRL, 1, 2, NA, NA), + VREG_INFO(SW2, PCAP_REG_SWCTRL, 6, 7, NA, NA), + /* SW3 STBY is on PCAP_REG_AUXVREG */ + VREG_INFO(SW3, PCAP_REG_SWCTRL, 11, 12, 24, NA), + + /* SWxS used to control SWx voltage on standby */ +/* VREG_INFO(SW1S, PCAP_REG_LOWPWR, NA, 12, NA, NA), + VREG_INFO(SW2S, PCAP_REG_LOWPWR, NA, 20, NA, NA), */ +}; + +static int pcap_regulator_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + + /* the regulator doesn't support voltage switching */ + if (rdev->desc->n_voltages == 1) + return -EINVAL; + + return ezx_pcap_set_bits(pcap, vreg->reg, + (rdev->desc->n_voltages - 1) << vreg->index, + selector << vreg->index); +} + +static int pcap_regulator_get_voltage_sel(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + u32 tmp; + + if (rdev->desc->n_voltages == 1) + return 0; + + ezx_pcap_read(pcap, vreg->reg, &tmp); + tmp = ((tmp >> vreg->index) & (rdev->desc->n_voltages - 1)); + return tmp; +} + +static int pcap_regulator_enable(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + + if (vreg->en == NA) + return -EINVAL; + + return ezx_pcap_set_bits(pcap, vreg->reg, 1 << vreg->en, 1 << vreg->en); +} + +static int pcap_regulator_disable(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + + if (vreg->en == NA) + return -EINVAL; + + return ezx_pcap_set_bits(pcap, vreg->reg, 1 << vreg->en, 0); +} + +static int pcap_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)]; + void *pcap = rdev_get_drvdata(rdev); + u32 tmp; + + if (vreg->en == NA) + return -EINVAL; + + ezx_pcap_read(pcap, vreg->reg, &tmp); + return (tmp >> vreg->en) & 1; +} + +static const struct regulator_ops pcap_regulator_ops = { + .list_voltage = regulator_list_voltage_table, + .set_voltage_sel = pcap_regulator_set_voltage_sel, + .get_voltage_sel = pcap_regulator_get_voltage_sel, + .enable = pcap_regulator_enable, + .disable = pcap_regulator_disable, + .is_enabled = pcap_regulator_is_enabled, +}; + +#define VREG(_vreg) \ + [_vreg] = { \ + .name = #_vreg, \ + .id = _vreg, \ + .n_voltages = ARRAY_SIZE(_vreg##_table), \ + .volt_table = _vreg##_table, \ + .ops = &pcap_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +static const struct regulator_desc pcap_regulators[] = { + VREG(V1), VREG(V2), VREG(V3), VREG(V4), VREG(V5), VREG(V6), VREG(V7), + VREG(V8), VREG(V9), VREG(V10), VREG(VAUX1), VREG(VAUX2), VREG(VAUX3), + VREG(VAUX4), VREG(VSIM), VREG(VSIM2), VREG(VVIB), VREG(SW1), VREG(SW2), +}; + +static int pcap_regulator_probe(struct platform_device *pdev) +{ + struct regulator_dev *rdev; + void *pcap = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = { }; + + config.dev = &pdev->dev; + config.init_data = dev_get_platdata(&pdev->dev); + config.driver_data = pcap; + + rdev = devm_regulator_register(&pdev->dev, &pcap_regulators[pdev->id], + &config); + if (IS_ERR(rdev)) + return PTR_ERR(rdev); + + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static struct platform_driver pcap_regulator_driver = { + .driver = { + .name = "pcap-regulator", + }, + .probe = pcap_regulator_probe, +}; + +static int __init pcap_regulator_init(void) +{ + return platform_driver_register(&pcap_regulator_driver); +} + +static void __exit pcap_regulator_exit(void) +{ + platform_driver_unregister(&pcap_regulator_driver); +} + +subsys_initcall(pcap_regulator_init); +module_exit(pcap_regulator_exit); + +MODULE_AUTHOR("Daniel Ribeiro <drwyrm@gmail.com>"); +MODULE_DESCRIPTION("PCAP2 Regulator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/pcf50633-regulator.c b/drivers/regulator/pcf50633-regulator.c new file mode 100644 index 000000000..762e18447 --- /dev/null +++ b/drivers/regulator/pcf50633-regulator.c @@ -0,0 +1,128 @@ +/* NXP PCF50633 PMIC Driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Balaji Rao <balajirrao@openmoko.org> + * All rights reserved. + * + * Broken down from monstrous PCF50633 driver mainly by + * Harald Welte and Andy Green and Werner Almesberger + * + * 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/device.h> +#include <linux/err.h> +#include <linux/platform_device.h> + +#include <linux/mfd/pcf50633/core.h> +#include <linux/mfd/pcf50633/pmic.h> + +#define PCF50633_REGULATOR(_name, _id, _min_uV, _uV_step, _min_sel, _n) \ + { \ + .name = _name, \ + .id = PCF50633_REGULATOR_##_id, \ + .ops = &pcf50633_regulator_ops, \ + .n_voltages = _n, \ + .min_uV = _min_uV, \ + .uV_step = _uV_step, \ + .linear_min_sel = _min_sel, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .vsel_reg = PCF50633_REG_##_id##OUT, \ + .vsel_mask = 0xff, \ + .enable_reg = PCF50633_REG_##_id##OUT + 1, \ + .enable_mask = PCF50633_REGULATOR_ON, \ + } + +static const struct regulator_ops pcf50633_regulator_ops = { + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct regulator_desc regulators[] = { + [PCF50633_REGULATOR_AUTO] = + PCF50633_REGULATOR("auto", AUTO, 1800000, 25000, 0x2f, 128), + [PCF50633_REGULATOR_DOWN1] = + PCF50633_REGULATOR("down1", DOWN1, 625000, 25000, 0, 96), + [PCF50633_REGULATOR_DOWN2] = + PCF50633_REGULATOR("down2", DOWN2, 625000, 25000, 0, 96), + [PCF50633_REGULATOR_LDO1] = + PCF50633_REGULATOR("ldo1", LDO1, 900000, 100000, 0, 28), + [PCF50633_REGULATOR_LDO2] = + PCF50633_REGULATOR("ldo2", LDO2, 900000, 100000, 0, 28), + [PCF50633_REGULATOR_LDO3] = + PCF50633_REGULATOR("ldo3", LDO3, 900000, 100000, 0, 28), + [PCF50633_REGULATOR_LDO4] = + PCF50633_REGULATOR("ldo4", LDO4, 900000, 100000, 0, 28), + [PCF50633_REGULATOR_LDO5] = + PCF50633_REGULATOR("ldo5", LDO5, 900000, 100000, 0, 28), + [PCF50633_REGULATOR_LDO6] = + PCF50633_REGULATOR("ldo6", LDO6, 900000, 100000, 0, 28), + [PCF50633_REGULATOR_HCLDO] = + PCF50633_REGULATOR("hcldo", HCLDO, 900000, 100000, 0, 28), + [PCF50633_REGULATOR_MEMLDO] = + PCF50633_REGULATOR("memldo", MEMLDO, 900000, 100000, 0, 28), +}; + +static int pcf50633_regulator_probe(struct platform_device *pdev) +{ + struct regulator_dev *rdev; + struct pcf50633 *pcf; + struct regulator_config config = { }; + + /* Already set by core driver */ + pcf = dev_to_pcf50633(pdev->dev.parent); + + config.dev = &pdev->dev; + config.init_data = dev_get_platdata(&pdev->dev); + config.driver_data = pcf; + config.regmap = pcf->regmap; + + rdev = devm_regulator_register(&pdev->dev, ®ulators[pdev->id], + &config); + if (IS_ERR(rdev)) + return PTR_ERR(rdev); + + platform_set_drvdata(pdev, rdev); + + if (pcf->pdata->regulator_registered) + pcf->pdata->regulator_registered(pcf, pdev->id); + + return 0; +} + +static struct platform_driver pcf50633_regulator_driver = { + .driver = { + .name = "pcf50633-regulator", + }, + .probe = pcf50633_regulator_probe, +}; + +static int __init pcf50633_regulator_init(void) +{ + return platform_driver_register(&pcf50633_regulator_driver); +} +subsys_initcall(pcf50633_regulator_init); + +static void __exit pcf50633_regulator_exit(void) +{ + platform_driver_unregister(&pcf50633_regulator_driver); +} +module_exit(pcf50633_regulator_exit); + +MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>"); +MODULE_DESCRIPTION("PCF50633 regulator driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcf50633-regulator"); diff --git a/drivers/regulator/pfuze100-regulator.c b/drivers/regulator/pfuze100-regulator.c new file mode 100644 index 000000000..8b1940110 --- /dev/null +++ b/drivers/regulator/pfuze100-regulator.c @@ -0,0 +1,800 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved. + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/of_regulator.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/pfuze100.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/regmap.h> + +#define PFUZE_FLAG_DISABLE_SW BIT(1) + +#define PFUZE_NUMREGS 128 +#define PFUZE100_VOL_OFFSET 0 +#define PFUZE100_STANDBY_OFFSET 1 +#define PFUZE100_MODE_OFFSET 3 +#define PFUZE100_CONF_OFFSET 4 + +#define PFUZE100_DEVICEID 0x0 +#define PFUZE100_REVID 0x3 +#define PFUZE100_FABID 0x4 + +#define PFUZE100_COINVOL 0x1a +#define PFUZE100_SW1ABVOL 0x20 +#define PFUZE100_SW1CVOL 0x2e +#define PFUZE100_SW2VOL 0x35 +#define PFUZE100_SW3AVOL 0x3c +#define PFUZE100_SW3BVOL 0x43 +#define PFUZE100_SW4VOL 0x4a +#define PFUZE100_SWBSTCON1 0x66 +#define PFUZE100_VREFDDRCON 0x6a +#define PFUZE100_VSNVSVOL 0x6b +#define PFUZE100_VGEN1VOL 0x6c +#define PFUZE100_VGEN2VOL 0x6d +#define PFUZE100_VGEN3VOL 0x6e +#define PFUZE100_VGEN4VOL 0x6f +#define PFUZE100_VGEN5VOL 0x70 +#define PFUZE100_VGEN6VOL 0x71 + +enum chips { PFUZE100, PFUZE200, PFUZE3000 = 3, PFUZE3001 = 0x31, }; + +struct pfuze_regulator { + struct regulator_desc desc; + unsigned char stby_reg; + unsigned char stby_mask; + bool sw_reg; +}; + +struct pfuze_chip { + int chip_id; + int flags; + struct regmap *regmap; + struct device *dev; + struct pfuze_regulator regulator_descs[PFUZE100_MAX_REGULATOR]; + struct regulator_dev *regulators[PFUZE100_MAX_REGULATOR]; + struct pfuze_regulator *pfuze_regulators; +}; + +static const int pfuze100_swbst[] = { + 5000000, 5050000, 5100000, 5150000, +}; + +static const int pfuze100_vsnvs[] = { + 1000000, 1100000, 1200000, 1300000, 1500000, 1800000, 3000000, +}; + +static const int pfuze100_coin[] = { + 2500000, 2700000, 2800000, 2900000, 3000000, 3100000, 3200000, 3300000, +}; + +static const int pfuze3000_sw1a[] = { + 700000, 725000, 750000, 775000, 800000, 825000, 850000, 875000, + 900000, 925000, 950000, 975000, 1000000, 1025000, 1050000, 1075000, + 1100000, 1125000, 1150000, 1175000, 1200000, 1225000, 1250000, 1275000, + 1300000, 1325000, 1350000, 1375000, 1400000, 1425000, 1800000, 3300000, +}; + +static const int pfuze3000_sw2lo[] = { + 1500000, 1550000, 1600000, 1650000, 1700000, 1750000, 1800000, 1850000, +}; + +static const int pfuze3000_sw2hi[] = { + 2500000, 2800000, 2850000, 3000000, 3100000, 3150000, 3200000, 3300000, +}; + +static const struct i2c_device_id pfuze_device_id[] = { + {.name = "pfuze100", .driver_data = PFUZE100}, + {.name = "pfuze200", .driver_data = PFUZE200}, + {.name = "pfuze3000", .driver_data = PFUZE3000}, + {.name = "pfuze3001", .driver_data = PFUZE3001}, + { } +}; +MODULE_DEVICE_TABLE(i2c, pfuze_device_id); + +static const struct of_device_id pfuze_dt_ids[] = { + { .compatible = "fsl,pfuze100", .data = (void *)PFUZE100}, + { .compatible = "fsl,pfuze200", .data = (void *)PFUZE200}, + { .compatible = "fsl,pfuze3000", .data = (void *)PFUZE3000}, + { .compatible = "fsl,pfuze3001", .data = (void *)PFUZE3001}, + { } +}; +MODULE_DEVICE_TABLE(of, pfuze_dt_ids); + +static int pfuze100_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay) +{ + struct pfuze_chip *pfuze100 = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + bool reg_has_ramp_delay; + unsigned int ramp_bits; + int ret; + + switch (pfuze100->chip_id) { + case PFUZE3001: + /* no dynamic voltage scaling for PF3001 */ + reg_has_ramp_delay = false; + break; + case PFUZE3000: + reg_has_ramp_delay = (id < PFUZE3000_SWBST); + break; + case PFUZE200: + reg_has_ramp_delay = (id < PFUZE200_SWBST); + break; + case PFUZE100: + default: + reg_has_ramp_delay = (id < PFUZE100_SWBST); + break; + } + + if (reg_has_ramp_delay) { + ramp_delay = 12500 / ramp_delay; + ramp_bits = (ramp_delay >> 1) - (ramp_delay >> 3); + ret = regmap_update_bits(pfuze100->regmap, + rdev->desc->vsel_reg + 4, + 0xc0, ramp_bits << 6); + if (ret < 0) + dev_err(pfuze100->dev, "ramp failed, err %d\n", ret); + } else { + ret = -EACCES; + } + + return ret; +} + +static const struct regulator_ops pfuze100_ldo_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + +static const struct regulator_ops pfuze100_fixed_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +static const struct regulator_ops pfuze100_sw_regulator_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = pfuze100_set_ramp_delay, +}; + +static const struct regulator_ops pfuze100_sw_disable_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = pfuze100_set_ramp_delay, +}; + +static const struct regulator_ops pfuze100_swb_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + +}; + +static const struct regulator_ops pfuze3000_sw_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = pfuze100_set_ramp_delay, + +}; + +#define PFUZE100_FIXED_REG(_chip, _name, base, voltage) \ + [_chip ## _ ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = 1, \ + .ops = &pfuze100_fixed_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _chip ## _ ## _name, \ + .owner = THIS_MODULE, \ + .min_uV = (voltage), \ + .enable_reg = (base), \ + .enable_mask = 0x10, \ + }, \ + } + +#define PFUZE100_SW_REG(_chip, _name, base, min, max, step) \ + [_chip ## _ ## _name] = { \ + .desc = { \ + .name = #_name,\ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .ops = &pfuze100_sw_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _chip ## _ ## _name, \ + .owner = THIS_MODULE, \ + .min_uV = (min), \ + .uV_step = (step), \ + .vsel_reg = (base) + PFUZE100_VOL_OFFSET, \ + .vsel_mask = 0x3f, \ + .enable_reg = (base) + PFUZE100_MODE_OFFSET, \ + .enable_mask = 0xf, \ + }, \ + .stby_reg = (base) + PFUZE100_STANDBY_OFFSET, \ + .stby_mask = 0x3f, \ + .sw_reg = true, \ + } + +#define PFUZE100_SWB_REG(_chip, _name, base, mask, voltages) \ + [_chip ## _ ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = ARRAY_SIZE(voltages), \ + .ops = &pfuze100_swb_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _chip ## _ ## _name, \ + .owner = THIS_MODULE, \ + .volt_table = voltages, \ + .vsel_reg = (base), \ + .vsel_mask = (mask), \ + .enable_reg = (base), \ + .enable_mask = 0x48, \ + }, \ + } + +#define PFUZE100_VGEN_REG(_chip, _name, base, min, max, step) \ + [_chip ## _ ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .ops = &pfuze100_ldo_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _chip ## _ ## _name, \ + .owner = THIS_MODULE, \ + .min_uV = (min), \ + .uV_step = (step), \ + .vsel_reg = (base), \ + .vsel_mask = 0xf, \ + .enable_reg = (base), \ + .enable_mask = 0x10, \ + }, \ + .stby_reg = (base), \ + .stby_mask = 0x20, \ + } + +#define PFUZE100_COIN_REG(_chip, _name, base, mask, voltages) \ + [_chip ## _ ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = ARRAY_SIZE(voltages), \ + .ops = &pfuze100_swb_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _chip ## _ ## _name, \ + .owner = THIS_MODULE, \ + .volt_table = voltages, \ + .vsel_reg = (base), \ + .vsel_mask = (mask), \ + .enable_reg = (base), \ + .enable_mask = 0x8, \ + }, \ + } + +#define PFUZE3000_VCC_REG(_chip, _name, base, min, max, step) { \ + .desc = { \ + .name = #_name, \ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .ops = &pfuze100_ldo_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _chip ## _ ## _name, \ + .owner = THIS_MODULE, \ + .min_uV = (min), \ + .uV_step = (step), \ + .vsel_reg = (base), \ + .vsel_mask = 0x3, \ + .enable_reg = (base), \ + .enable_mask = 0x10, \ + }, \ + .stby_reg = (base), \ + .stby_mask = 0x20, \ +} + +/* No linar case for the some switches of PFUZE3000 */ +#define PFUZE3000_SW_REG(_chip, _name, base, mask, voltages) \ + [_chip ## _ ## _name] = { \ + .desc = { \ + .name = #_name, \ + .n_voltages = ARRAY_SIZE(voltages), \ + .ops = &pfuze3000_sw_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _chip ## _ ## _name, \ + .owner = THIS_MODULE, \ + .volt_table = voltages, \ + .vsel_reg = (base) + PFUZE100_VOL_OFFSET, \ + .vsel_mask = (mask), \ + .enable_reg = (base) + PFUZE100_MODE_OFFSET, \ + .enable_mask = 0xf, \ + .enable_val = 0x8, \ + .enable_time = 500, \ + }, \ + .stby_reg = (base) + PFUZE100_STANDBY_OFFSET, \ + .stby_mask = (mask), \ + .sw_reg = true, \ + } + +#define PFUZE3000_SW3_REG(_chip, _name, base, min, max, step) { \ + .desc = { \ + .name = #_name,\ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .ops = &pfuze100_sw_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _chip ## _ ## _name, \ + .owner = THIS_MODULE, \ + .min_uV = (min), \ + .uV_step = (step), \ + .vsel_reg = (base) + PFUZE100_VOL_OFFSET, \ + .vsel_mask = 0xf, \ + }, \ + .stby_reg = (base) + PFUZE100_STANDBY_OFFSET, \ + .stby_mask = 0xf, \ +} + +/* PFUZE100 */ +static struct pfuze_regulator pfuze100_regulators[] = { + PFUZE100_SW_REG(PFUZE100, SW1AB, PFUZE100_SW1ABVOL, 300000, 1875000, 25000), + PFUZE100_SW_REG(PFUZE100, SW1C, PFUZE100_SW1CVOL, 300000, 1875000, 25000), + PFUZE100_SW_REG(PFUZE100, SW2, PFUZE100_SW2VOL, 400000, 1975000, 25000), + PFUZE100_SW_REG(PFUZE100, SW3A, PFUZE100_SW3AVOL, 400000, 1975000, 25000), + PFUZE100_SW_REG(PFUZE100, SW3B, PFUZE100_SW3BVOL, 400000, 1975000, 25000), + PFUZE100_SW_REG(PFUZE100, SW4, PFUZE100_SW4VOL, 400000, 1975000, 25000), + PFUZE100_SWB_REG(PFUZE100, SWBST, PFUZE100_SWBSTCON1, 0x3 , pfuze100_swbst), + PFUZE100_SWB_REG(PFUZE100, VSNVS, PFUZE100_VSNVSVOL, 0x7, pfuze100_vsnvs), + PFUZE100_FIXED_REG(PFUZE100, VREFDDR, PFUZE100_VREFDDRCON, 750000), + PFUZE100_VGEN_REG(PFUZE100, VGEN1, PFUZE100_VGEN1VOL, 800000, 1550000, 50000), + PFUZE100_VGEN_REG(PFUZE100, VGEN2, PFUZE100_VGEN2VOL, 800000, 1550000, 50000), + PFUZE100_VGEN_REG(PFUZE100, VGEN3, PFUZE100_VGEN3VOL, 1800000, 3300000, 100000), + PFUZE100_VGEN_REG(PFUZE100, VGEN4, PFUZE100_VGEN4VOL, 1800000, 3300000, 100000), + PFUZE100_VGEN_REG(PFUZE100, VGEN5, PFUZE100_VGEN5VOL, 1800000, 3300000, 100000), + PFUZE100_VGEN_REG(PFUZE100, VGEN6, PFUZE100_VGEN6VOL, 1800000, 3300000, 100000), +}; + +static struct pfuze_regulator pfuze200_regulators[] = { + PFUZE100_SW_REG(PFUZE200, SW1AB, PFUZE100_SW1ABVOL, 300000, 1875000, 25000), + PFUZE100_SW_REG(PFUZE200, SW2, PFUZE100_SW2VOL, 400000, 1975000, 25000), + PFUZE100_SW_REG(PFUZE200, SW3A, PFUZE100_SW3AVOL, 400000, 1975000, 25000), + PFUZE100_SW_REG(PFUZE200, SW3B, PFUZE100_SW3BVOL, 400000, 1975000, 25000), + PFUZE100_SWB_REG(PFUZE200, SWBST, PFUZE100_SWBSTCON1, 0x3 , pfuze100_swbst), + PFUZE100_SWB_REG(PFUZE200, VSNVS, PFUZE100_VSNVSVOL, 0x7, pfuze100_vsnvs), + PFUZE100_FIXED_REG(PFUZE200, VREFDDR, PFUZE100_VREFDDRCON, 750000), + PFUZE100_VGEN_REG(PFUZE200, VGEN1, PFUZE100_VGEN1VOL, 800000, 1550000, 50000), + PFUZE100_VGEN_REG(PFUZE200, VGEN2, PFUZE100_VGEN2VOL, 800000, 1550000, 50000), + PFUZE100_VGEN_REG(PFUZE200, VGEN3, PFUZE100_VGEN3VOL, 1800000, 3300000, 100000), + PFUZE100_VGEN_REG(PFUZE200, VGEN4, PFUZE100_VGEN4VOL, 1800000, 3300000, 100000), + PFUZE100_VGEN_REG(PFUZE200, VGEN5, PFUZE100_VGEN5VOL, 1800000, 3300000, 100000), + PFUZE100_VGEN_REG(PFUZE200, VGEN6, PFUZE100_VGEN6VOL, 1800000, 3300000, 100000), + PFUZE100_COIN_REG(PFUZE200, COIN, PFUZE100_COINVOL, 0x7, pfuze100_coin), +}; + +static struct pfuze_regulator pfuze3000_regulators[] = { + PFUZE3000_SW_REG(PFUZE3000, SW1A, PFUZE100_SW1ABVOL, 0x1f, pfuze3000_sw1a), + PFUZE100_SW_REG(PFUZE3000, SW1B, PFUZE100_SW1CVOL, 700000, 1475000, 25000), + PFUZE3000_SW_REG(PFUZE3000, SW2, PFUZE100_SW2VOL, 0x7, pfuze3000_sw2lo), + PFUZE3000_SW3_REG(PFUZE3000, SW3, PFUZE100_SW3AVOL, 900000, 1650000, 50000), + PFUZE100_SWB_REG(PFUZE3000, SWBST, PFUZE100_SWBSTCON1, 0x3, pfuze100_swbst), + PFUZE100_SWB_REG(PFUZE3000, VSNVS, PFUZE100_VSNVSVOL, 0x7, pfuze100_vsnvs), + PFUZE100_FIXED_REG(PFUZE3000, VREFDDR, PFUZE100_VREFDDRCON, 750000), + PFUZE100_VGEN_REG(PFUZE3000, VLDO1, PFUZE100_VGEN1VOL, 1800000, 3300000, 100000), + PFUZE100_VGEN_REG(PFUZE3000, VLDO2, PFUZE100_VGEN2VOL, 800000, 1550000, 50000), + PFUZE3000_VCC_REG(PFUZE3000, VCCSD, PFUZE100_VGEN3VOL, 2850000, 3300000, 150000), + PFUZE3000_VCC_REG(PFUZE3000, V33, PFUZE100_VGEN4VOL, 2850000, 3300000, 150000), + PFUZE100_VGEN_REG(PFUZE3000, VLDO3, PFUZE100_VGEN5VOL, 1800000, 3300000, 100000), + PFUZE100_VGEN_REG(PFUZE3000, VLDO4, PFUZE100_VGEN6VOL, 1800000, 3300000, 100000), +}; + +static struct pfuze_regulator pfuze3001_regulators[] = { + PFUZE3000_SW_REG(PFUZE3001, SW1, PFUZE100_SW1ABVOL, 0x1f, pfuze3000_sw1a), + PFUZE3000_SW_REG(PFUZE3001, SW2, PFUZE100_SW2VOL, 0x7, pfuze3000_sw2lo), + PFUZE3000_SW3_REG(PFUZE3001, SW3, PFUZE100_SW3AVOL, 900000, 1650000, 50000), + PFUZE100_SWB_REG(PFUZE3001, VSNVS, PFUZE100_VSNVSVOL, 0x7, pfuze100_vsnvs), + PFUZE100_VGEN_REG(PFUZE3001, VLDO1, PFUZE100_VGEN1VOL, 1800000, 3300000, 100000), + PFUZE100_VGEN_REG(PFUZE3001, VLDO2, PFUZE100_VGEN2VOL, 800000, 1550000, 50000), + PFUZE3000_VCC_REG(PFUZE3001, VCCSD, PFUZE100_VGEN3VOL, 2850000, 3300000, 150000), + PFUZE3000_VCC_REG(PFUZE3001, V33, PFUZE100_VGEN4VOL, 2850000, 3300000, 150000), + PFUZE100_VGEN_REG(PFUZE3001, VLDO3, PFUZE100_VGEN5VOL, 1800000, 3300000, 100000), + PFUZE100_VGEN_REG(PFUZE3001, VLDO4, PFUZE100_VGEN6VOL, 1800000, 3300000, 100000), +}; + +#ifdef CONFIG_OF +/* PFUZE100 */ +static struct of_regulator_match pfuze100_matches[] = { + { .name = "sw1ab", }, + { .name = "sw1c", }, + { .name = "sw2", }, + { .name = "sw3a", }, + { .name = "sw3b", }, + { .name = "sw4", }, + { .name = "swbst", }, + { .name = "vsnvs", }, + { .name = "vrefddr", }, + { .name = "vgen1", }, + { .name = "vgen2", }, + { .name = "vgen3", }, + { .name = "vgen4", }, + { .name = "vgen5", }, + { .name = "vgen6", }, +}; + +/* PFUZE200 */ +static struct of_regulator_match pfuze200_matches[] = { + + { .name = "sw1ab", }, + { .name = "sw2", }, + { .name = "sw3a", }, + { .name = "sw3b", }, + { .name = "swbst", }, + { .name = "vsnvs", }, + { .name = "vrefddr", }, + { .name = "vgen1", }, + { .name = "vgen2", }, + { .name = "vgen3", }, + { .name = "vgen4", }, + { .name = "vgen5", }, + { .name = "vgen6", }, + { .name = "coin", }, +}; + +/* PFUZE3000 */ +static struct of_regulator_match pfuze3000_matches[] = { + + { .name = "sw1a", }, + { .name = "sw1b", }, + { .name = "sw2", }, + { .name = "sw3", }, + { .name = "swbst", }, + { .name = "vsnvs", }, + { .name = "vrefddr", }, + { .name = "vldo1", }, + { .name = "vldo2", }, + { .name = "vccsd", }, + { .name = "v33", }, + { .name = "vldo3", }, + { .name = "vldo4", }, +}; + +/* PFUZE3001 */ +static struct of_regulator_match pfuze3001_matches[] = { + + { .name = "sw1", }, + { .name = "sw2", }, + { .name = "sw3", }, + { .name = "vsnvs", }, + { .name = "vldo1", }, + { .name = "vldo2", }, + { .name = "vccsd", }, + { .name = "v33", }, + { .name = "vldo3", }, + { .name = "vldo4", }, +}; + +static struct of_regulator_match *pfuze_matches; + +static int pfuze_parse_regulators_dt(struct pfuze_chip *chip) +{ + struct device *dev = chip->dev; + struct device_node *np, *parent; + int ret; + + np = of_node_get(dev->of_node); + if (!np) + return -EINVAL; + + if (of_property_read_bool(np, "fsl,pfuze-support-disable-sw")) + chip->flags |= PFUZE_FLAG_DISABLE_SW; + + parent = of_get_child_by_name(np, "regulators"); + if (!parent) { + dev_err(dev, "regulators node not found\n"); + of_node_put(np); + return -EINVAL; + } + + switch (chip->chip_id) { + case PFUZE3001: + pfuze_matches = pfuze3001_matches; + ret = of_regulator_match(dev, parent, pfuze3001_matches, + ARRAY_SIZE(pfuze3001_matches)); + break; + case PFUZE3000: + pfuze_matches = pfuze3000_matches; + ret = of_regulator_match(dev, parent, pfuze3000_matches, + ARRAY_SIZE(pfuze3000_matches)); + break; + case PFUZE200: + pfuze_matches = pfuze200_matches; + ret = of_regulator_match(dev, parent, pfuze200_matches, + ARRAY_SIZE(pfuze200_matches)); + break; + + case PFUZE100: + default: + pfuze_matches = pfuze100_matches; + ret = of_regulator_match(dev, parent, pfuze100_matches, + ARRAY_SIZE(pfuze100_matches)); + break; + } + + of_node_put(parent); + of_node_put(np); + if (ret < 0) { + dev_err(dev, "Error parsing regulator init data: %d\n", + ret); + return ret; + } + + return 0; +} + +static inline struct regulator_init_data *match_init_data(int index) +{ + return pfuze_matches[index].init_data; +} + +static inline struct device_node *match_of_node(int index) +{ + return pfuze_matches[index].of_node; +} +#else +static int pfuze_parse_regulators_dt(struct pfuze_chip *chip) +{ + return 0; +} + +static inline struct regulator_init_data *match_init_data(int index) +{ + return NULL; +} + +static inline struct device_node *match_of_node(int index) +{ + return NULL; +} +#endif + +static int pfuze_identify(struct pfuze_chip *pfuze_chip) +{ + unsigned int value; + int ret; + + ret = regmap_read(pfuze_chip->regmap, PFUZE100_DEVICEID, &value); + if (ret) + return ret; + + if (((value & 0x0f) == 0x8) && (pfuze_chip->chip_id == PFUZE100)) { + /* + * Freescale misprogrammed 1-3% of parts prior to week 8 of 2013 + * as ID=8 in PFUZE100 + */ + dev_info(pfuze_chip->dev, "Assuming misprogrammed ID=0x8"); + } else if ((value & 0x0f) != pfuze_chip->chip_id && + (value & 0xf0) >> 4 != pfuze_chip->chip_id && + (value != pfuze_chip->chip_id)) { + /* device id NOT match with your setting */ + dev_warn(pfuze_chip->dev, "Illegal ID: %x\n", value); + return -ENODEV; + } + + ret = regmap_read(pfuze_chip->regmap, PFUZE100_REVID, &value); + if (ret) + return ret; + dev_info(pfuze_chip->dev, + "Full layer: %x, Metal layer: %x\n", + (value & 0xf0) >> 4, value & 0x0f); + + ret = regmap_read(pfuze_chip->regmap, PFUZE100_FABID, &value); + if (ret) + return ret; + dev_info(pfuze_chip->dev, "FAB: %x, FIN: %x\n", + (value & 0xc) >> 2, value & 0x3); + + return 0; +} + +static const struct regmap_config pfuze_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PFUZE_NUMREGS - 1, + .cache_type = REGCACHE_RBTREE, +}; + +static int pfuze100_regulator_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pfuze_chip *pfuze_chip; + struct pfuze_regulator_platform_data *pdata = + dev_get_platdata(&client->dev); + struct regulator_config config = { }; + int i, ret; + const struct of_device_id *match; + u32 regulator_num; + u32 sw_check_start, sw_check_end, sw_hi = 0x40; + + pfuze_chip = devm_kzalloc(&client->dev, sizeof(*pfuze_chip), + GFP_KERNEL); + if (!pfuze_chip) + return -ENOMEM; + + if (client->dev.of_node) { + match = of_match_device(of_match_ptr(pfuze_dt_ids), + &client->dev); + if (!match) { + dev_err(&client->dev, "Error: No device match found\n"); + return -ENODEV; + } + pfuze_chip->chip_id = (int)(long)match->data; + } else if (id) { + pfuze_chip->chip_id = id->driver_data; + } else { + dev_err(&client->dev, "No dts match or id table match found\n"); + return -ENODEV; + } + + i2c_set_clientdata(client, pfuze_chip); + pfuze_chip->dev = &client->dev; + + pfuze_chip->regmap = devm_regmap_init_i2c(client, &pfuze_regmap_config); + if (IS_ERR(pfuze_chip->regmap)) { + ret = PTR_ERR(pfuze_chip->regmap); + dev_err(&client->dev, + "regmap allocation failed with err %d\n", ret); + return ret; + } + + ret = pfuze_identify(pfuze_chip); + if (ret) { + dev_err(&client->dev, "unrecognized pfuze chip ID!\n"); + return ret; + } + + /* use the right regulators after identify the right device */ + switch (pfuze_chip->chip_id) { + case PFUZE3001: + pfuze_chip->pfuze_regulators = pfuze3001_regulators; + regulator_num = ARRAY_SIZE(pfuze3001_regulators); + sw_check_start = PFUZE3001_SW2; + sw_check_end = PFUZE3001_SW2; + sw_hi = 1 << 3; + break; + case PFUZE3000: + pfuze_chip->pfuze_regulators = pfuze3000_regulators; + regulator_num = ARRAY_SIZE(pfuze3000_regulators); + sw_check_start = PFUZE3000_SW2; + sw_check_end = PFUZE3000_SW2; + sw_hi = 1 << 3; + break; + case PFUZE200: + pfuze_chip->pfuze_regulators = pfuze200_regulators; + regulator_num = ARRAY_SIZE(pfuze200_regulators); + sw_check_start = PFUZE200_SW2; + sw_check_end = PFUZE200_SW3B; + break; + case PFUZE100: + default: + pfuze_chip->pfuze_regulators = pfuze100_regulators; + regulator_num = ARRAY_SIZE(pfuze100_regulators); + sw_check_start = PFUZE100_SW2; + sw_check_end = PFUZE100_SW4; + break; + } + dev_info(&client->dev, "pfuze%s found.\n", + (pfuze_chip->chip_id == PFUZE100) ? "100" : + (((pfuze_chip->chip_id == PFUZE200) ? "200" : + ((pfuze_chip->chip_id == PFUZE3000) ? "3000" : "3001")))); + + memcpy(pfuze_chip->regulator_descs, pfuze_chip->pfuze_regulators, + sizeof(pfuze_chip->regulator_descs)); + + ret = pfuze_parse_regulators_dt(pfuze_chip); + if (ret) + return ret; + + for (i = 0; i < regulator_num; i++) { + struct regulator_init_data *init_data; + struct regulator_desc *desc; + int val; + + desc = &pfuze_chip->regulator_descs[i].desc; + + if (pdata) + init_data = pdata->init_data[i]; + else + init_data = match_init_data(i); + + /* SW2~SW4 high bit check and modify the voltage value table */ + if (i >= sw_check_start && i <= sw_check_end) { + ret = regmap_read(pfuze_chip->regmap, + desc->vsel_reg, &val); + if (ret) { + dev_err(&client->dev, "Fails to read from the register.\n"); + return ret; + } + + if (val & sw_hi) { + if (pfuze_chip->chip_id == PFUZE3000 || + pfuze_chip->chip_id == PFUZE3001) { + desc->volt_table = pfuze3000_sw2hi; + desc->n_voltages = ARRAY_SIZE(pfuze3000_sw2hi); + } else { + desc->min_uV = 800000; + desc->uV_step = 50000; + desc->n_voltages = 51; + } + } + } + + /* + * Allow SW regulators to turn off. Checking it trough a flag is + * a workaround to keep the backward compatibility with existing + * old dtb's which may relay on the fact that we didn't disable + * the switched regulator till yet. + */ + if (pfuze_chip->flags & PFUZE_FLAG_DISABLE_SW) { + if (pfuze_chip->chip_id == PFUZE100 || + pfuze_chip->chip_id == PFUZE200) { + if (pfuze_chip->regulator_descs[i].sw_reg) { + desc->ops = &pfuze100_sw_disable_regulator_ops; + desc->enable_val = 0x8; + desc->disable_val = 0x0; + desc->enable_time = 500; + } + } + } + + config.dev = &client->dev; + config.init_data = init_data; + config.driver_data = pfuze_chip; + config.of_node = match_of_node(i); + + pfuze_chip->regulators[i] = + devm_regulator_register(&client->dev, desc, &config); + if (IS_ERR(pfuze_chip->regulators[i])) { + dev_err(&client->dev, "register regulator%s failed\n", + pfuze_chip->pfuze_regulators[i].desc.name); + return PTR_ERR(pfuze_chip->regulators[i]); + } + } + + return 0; +} + +static struct i2c_driver pfuze_driver = { + .id_table = pfuze_device_id, + .driver = { + .name = "pfuze100-regulator", + .of_match_table = pfuze_dt_ids, + }, + .probe = pfuze100_regulator_probe, +}; +module_i2c_driver(pfuze_driver); + +MODULE_AUTHOR("Robin Gong <b38343@freescale.com>"); +MODULE_DESCRIPTION("Regulator Driver for Freescale PFUZE100/200/3000/3001 PMIC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/pv88060-regulator.c b/drivers/regulator/pv88060-regulator.c new file mode 100644 index 000000000..1f2d81805 --- /dev/null +++ b/drivers/regulator/pv88060-regulator.c @@ -0,0 +1,434 @@ +/* + * pv88060-regulator.c - Regulator device driver for PV88060 + * Copyright (C) 2015 Powerventure Semiconductor Ltd. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regmap.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/regulator/of_regulator.h> +#include "pv88060-regulator.h" + +#define PV88060_MAX_REGULATORS 14 + +/* PV88060 REGULATOR IDs */ +enum { + /* BUCKs */ + PV88060_ID_BUCK1, + + /* LDOs */ + PV88060_ID_LDO1, + PV88060_ID_LDO2, + PV88060_ID_LDO3, + PV88060_ID_LDO4, + PV88060_ID_LDO5, + PV88060_ID_LDO6, + PV88060_ID_LDO7, + + /* SWTs */ + PV88060_ID_SW1, + PV88060_ID_SW2, + PV88060_ID_SW3, + PV88060_ID_SW4, + PV88060_ID_SW5, + PV88060_ID_SW6, +}; + +struct pv88060_regulator { + struct regulator_desc desc; + /* Current limiting */ + unsigned n_current_limits; + const int *current_limits; + unsigned int limit_mask; + unsigned int conf; /* buck configuration register */ +}; + +struct pv88060 { + struct device *dev; + struct regmap *regmap; + struct regulator_dev *rdev[PV88060_MAX_REGULATORS]; +}; + +static const struct regmap_config pv88060_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +/* Current limits array (in uA) for BUCK1 + * Entry indexes corresponds to register values. + */ + +static const int pv88060_buck1_limits[] = { + 1496000, 2393000, 3291000, 4189000 +}; + +static unsigned int pv88060_buck_get_mode(struct regulator_dev *rdev) +{ + struct pv88060_regulator *info = rdev_get_drvdata(rdev); + unsigned int data; + int ret, mode = 0; + + ret = regmap_read(rdev->regmap, info->conf, &data); + if (ret < 0) + return ret; + + switch (data & PV88060_BUCK_MODE_MASK) { + case PV88060_BUCK_MODE_SYNC: + mode = REGULATOR_MODE_FAST; + break; + case PV88060_BUCK_MODE_AUTO: + mode = REGULATOR_MODE_NORMAL; + break; + case PV88060_BUCK_MODE_SLEEP: + mode = REGULATOR_MODE_STANDBY; + break; + } + + return mode; +} + +static int pv88060_buck_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct pv88060_regulator *info = rdev_get_drvdata(rdev); + int val = 0; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = PV88060_BUCK_MODE_SYNC; + break; + case REGULATOR_MODE_NORMAL: + val = PV88060_BUCK_MODE_AUTO; + break; + case REGULATOR_MODE_STANDBY: + val = PV88060_BUCK_MODE_SLEEP; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(rdev->regmap, info->conf, + PV88060_BUCK_MODE_MASK, val); +} + +static int pv88060_set_current_limit(struct regulator_dev *rdev, int min, + int max) +{ + struct pv88060_regulator *info = rdev_get_drvdata(rdev); + int i; + + /* search for closest to maximum */ + for (i = info->n_current_limits - 1; i >= 0; i--) { + if (min <= info->current_limits[i] + && max >= info->current_limits[i]) { + return regmap_update_bits(rdev->regmap, + info->conf, + info->limit_mask, + i << PV88060_BUCK_ILIM_SHIFT); + } + } + + return -EINVAL; +} + +static int pv88060_get_current_limit(struct regulator_dev *rdev) +{ + struct pv88060_regulator *info = rdev_get_drvdata(rdev); + unsigned int data; + int ret; + + ret = regmap_read(rdev->regmap, info->conf, &data); + if (ret < 0) + return ret; + + data = (data & info->limit_mask) >> PV88060_BUCK_ILIM_SHIFT; + return info->current_limits[data]; +} + +static const struct regulator_ops pv88060_buck_ops = { + .get_mode = pv88060_buck_get_mode, + .set_mode = pv88060_buck_set_mode, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_current_limit = pv88060_set_current_limit, + .get_current_limit = pv88060_get_current_limit, +}; + +static const struct regulator_ops pv88060_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +#define PV88060_BUCK(chip, regl_name, min, step, max, limits_array) \ +{\ + .desc = {\ + .id = chip##_ID_##regl_name,\ + .name = __stringify(chip##_##regl_name),\ + .of_match = of_match_ptr(#regl_name),\ + .regulators_node = of_match_ptr("regulators"),\ + .type = REGULATOR_VOLTAGE,\ + .owner = THIS_MODULE,\ + .ops = &pv88060_buck_ops,\ + .min_uV = min,\ + .uV_step = step,\ + .n_voltages = ((max) - (min))/(step) + 1,\ + .enable_reg = PV88060_REG_##regl_name##_CONF0,\ + .enable_mask = PV88060_BUCK_EN, \ + .vsel_reg = PV88060_REG_##regl_name##_CONF0,\ + .vsel_mask = PV88060_VBUCK_MASK,\ + },\ + .current_limits = limits_array,\ + .n_current_limits = ARRAY_SIZE(limits_array),\ + .limit_mask = PV88060_BUCK_ILIM_MASK, \ + .conf = PV88060_REG_##regl_name##_CONF1,\ +} + +#define PV88060_LDO(chip, regl_name, min, step, max) \ +{\ + .desc = {\ + .id = chip##_ID_##regl_name,\ + .name = __stringify(chip##_##regl_name),\ + .of_match = of_match_ptr(#regl_name),\ + .regulators_node = of_match_ptr("regulators"),\ + .type = REGULATOR_VOLTAGE,\ + .owner = THIS_MODULE,\ + .ops = &pv88060_ldo_ops,\ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = (step) ? ((max - min) / step + 1) : 1, \ + .enable_reg = PV88060_REG_##regl_name##_CONF, \ + .enable_mask = PV88060_LDO_EN, \ + .vsel_reg = PV88060_REG_##regl_name##_CONF, \ + .vsel_mask = PV88060_VLDO_MASK, \ + },\ +} + +#define PV88060_SW(chip, regl_name, max) \ +{\ + .desc = {\ + .id = chip##_ID_##regl_name,\ + .name = __stringify(chip##_##regl_name),\ + .of_match = of_match_ptr(#regl_name),\ + .regulators_node = of_match_ptr("regulators"),\ + .type = REGULATOR_VOLTAGE,\ + .owner = THIS_MODULE,\ + .ops = &pv88060_ldo_ops,\ + .min_uV = max,\ + .uV_step = 0,\ + .n_voltages = 1,\ + .enable_reg = PV88060_REG_##regl_name##_CONF,\ + .enable_mask = PV88060_SW_EN,\ + },\ +} + +static const struct pv88060_regulator pv88060_regulator_info[] = { + PV88060_BUCK(PV88060, BUCK1, 2800000, 12500, 4387500, + pv88060_buck1_limits), + PV88060_LDO(PV88060, LDO1, 1200000, 50000, 3350000), + PV88060_LDO(PV88060, LDO2, 1200000, 50000, 3350000), + PV88060_LDO(PV88060, LDO3, 1200000, 50000, 3350000), + PV88060_LDO(PV88060, LDO4, 1200000, 50000, 3350000), + PV88060_LDO(PV88060, LDO5, 1200000, 50000, 3350000), + PV88060_LDO(PV88060, LDO6, 1200000, 50000, 3350000), + PV88060_LDO(PV88060, LDO7, 1200000, 50000, 3350000), + PV88060_SW(PV88060, SW1, 5000000), + PV88060_SW(PV88060, SW2, 5000000), + PV88060_SW(PV88060, SW3, 5000000), + PV88060_SW(PV88060, SW4, 5000000), + PV88060_SW(PV88060, SW5, 5000000), + PV88060_SW(PV88060, SW6, 5000000), +}; + +static irqreturn_t pv88060_irq_handler(int irq, void *data) +{ + struct pv88060 *chip = data; + int i, reg_val, err, ret = IRQ_NONE; + + err = regmap_read(chip->regmap, PV88060_REG_EVENT_A, ®_val); + if (err < 0) + goto error_i2c; + + if (reg_val & PV88060_E_VDD_FLT) { + for (i = 0; i < PV88060_MAX_REGULATORS; i++) { + if (chip->rdev[i] != NULL) { + regulator_notifier_call_chain(chip->rdev[i], + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + } + } + + err = regmap_write(chip->regmap, PV88060_REG_EVENT_A, + PV88060_E_VDD_FLT); + if (err < 0) + goto error_i2c; + + ret = IRQ_HANDLED; + } + + if (reg_val & PV88060_E_OVER_TEMP) { + for (i = 0; i < PV88060_MAX_REGULATORS; i++) { + if (chip->rdev[i] != NULL) { + regulator_notifier_call_chain(chip->rdev[i], + REGULATOR_EVENT_OVER_TEMP, + NULL); + } + } + + err = regmap_write(chip->regmap, PV88060_REG_EVENT_A, + PV88060_E_OVER_TEMP); + if (err < 0) + goto error_i2c; + + ret = IRQ_HANDLED; + } + + return ret; + +error_i2c: + dev_err(chip->dev, "I2C error : %d\n", err); + return IRQ_NONE; +} + +/* + * I2C driver interface functions + */ +static int pv88060_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regulator_init_data *init_data = dev_get_platdata(&i2c->dev); + struct pv88060 *chip; + struct regulator_config config = { }; + int error, i, ret = 0; + + chip = devm_kzalloc(&i2c->dev, sizeof(struct pv88060), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &i2c->dev; + chip->regmap = devm_regmap_init_i2c(i2c, &pv88060_regmap_config); + if (IS_ERR(chip->regmap)) { + error = PTR_ERR(chip->regmap); + dev_err(chip->dev, "Failed to allocate register map: %d\n", + error); + return error; + } + + i2c_set_clientdata(i2c, chip); + + if (i2c->irq != 0) { + ret = regmap_write(chip->regmap, PV88060_REG_MASK_A, 0xFF); + if (ret < 0) { + dev_err(chip->dev, + "Failed to mask A reg: %d\n", ret); + return ret; + } + + ret = regmap_write(chip->regmap, PV88060_REG_MASK_B, 0xFF); + if (ret < 0) { + dev_err(chip->dev, + "Failed to mask B reg: %d\n", ret); + return ret; + } + + ret = regmap_write(chip->regmap, PV88060_REG_MASK_C, 0xFF); + if (ret < 0) { + dev_err(chip->dev, + "Failed to mask C reg: %d\n", ret); + return ret; + } + + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + pv88060_irq_handler, + IRQF_TRIGGER_LOW|IRQF_ONESHOT, + "pv88060", chip); + if (ret != 0) { + dev_err(chip->dev, "Failed to request IRQ: %d\n", + i2c->irq); + return ret; + } + + ret = regmap_update_bits(chip->regmap, PV88060_REG_MASK_A, + PV88060_M_VDD_FLT | PV88060_M_OVER_TEMP, 0); + if (ret < 0) { + dev_err(chip->dev, + "Failed to update mask reg: %d\n", ret); + return ret; + } + + } else { + dev_warn(chip->dev, "No IRQ configured\n"); + } + + config.dev = chip->dev; + config.regmap = chip->regmap; + + for (i = 0; i < PV88060_MAX_REGULATORS; i++) { + if (init_data) + config.init_data = &init_data[i]; + + config.driver_data = (void *)&pv88060_regulator_info[i]; + chip->rdev[i] = devm_regulator_register(chip->dev, + &pv88060_regulator_info[i].desc, &config); + if (IS_ERR(chip->rdev[i])) { + dev_err(chip->dev, + "Failed to register PV88060 regulator\n"); + return PTR_ERR(chip->rdev[i]); + } + } + + return 0; +} + +static const struct i2c_device_id pv88060_i2c_id[] = { + {"pv88060", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, pv88060_i2c_id); + +#ifdef CONFIG_OF +static const struct of_device_id pv88060_dt_ids[] = { + { .compatible = "pvs,pv88060", .data = &pv88060_i2c_id[0] }, + {}, +}; +MODULE_DEVICE_TABLE(of, pv88060_dt_ids); +#endif + +static struct i2c_driver pv88060_regulator_driver = { + .driver = { + .name = "pv88060", + .of_match_table = of_match_ptr(pv88060_dt_ids), + }, + .probe = pv88060_i2c_probe, + .id_table = pv88060_i2c_id, +}; + +module_i2c_driver(pv88060_regulator_driver); + +MODULE_AUTHOR("James Ban <James.Ban.opensource@diasemi.com>"); +MODULE_DESCRIPTION("Regulator device driver for Powerventure PV88060"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/pv88060-regulator.h b/drivers/regulator/pv88060-regulator.h new file mode 100644 index 000000000..02ca9203a --- /dev/null +++ b/drivers/regulator/pv88060-regulator.h @@ -0,0 +1,69 @@ +/* + * pv88060-regulator.h - Regulator definitions for PV88060 + * Copyright (C) 2015 Powerventure Semiconductor Ltd. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PV88060_REGISTERS_H__ +#define __PV88060_REGISTERS_H__ + +/* System Control and Event Registers */ +#define PV88060_REG_EVENT_A 0x04 +#define PV88060_REG_MASK_A 0x08 +#define PV88060_REG_MASK_B 0x09 +#define PV88060_REG_MASK_C 0x0A + +/* Regulator Registers */ +#define PV88060_REG_BUCK1_CONF0 0x1B +#define PV88060_REG_BUCK1_CONF1 0x1C +#define PV88060_REG_LDO1_CONF 0x1D +#define PV88060_REG_LDO2_CONF 0x1E +#define PV88060_REG_LDO3_CONF 0x1F +#define PV88060_REG_LDO4_CONF 0x20 +#define PV88060_REG_LDO5_CONF 0x21 +#define PV88060_REG_LDO6_CONF 0x22 +#define PV88060_REG_LDO7_CONF 0x23 + +#define PV88060_REG_SW1_CONF 0x3B +#define PV88060_REG_SW2_CONF 0x3C +#define PV88060_REG_SW3_CONF 0x3D +#define PV88060_REG_SW4_CONF 0x3E +#define PV88060_REG_SW5_CONF 0x3F +#define PV88060_REG_SW6_CONF 0x40 + +/* PV88060_REG_EVENT_A (addr=0x04) */ +#define PV88060_E_VDD_FLT 0x01 +#define PV88060_E_OVER_TEMP 0x02 + +/* PV88060_REG_MASK_A (addr=0x08) */ +#define PV88060_M_VDD_FLT 0x01 +#define PV88060_M_OVER_TEMP 0x02 + +/* PV88060_REG_BUCK1_CONF0 (addr=0x1B) */ +#define PV88060_BUCK_EN 0x80 +#define PV88060_VBUCK_MASK 0x7F +/* PV88060_REG_LDO1/2/3/4/5/6/7_CONT */ +#define PV88060_LDO_EN 0x40 +#define PV88060_VLDO_MASK 0x3F +/* PV88060_REG_SW1/2/3/4/5_CONF */ +#define PV88060_SW_EN 0x80 + +/* PV88060_REG_BUCK1_CONF1 (addr=0x1C) */ +#define PV88060_BUCK_ILIM_SHIFT 2 +#define PV88060_BUCK_ILIM_MASK 0x0C +#define PV88060_BUCK_MODE_SHIFT 0 +#define PV88060_BUCK_MODE_MASK 0x03 +#define PV88060_BUCK_MODE_SLEEP 0x00 +#define PV88060_BUCK_MODE_AUTO 0x01 +#define PV88060_BUCK_MODE_SYNC 0x02 + +#endif /* __PV88060_REGISTERS_H__ */ diff --git a/drivers/regulator/pv88080-regulator.c b/drivers/regulator/pv88080-regulator.c new file mode 100644 index 000000000..6770e4de2 --- /dev/null +++ b/drivers/regulator/pv88080-regulator.c @@ -0,0 +1,621 @@ +/* + * pv88080-regulator.c - Regulator device driver for PV88080 + * Copyright (C) 2016 Powerventure Semiconductor Ltd. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regmap.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/regulator/of_regulator.h> +#include "pv88080-regulator.h" + +#define PV88080_MAX_REGULATORS 4 + +/* PV88080 REGULATOR IDs */ +enum { + /* BUCKs */ + PV88080_ID_BUCK1, + PV88080_ID_BUCK2, + PV88080_ID_BUCK3, + PV88080_ID_HVBUCK, +}; + +enum pv88080_types { + TYPE_PV88080_AA, + TYPE_PV88080_BA, +}; + +struct pv88080_regulator { + struct regulator_desc desc; + /* Current limiting */ + unsigned int n_current_limits; + const int *current_limits; + unsigned int limit_mask; + unsigned int mode_reg; + unsigned int limit_reg; + unsigned int conf2; + unsigned int conf5; +}; + +struct pv88080 { + struct device *dev; + struct regmap *regmap; + struct regulator_dev *rdev[PV88080_MAX_REGULATORS]; + unsigned long type; + const struct pv88080_compatible_regmap *regmap_config; +}; + +struct pv88080_buck_voltage { + int min_uV; + int max_uV; + int uV_step; +}; + +struct pv88080_buck_regmap { + /* REGS */ + int buck_enable_reg; + int buck_vsel_reg; + int buck_mode_reg; + int buck_limit_reg; + int buck_vdac_range_reg; + int buck_vrange_gain_reg; + /* MASKS */ + int buck_enable_mask; + int buck_vsel_mask; + int buck_limit_mask; +}; + +struct pv88080_compatible_regmap { + /* BUCK1, 2, 3 */ + struct pv88080_buck_regmap buck_regmap[PV88080_MAX_REGULATORS-1]; + /* HVBUCK */ + int hvbuck_enable_reg; + int hvbuck_vsel_reg; + int hvbuck_enable_mask; + int hvbuck_vsel_mask; +}; + +static const struct regmap_config pv88080_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +/* Current limits array (in uA) for BUCK1, BUCK2, BUCK3. + * Entry indexes corresponds to register values. + */ + +static const int pv88080_buck1_limits[] = { + 3230000, 5130000, 6960000, 8790000 +}; + +static const int pv88080_buck23_limits[] = { + 1496000, 2393000, 3291000, 4189000 +}; + +static const struct pv88080_buck_voltage pv88080_buck_vol[2] = { + { + .min_uV = 600000, + .max_uV = 1393750, + .uV_step = 6250, + }, + { + .min_uV = 1400000, + .max_uV = 2193750, + .uV_step = 6250, + }, +}; + +static const struct pv88080_compatible_regmap pv88080_aa_regs = { + /* BUCK1 */ + .buck_regmap[0] = { + .buck_enable_reg = PV88080AA_REG_BUCK1_CONF0, + .buck_vsel_reg = PV88080AA_REG_BUCK1_CONF0, + .buck_mode_reg = PV88080AA_REG_BUCK1_CONF1, + .buck_limit_reg = PV88080AA_REG_BUCK1_CONF1, + .buck_vdac_range_reg = PV88080AA_REG_BUCK1_CONF2, + .buck_vrange_gain_reg = PV88080AA_REG_BUCK1_CONF5, + .buck_enable_mask = PV88080_BUCK1_EN, + .buck_vsel_mask = PV88080_VBUCK1_MASK, + .buck_limit_mask = PV88080_BUCK1_ILIM_MASK, + }, + /* BUCK2 */ + .buck_regmap[1] = { + .buck_enable_reg = PV88080AA_REG_BUCK2_CONF0, + .buck_vsel_reg = PV88080AA_REG_BUCK2_CONF0, + .buck_mode_reg = PV88080AA_REG_BUCK2_CONF1, + .buck_limit_reg = PV88080AA_REG_BUCK2_CONF1, + .buck_vdac_range_reg = PV88080AA_REG_BUCK2_CONF2, + .buck_vrange_gain_reg = PV88080AA_REG_BUCK2_CONF5, + .buck_enable_mask = PV88080_BUCK2_EN, + .buck_vsel_mask = PV88080_VBUCK2_MASK, + .buck_limit_mask = PV88080_BUCK2_ILIM_MASK, + }, + /* BUCK3 */ + .buck_regmap[2] = { + .buck_enable_reg = PV88080AA_REG_BUCK3_CONF0, + .buck_vsel_reg = PV88080AA_REG_BUCK3_CONF0, + .buck_mode_reg = PV88080AA_REG_BUCK3_CONF1, + .buck_limit_reg = PV88080AA_REG_BUCK3_CONF1, + .buck_vdac_range_reg = PV88080AA_REG_BUCK3_CONF2, + .buck_vrange_gain_reg = PV88080AA_REG_BUCK3_CONF5, + .buck_enable_mask = PV88080_BUCK3_EN, + .buck_vsel_mask = PV88080_VBUCK3_MASK, + .buck_limit_mask = PV88080_BUCK3_ILIM_MASK, + }, + /* HVBUCK */ + .hvbuck_enable_reg = PV88080AA_REG_HVBUCK_CONF2, + .hvbuck_vsel_reg = PV88080AA_REG_HVBUCK_CONF1, + .hvbuck_enable_mask = PV88080_HVBUCK_EN, + .hvbuck_vsel_mask = PV88080_VHVBUCK_MASK, +}; + +static const struct pv88080_compatible_regmap pv88080_ba_regs = { + /* BUCK1 */ + .buck_regmap[0] = { + .buck_enable_reg = PV88080BA_REG_BUCK1_CONF0, + .buck_vsel_reg = PV88080BA_REG_BUCK1_CONF0, + .buck_mode_reg = PV88080BA_REG_BUCK1_CONF1, + .buck_limit_reg = PV88080BA_REG_BUCK1_CONF1, + .buck_vdac_range_reg = PV88080BA_REG_BUCK1_CONF2, + .buck_vrange_gain_reg = PV88080BA_REG_BUCK1_CONF5, + .buck_enable_mask = PV88080_BUCK1_EN, + .buck_vsel_mask = PV88080_VBUCK1_MASK, + .buck_limit_mask = PV88080_BUCK1_ILIM_MASK, + }, + /* BUCK2 */ + .buck_regmap[1] = { + .buck_enable_reg = PV88080BA_REG_BUCK2_CONF0, + .buck_vsel_reg = PV88080BA_REG_BUCK2_CONF0, + .buck_mode_reg = PV88080BA_REG_BUCK2_CONF1, + .buck_limit_reg = PV88080BA_REG_BUCK2_CONF1, + .buck_vdac_range_reg = PV88080BA_REG_BUCK2_CONF2, + .buck_vrange_gain_reg = PV88080BA_REG_BUCK2_CONF5, + .buck_enable_mask = PV88080_BUCK2_EN, + .buck_vsel_mask = PV88080_VBUCK2_MASK, + .buck_limit_mask = PV88080_BUCK2_ILIM_MASK, + }, + /* BUCK3 */ + .buck_regmap[2] = { + .buck_enable_reg = PV88080BA_REG_BUCK3_CONF0, + .buck_vsel_reg = PV88080BA_REG_BUCK3_CONF0, + .buck_mode_reg = PV88080BA_REG_BUCK3_CONF1, + .buck_limit_reg = PV88080BA_REG_BUCK3_CONF1, + .buck_vdac_range_reg = PV88080BA_REG_BUCK3_CONF2, + .buck_vrange_gain_reg = PV88080BA_REG_BUCK3_CONF5, + .buck_enable_mask = PV88080_BUCK3_EN, + .buck_vsel_mask = PV88080_VBUCK3_MASK, + .buck_limit_mask = PV88080_BUCK3_ILIM_MASK, + }, + /* HVBUCK */ + .hvbuck_enable_reg = PV88080BA_REG_HVBUCK_CONF2, + .hvbuck_vsel_reg = PV88080BA_REG_HVBUCK_CONF1, + .hvbuck_enable_mask = PV88080_HVBUCK_EN, + .hvbuck_vsel_mask = PV88080_VHVBUCK_MASK, +}; + +#ifdef CONFIG_OF +static const struct of_device_id pv88080_dt_ids[] = { + { .compatible = "pvs,pv88080", .data = (void *)TYPE_PV88080_AA }, + { .compatible = "pvs,pv88080-aa", .data = (void *)TYPE_PV88080_AA }, + { .compatible = "pvs,pv88080-ba", .data = (void *)TYPE_PV88080_BA }, + {}, +}; +MODULE_DEVICE_TABLE(of, pv88080_dt_ids); +#endif + +static unsigned int pv88080_buck_get_mode(struct regulator_dev *rdev) +{ + struct pv88080_regulator *info = rdev_get_drvdata(rdev); + unsigned int data; + int ret, mode = 0; + + ret = regmap_read(rdev->regmap, info->mode_reg, &data); + if (ret < 0) + return ret; + + switch (data & PV88080_BUCK1_MODE_MASK) { + case PV88080_BUCK_MODE_SYNC: + mode = REGULATOR_MODE_FAST; + break; + case PV88080_BUCK_MODE_AUTO: + mode = REGULATOR_MODE_NORMAL; + break; + case PV88080_BUCK_MODE_SLEEP: + mode = REGULATOR_MODE_STANDBY; + break; + default: + return -EINVAL; + } + + return mode; +} + +static int pv88080_buck_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct pv88080_regulator *info = rdev_get_drvdata(rdev); + int val = 0; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = PV88080_BUCK_MODE_SYNC; + break; + case REGULATOR_MODE_NORMAL: + val = PV88080_BUCK_MODE_AUTO; + break; + case REGULATOR_MODE_STANDBY: + val = PV88080_BUCK_MODE_SLEEP; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(rdev->regmap, info->mode_reg, + PV88080_BUCK1_MODE_MASK, val); +} + +static int pv88080_set_current_limit(struct regulator_dev *rdev, int min, + int max) +{ + struct pv88080_regulator *info = rdev_get_drvdata(rdev); + int i; + + /* search for closest to maximum */ + for (i = info->n_current_limits - 1; i >= 0; i--) { + if (min <= info->current_limits[i] + && max >= info->current_limits[i]) { + return regmap_update_bits(rdev->regmap, + info->limit_reg, + info->limit_mask, + i << PV88080_BUCK1_ILIM_SHIFT); + } + } + + return -EINVAL; +} + +static int pv88080_get_current_limit(struct regulator_dev *rdev) +{ + struct pv88080_regulator *info = rdev_get_drvdata(rdev); + unsigned int data; + int ret; + + ret = regmap_read(rdev->regmap, info->limit_reg, &data); + if (ret < 0) + return ret; + + data = (data & info->limit_mask) >> PV88080_BUCK1_ILIM_SHIFT; + return info->current_limits[data]; +} + +static const struct regulator_ops pv88080_buck_ops = { + .get_mode = pv88080_buck_get_mode, + .set_mode = pv88080_buck_set_mode, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_current_limit = pv88080_set_current_limit, + .get_current_limit = pv88080_get_current_limit, +}; + +static const struct regulator_ops pv88080_hvbuck_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +#define PV88080_BUCK(chip, regl_name, min, step, max, limits_array) \ +{\ + .desc = {\ + .id = chip##_ID_##regl_name,\ + .name = __stringify(chip##_##regl_name),\ + .of_match = of_match_ptr(#regl_name),\ + .regulators_node = of_match_ptr("regulators"),\ + .type = REGULATOR_VOLTAGE,\ + .owner = THIS_MODULE,\ + .ops = &pv88080_buck_ops,\ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = ((max) - (min))/(step) + 1, \ + },\ + .current_limits = limits_array, \ + .n_current_limits = ARRAY_SIZE(limits_array), \ +} + +#define PV88080_HVBUCK(chip, regl_name, min, step, max) \ +{\ + .desc = {\ + .id = chip##_ID_##regl_name,\ + .name = __stringify(chip##_##regl_name),\ + .of_match = of_match_ptr(#regl_name),\ + .regulators_node = of_match_ptr("regulators"),\ + .type = REGULATOR_VOLTAGE,\ + .owner = THIS_MODULE,\ + .ops = &pv88080_hvbuck_ops,\ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = ((max) - (min))/(step) + 1, \ + },\ +} + +static struct pv88080_regulator pv88080_regulator_info[] = { + PV88080_BUCK(PV88080, BUCK1, 600000, 6250, 1393750, + pv88080_buck1_limits), + PV88080_BUCK(PV88080, BUCK2, 600000, 6250, 1393750, + pv88080_buck23_limits), + PV88080_BUCK(PV88080, BUCK3, 600000, 6250, 1393750, + pv88080_buck23_limits), + PV88080_HVBUCK(PV88080, HVBUCK, 0, 5000, 1275000), +}; + +static irqreturn_t pv88080_irq_handler(int irq, void *data) +{ + struct pv88080 *chip = data; + int i, reg_val, err, ret = IRQ_NONE; + + err = regmap_read(chip->regmap, PV88080_REG_EVENT_A, ®_val); + if (err < 0) + goto error_i2c; + + if (reg_val & PV88080_E_VDD_FLT) { + for (i = 0; i < PV88080_MAX_REGULATORS; i++) { + if (chip->rdev[i] != NULL) { + regulator_notifier_call_chain(chip->rdev[i], + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + } + } + + err = regmap_write(chip->regmap, PV88080_REG_EVENT_A, + PV88080_E_VDD_FLT); + if (err < 0) + goto error_i2c; + + ret = IRQ_HANDLED; + } + + if (reg_val & PV88080_E_OVER_TEMP) { + for (i = 0; i < PV88080_MAX_REGULATORS; i++) { + if (chip->rdev[i] != NULL) { + regulator_notifier_call_chain(chip->rdev[i], + REGULATOR_EVENT_OVER_TEMP, + NULL); + } + } + + err = regmap_write(chip->regmap, PV88080_REG_EVENT_A, + PV88080_E_OVER_TEMP); + if (err < 0) + goto error_i2c; + + ret = IRQ_HANDLED; + } + + return ret; + +error_i2c: + dev_err(chip->dev, "I2C error : %d\n", err); + return IRQ_NONE; +} + +/* + * I2C driver interface functions + */ +static int pv88080_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regulator_init_data *init_data = dev_get_platdata(&i2c->dev); + struct pv88080 *chip; + const struct pv88080_compatible_regmap *regmap_config; + const struct of_device_id *match; + struct regulator_config config = { }; + int i, error, ret; + unsigned int conf2, conf5; + + chip = devm_kzalloc(&i2c->dev, sizeof(struct pv88080), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &i2c->dev; + chip->regmap = devm_regmap_init_i2c(i2c, &pv88080_regmap_config); + if (IS_ERR(chip->regmap)) { + error = PTR_ERR(chip->regmap); + dev_err(chip->dev, "Failed to allocate register map: %d\n", + error); + return error; + } + + if (i2c->dev.of_node) { + match = of_match_node(pv88080_dt_ids, i2c->dev.of_node); + if (!match) { + dev_err(chip->dev, "Failed to get of_match_node\n"); + return -EINVAL; + } + chip->type = (unsigned long)match->data; + } else { + chip->type = id->driver_data; + } + + i2c_set_clientdata(i2c, chip); + + if (i2c->irq != 0) { + ret = regmap_write(chip->regmap, PV88080_REG_MASK_A, 0xFF); + if (ret < 0) { + dev_err(chip->dev, + "Failed to mask A reg: %d\n", ret); + return ret; + } + ret = regmap_write(chip->regmap, PV88080_REG_MASK_B, 0xFF); + if (ret < 0) { + dev_err(chip->dev, + "Failed to mask B reg: %d\n", ret); + return ret; + } + ret = regmap_write(chip->regmap, PV88080_REG_MASK_C, 0xFF); + if (ret < 0) { + dev_err(chip->dev, + "Failed to mask C reg: %d\n", ret); + return ret; + } + + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + pv88080_irq_handler, + IRQF_TRIGGER_LOW|IRQF_ONESHOT, + "pv88080", chip); + if (ret != 0) { + dev_err(chip->dev, "Failed to request IRQ: %d\n", + i2c->irq); + return ret; + } + + ret = regmap_update_bits(chip->regmap, PV88080_REG_MASK_A, + PV88080_M_VDD_FLT | PV88080_M_OVER_TEMP, 0); + if (ret < 0) { + dev_err(chip->dev, + "Failed to update mask reg: %d\n", ret); + return ret; + } + } else { + dev_warn(chip->dev, "No IRQ configured\n"); + } + + switch (chip->type) { + case TYPE_PV88080_AA: + chip->regmap_config = &pv88080_aa_regs; + break; + case TYPE_PV88080_BA: + chip->regmap_config = &pv88080_ba_regs; + break; + } + + regmap_config = chip->regmap_config; + config.dev = chip->dev; + config.regmap = chip->regmap; + + /* Registeration for BUCK1, 2, 3 */ + for (i = 0; i < PV88080_MAX_REGULATORS-1; i++) { + if (init_data) + config.init_data = &init_data[i]; + + pv88080_regulator_info[i].limit_reg + = regmap_config->buck_regmap[i].buck_limit_reg; + pv88080_regulator_info[i].limit_mask + = regmap_config->buck_regmap[i].buck_limit_mask; + pv88080_regulator_info[i].mode_reg + = regmap_config->buck_regmap[i].buck_mode_reg; + pv88080_regulator_info[i].conf2 + = regmap_config->buck_regmap[i].buck_vdac_range_reg; + pv88080_regulator_info[i].conf5 + = regmap_config->buck_regmap[i].buck_vrange_gain_reg; + pv88080_regulator_info[i].desc.enable_reg + = regmap_config->buck_regmap[i].buck_enable_reg; + pv88080_regulator_info[i].desc.enable_mask + = regmap_config->buck_regmap[i].buck_enable_mask; + pv88080_regulator_info[i].desc.vsel_reg + = regmap_config->buck_regmap[i].buck_vsel_reg; + pv88080_regulator_info[i].desc.vsel_mask + = regmap_config->buck_regmap[i].buck_vsel_mask; + + ret = regmap_read(chip->regmap, + pv88080_regulator_info[i].conf2, &conf2); + if (ret < 0) + return ret; + conf2 = ((conf2 >> PV88080_BUCK_VDAC_RANGE_SHIFT) & + PV88080_BUCK_VDAC_RANGE_MASK); + + ret = regmap_read(chip->regmap, + pv88080_regulator_info[i].conf5, &conf5); + if (ret < 0) + return ret; + conf5 = ((conf5 >> PV88080_BUCK_VRANGE_GAIN_SHIFT) & + PV88080_BUCK_VRANGE_GAIN_MASK); + + pv88080_regulator_info[i].desc.min_uV = + pv88080_buck_vol[conf2].min_uV * (conf5+1); + pv88080_regulator_info[i].desc.uV_step = + pv88080_buck_vol[conf2].uV_step * (conf5+1); + pv88080_regulator_info[i].desc.n_voltages = + ((pv88080_buck_vol[conf2].max_uV * (conf5+1)) + - (pv88080_regulator_info[i].desc.min_uV)) + /(pv88080_regulator_info[i].desc.uV_step) + 1; + + config.driver_data = (void *)&pv88080_regulator_info[i]; + chip->rdev[i] = devm_regulator_register(chip->dev, + &pv88080_regulator_info[i].desc, &config); + if (IS_ERR(chip->rdev[i])) { + dev_err(chip->dev, + "Failed to register PV88080 regulator\n"); + return PTR_ERR(chip->rdev[i]); + } + } + + pv88080_regulator_info[PV88080_ID_HVBUCK].desc.enable_reg + = regmap_config->hvbuck_enable_reg; + pv88080_regulator_info[PV88080_ID_HVBUCK].desc.enable_mask + = regmap_config->hvbuck_enable_mask; + pv88080_regulator_info[PV88080_ID_HVBUCK].desc.vsel_reg + = regmap_config->hvbuck_vsel_reg; + pv88080_regulator_info[PV88080_ID_HVBUCK].desc.vsel_mask + = regmap_config->hvbuck_vsel_mask; + + /* Registeration for HVBUCK */ + if (init_data) + config.init_data = &init_data[PV88080_ID_HVBUCK]; + + config.driver_data = (void *)&pv88080_regulator_info[PV88080_ID_HVBUCK]; + chip->rdev[PV88080_ID_HVBUCK] = devm_regulator_register(chip->dev, + &pv88080_regulator_info[PV88080_ID_HVBUCK].desc, &config); + if (IS_ERR(chip->rdev[PV88080_ID_HVBUCK])) { + dev_err(chip->dev, "Failed to register PV88080 regulator\n"); + return PTR_ERR(chip->rdev[PV88080_ID_HVBUCK]); + } + + return 0; +} + +static const struct i2c_device_id pv88080_i2c_id[] = { + { "pv88080", TYPE_PV88080_AA }, + { "pv88080-aa", TYPE_PV88080_AA }, + { "pv88080-ba", TYPE_PV88080_BA }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, pv88080_i2c_id); + +static struct i2c_driver pv88080_regulator_driver = { + .driver = { + .name = "pv88080", + .of_match_table = of_match_ptr(pv88080_dt_ids), + }, + .probe = pv88080_i2c_probe, + .id_table = pv88080_i2c_id, +}; + +module_i2c_driver(pv88080_regulator_driver); + +MODULE_AUTHOR("James Ban <James.Ban.opensource@diasemi.com>"); +MODULE_DESCRIPTION("Regulator device driver for Powerventure PV88080"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/pv88080-regulator.h b/drivers/regulator/pv88080-regulator.h new file mode 100644 index 000000000..ae25ff360 --- /dev/null +++ b/drivers/regulator/pv88080-regulator.h @@ -0,0 +1,118 @@ +/* + * pv88080-regulator.h - Regulator definitions for PV88080 + * Copyright (C) 2016 Powerventure Semiconductor Ltd. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PV88080_REGISTERS_H__ +#define __PV88080_REGISTERS_H__ + +/* System Control and Event Registers */ +#define PV88080_REG_EVENT_A 0x04 +#define PV88080_REG_MASK_A 0x09 +#define PV88080_REG_MASK_B 0x0A +#define PV88080_REG_MASK_C 0x0B + +/* Regulator Registers - rev. AA */ +#define PV88080AA_REG_HVBUCK_CONF1 0x2D +#define PV88080AA_REG_HVBUCK_CONF2 0x2E +#define PV88080AA_REG_BUCK1_CONF0 0x27 +#define PV88080AA_REG_BUCK1_CONF1 0x28 +#define PV88080AA_REG_BUCK1_CONF2 0x59 +#define PV88080AA_REG_BUCK1_CONF5 0x5C +#define PV88080AA_REG_BUCK2_CONF0 0x29 +#define PV88080AA_REG_BUCK2_CONF1 0x2A +#define PV88080AA_REG_BUCK2_CONF2 0x61 +#define PV88080AA_REG_BUCK2_CONF5 0x64 +#define PV88080AA_REG_BUCK3_CONF0 0x2B +#define PV88080AA_REG_BUCK3_CONF1 0x2C +#define PV88080AA_REG_BUCK3_CONF2 0x69 +#define PV88080AA_REG_BUCK3_CONF5 0x6C + +/* Regulator Registers - rev. BA */ +#define PV88080BA_REG_HVBUCK_CONF1 0x33 +#define PV88080BA_REG_HVBUCK_CONF2 0x34 +#define PV88080BA_REG_BUCK1_CONF0 0x2A +#define PV88080BA_REG_BUCK1_CONF1 0x2C +#define PV88080BA_REG_BUCK1_CONF2 0x5A +#define PV88080BA_REG_BUCK1_CONF5 0x5D +#define PV88080BA_REG_BUCK2_CONF0 0x2D +#define PV88080BA_REG_BUCK2_CONF1 0x2F +#define PV88080BA_REG_BUCK2_CONF2 0x63 +#define PV88080BA_REG_BUCK2_CONF5 0x66 +#define PV88080BA_REG_BUCK3_CONF0 0x30 +#define PV88080BA_REG_BUCK3_CONF1 0x32 +#define PV88080BA_REG_BUCK3_CONF2 0x6C +#define PV88080BA_REG_BUCK3_CONF5 0x6F + +/* PV88080_REG_EVENT_A (addr=0x04) */ +#define PV88080_E_VDD_FLT 0x01 +#define PV88080_E_OVER_TEMP 0x02 + +/* PV88080_REG_MASK_A (addr=0x09) */ +#define PV88080_M_VDD_FLT 0x01 +#define PV88080_M_OVER_TEMP 0x02 + +/* PV88080_REG_BUCK1_CONF0 (addr=0x27|0x2A) */ +#define PV88080_BUCK1_EN 0x80 +#define PV88080_VBUCK1_MASK 0x7F + +/* PV88080_REG_BUCK2_CONF0 (addr=0x29|0x2D) */ +#define PV88080_BUCK2_EN 0x80 +#define PV88080_VBUCK2_MASK 0x7F + +/* PV88080_REG_BUCK3_CONF0 (addr=0x2B|0x30) */ +#define PV88080_BUCK3_EN 0x80 +#define PV88080_VBUCK3_MASK 0x7F + +/* PV88080_REG_BUCK1_CONF1 (addr=0x28|0x2C) */ +#define PV88080_BUCK1_ILIM_SHIFT 2 +#define PV88080_BUCK1_ILIM_MASK 0x0C +#define PV88080_BUCK1_MODE_MASK 0x03 + +/* PV88080_REG_BUCK2_CONF1 (addr=0x2A|0x2F) */ +#define PV88080_BUCK2_ILIM_SHIFT 2 +#define PV88080_BUCK2_ILIM_MASK 0x0C +#define PV88080_BUCK2_MODE_MASK 0x03 + +/* PV88080_REG_BUCK3_CONF1 (addr=0x2C|0x32) */ +#define PV88080_BUCK3_ILIM_SHIFT 2 +#define PV88080_BUCK3_ILIM_MASK 0x0C +#define PV88080_BUCK3_MODE_MASK 0x03 + +#define PV88080_BUCK_MODE_SLEEP 0x00 +#define PV88080_BUCK_MODE_AUTO 0x01 +#define PV88080_BUCK_MODE_SYNC 0x02 + +/* PV88080_REG_HVBUCK_CONF1 (addr=0x2D|0x33) */ +#define PV88080_VHVBUCK_MASK 0xFF + +/* PV88080_REG_HVBUCK_CONF1 (addr=0x2E|0x34) */ +#define PV88080_HVBUCK_EN 0x01 + +/* PV88080_REG_BUCK2_CONF2 (addr=0x61|0x63) */ +/* PV88080_REG_BUCK3_CONF2 (addr=0x69|0x6C) */ +#define PV88080_BUCK_VDAC_RANGE_SHIFT 7 +#define PV88080_BUCK_VDAC_RANGE_MASK 0x01 + +#define PV88080_BUCK_VDAC_RANGE_1 0x00 +#define PV88080_BUCK_VDAC_RANGE_2 0x01 + +/* PV88080_REG_BUCK2_CONF5 (addr=0x64|0x66) */ +/* PV88080_REG_BUCK3_CONF5 (addr=0x6C|0x6F) */ +#define PV88080_BUCK_VRANGE_GAIN_SHIFT 0 +#define PV88080_BUCK_VRANGE_GAIN_MASK 0x01 + +#define PV88080_BUCK_VRANGE_GAIN_1 0x00 +#define PV88080_BUCK_VRANGE_GAIN_2 0x01 + +#endif /* __PV88080_REGISTERS_H__ */ diff --git a/drivers/regulator/pv88090-regulator.c b/drivers/regulator/pv88090-regulator.c new file mode 100644 index 000000000..2302b0df7 --- /dev/null +++ b/drivers/regulator/pv88090-regulator.c @@ -0,0 +1,460 @@ +/* + * pv88090-regulator.c - Regulator device driver for PV88090 + * Copyright (C) 2015 Powerventure Semiconductor Ltd. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regmap.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/regulator/of_regulator.h> +#include "pv88090-regulator.h" + +#define PV88090_MAX_REGULATORS 5 + +/* PV88090 REGULATOR IDs */ +enum { + /* BUCKs */ + PV88090_ID_BUCK1, + PV88090_ID_BUCK2, + PV88090_ID_BUCK3, + + /* LDOs */ + PV88090_ID_LDO1, + PV88090_ID_LDO2, +}; + +struct pv88090_regulator { + struct regulator_desc desc; + /* Current limiting */ + unsigned int n_current_limits; + const int *current_limits; + unsigned int limit_mask; + unsigned int conf; + unsigned int conf2; +}; + +struct pv88090 { + struct device *dev; + struct regmap *regmap; + struct regulator_dev *rdev[PV88090_MAX_REGULATORS]; +}; + +struct pv88090_buck_voltage { + int min_uV; + int max_uV; + int uV_step; +}; + +static const struct regmap_config pv88090_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +/* Current limits array (in uA) for BUCK1, BUCK2, BUCK3. + * Entry indexes corresponds to register values. + */ + +static const int pv88090_buck1_limits[] = { + 220000, 440000, 660000, 880000, 1100000, 1320000, 1540000, 1760000, + 1980000, 2200000, 2420000, 2640000, 2860000, 3080000, 3300000, 3520000, + 3740000, 3960000, 4180000, 4400000, 4620000, 4840000, 5060000, 5280000, + 5500000, 5720000, 5940000, 6160000, 6380000, 6600000, 6820000, 7040000 +}; + +static const int pv88090_buck23_limits[] = { + 1496000, 2393000, 3291000, 4189000 +}; + +static const struct pv88090_buck_voltage pv88090_buck_vol[3] = { + { + .min_uV = 600000, + .max_uV = 1393750, + .uV_step = 6250, + }, + + { + .min_uV = 1400000, + .max_uV = 2193750, + .uV_step = 6250, + }, + { + .min_uV = 1250000, + .max_uV = 2837500, + .uV_step = 12500, + }, +}; + +static unsigned int pv88090_buck_get_mode(struct regulator_dev *rdev) +{ + struct pv88090_regulator *info = rdev_get_drvdata(rdev); + unsigned int data; + int ret, mode = 0; + + ret = regmap_read(rdev->regmap, info->conf, &data); + if (ret < 0) + return ret; + + switch (data & PV88090_BUCK1_MODE_MASK) { + case PV88090_BUCK_MODE_SYNC: + mode = REGULATOR_MODE_FAST; + break; + case PV88090_BUCK_MODE_AUTO: + mode = REGULATOR_MODE_NORMAL; + break; + case PV88090_BUCK_MODE_SLEEP: + mode = REGULATOR_MODE_STANDBY; + break; + } + + return mode; +} + +static int pv88090_buck_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct pv88090_regulator *info = rdev_get_drvdata(rdev); + int val = 0; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = PV88090_BUCK_MODE_SYNC; + break; + case REGULATOR_MODE_NORMAL: + val = PV88090_BUCK_MODE_AUTO; + break; + case REGULATOR_MODE_STANDBY: + val = PV88090_BUCK_MODE_SLEEP; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(rdev->regmap, info->conf, + PV88090_BUCK1_MODE_MASK, val); +} + +static int pv88090_set_current_limit(struct regulator_dev *rdev, int min, + int max) +{ + struct pv88090_regulator *info = rdev_get_drvdata(rdev); + int i; + + /* search for closest to maximum */ + for (i = info->n_current_limits - 1; i >= 0; i--) { + if (min <= info->current_limits[i] + && max >= info->current_limits[i]) { + return regmap_update_bits(rdev->regmap, + info->conf, + info->limit_mask, + i << PV88090_BUCK1_ILIM_SHIFT); + } + } + + return -EINVAL; +} + +static int pv88090_get_current_limit(struct regulator_dev *rdev) +{ + struct pv88090_regulator *info = rdev_get_drvdata(rdev); + unsigned int data; + int ret; + + ret = regmap_read(rdev->regmap, info->conf, &data); + if (ret < 0) + return ret; + + data = (data & info->limit_mask) >> PV88090_BUCK1_ILIM_SHIFT; + return info->current_limits[data]; +} + +static const struct regulator_ops pv88090_buck_ops = { + .get_mode = pv88090_buck_get_mode, + .set_mode = pv88090_buck_set_mode, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_current_limit = pv88090_set_current_limit, + .get_current_limit = pv88090_get_current_limit, +}; + +static const struct regulator_ops pv88090_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +#define PV88090_BUCK(chip, regl_name, min, step, max, limits_array) \ +{\ + .desc = {\ + .id = chip##_ID_##regl_name,\ + .name = __stringify(chip##_##regl_name),\ + .of_match = of_match_ptr(#regl_name),\ + .regulators_node = of_match_ptr("regulators"),\ + .type = REGULATOR_VOLTAGE,\ + .owner = THIS_MODULE,\ + .ops = &pv88090_buck_ops,\ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = ((max) - (min))/(step) + 1, \ + .enable_reg = PV88090_REG_##regl_name##_CONF0, \ + .enable_mask = PV88090_##regl_name##_EN, \ + .vsel_reg = PV88090_REG_##regl_name##_CONF0, \ + .vsel_mask = PV88090_V##regl_name##_MASK, \ + },\ + .current_limits = limits_array, \ + .n_current_limits = ARRAY_SIZE(limits_array), \ + .limit_mask = PV88090_##regl_name##_ILIM_MASK, \ + .conf = PV88090_REG_##regl_name##_CONF1, \ + .conf2 = PV88090_REG_##regl_name##_CONF2, \ +} + +#define PV88090_LDO(chip, regl_name, min, step, max) \ +{\ + .desc = {\ + .id = chip##_ID_##regl_name,\ + .name = __stringify(chip##_##regl_name),\ + .of_match = of_match_ptr(#regl_name),\ + .regulators_node = of_match_ptr("regulators"),\ + .type = REGULATOR_VOLTAGE,\ + .owner = THIS_MODULE,\ + .ops = &pv88090_ldo_ops,\ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = ((max) - (min))/(step) + 1, \ + .enable_reg = PV88090_REG_##regl_name##_CONT, \ + .enable_mask = PV88090_##regl_name##_EN, \ + .vsel_reg = PV88090_REG_##regl_name##_CONT, \ + .vsel_mask = PV88090_V##regl_name##_MASK, \ + },\ +} + +static struct pv88090_regulator pv88090_regulator_info[] = { + PV88090_BUCK(PV88090, BUCK1, 600000, 6250, 1393750, + pv88090_buck1_limits), + PV88090_BUCK(PV88090, BUCK2, 600000, 6250, 1393750, + pv88090_buck23_limits), + PV88090_BUCK(PV88090, BUCK3, 600000, 6250, 1393750, + pv88090_buck23_limits), + PV88090_LDO(PV88090, LDO1, 1200000, 50000, 4350000), + PV88090_LDO(PV88090, LDO2, 650000, 25000, 2225000), +}; + +static irqreturn_t pv88090_irq_handler(int irq, void *data) +{ + struct pv88090 *chip = data; + int i, reg_val, err, ret = IRQ_NONE; + + err = regmap_read(chip->regmap, PV88090_REG_EVENT_A, ®_val); + if (err < 0) + goto error_i2c; + + if (reg_val & PV88090_E_VDD_FLT) { + for (i = 0; i < PV88090_MAX_REGULATORS; i++) { + if (chip->rdev[i] != NULL) { + regulator_notifier_call_chain(chip->rdev[i], + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + } + } + + err = regmap_write(chip->regmap, PV88090_REG_EVENT_A, + PV88090_E_VDD_FLT); + if (err < 0) + goto error_i2c; + + ret = IRQ_HANDLED; + } + + if (reg_val & PV88090_E_OVER_TEMP) { + for (i = 0; i < PV88090_MAX_REGULATORS; i++) { + if (chip->rdev[i] != NULL) { + regulator_notifier_call_chain(chip->rdev[i], + REGULATOR_EVENT_OVER_TEMP, + NULL); + } + } + + err = regmap_write(chip->regmap, PV88090_REG_EVENT_A, + PV88090_E_OVER_TEMP); + if (err < 0) + goto error_i2c; + + ret = IRQ_HANDLED; + } + + return ret; + +error_i2c: + dev_err(chip->dev, "I2C error : %d\n", err); + return IRQ_NONE; +} + +/* + * I2C driver interface functions + */ +static int pv88090_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regulator_init_data *init_data = dev_get_platdata(&i2c->dev); + struct pv88090 *chip; + struct regulator_config config = { }; + int error, i, ret = 0; + unsigned int conf2, range, index; + + chip = devm_kzalloc(&i2c->dev, sizeof(struct pv88090), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &i2c->dev; + chip->regmap = devm_regmap_init_i2c(i2c, &pv88090_regmap_config); + if (IS_ERR(chip->regmap)) { + error = PTR_ERR(chip->regmap); + dev_err(chip->dev, "Failed to allocate register map: %d\n", + error); + return error; + } + + i2c_set_clientdata(i2c, chip); + + if (i2c->irq != 0) { + ret = regmap_write(chip->regmap, PV88090_REG_MASK_A, 0xFF); + if (ret < 0) { + dev_err(chip->dev, + "Failed to mask A reg: %d\n", ret); + return ret; + } + + ret = regmap_write(chip->regmap, PV88090_REG_MASK_B, 0xFF); + if (ret < 0) { + dev_err(chip->dev, + "Failed to mask B reg: %d\n", ret); + return ret; + } + + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + pv88090_irq_handler, + IRQF_TRIGGER_LOW|IRQF_ONESHOT, + "pv88090", chip); + if (ret != 0) { + dev_err(chip->dev, "Failed to request IRQ: %d\n", + i2c->irq); + return ret; + } + + ret = regmap_update_bits(chip->regmap, PV88090_REG_MASK_A, + PV88090_M_VDD_FLT | PV88090_M_OVER_TEMP, 0); + if (ret < 0) { + dev_err(chip->dev, + "Failed to update mask reg: %d\n", ret); + return ret; + } + + } else { + dev_warn(chip->dev, "No IRQ configured\n"); + } + + config.dev = chip->dev; + config.regmap = chip->regmap; + + for (i = 0; i < PV88090_MAX_REGULATORS; i++) { + if (init_data) + config.init_data = &init_data[i]; + + if (i == PV88090_ID_BUCK2 || i == PV88090_ID_BUCK3) { + ret = regmap_read(chip->regmap, + pv88090_regulator_info[i].conf2, &conf2); + if (ret < 0) + return ret; + + conf2 = (conf2 >> PV88090_BUCK_VDAC_RANGE_SHIFT) & + PV88090_BUCK_VDAC_RANGE_MASK; + + ret = regmap_read(chip->regmap, + PV88090_REG_BUCK_FOLD_RANGE, &range); + if (ret < 0) + return ret; + + range = (range >> + (PV88090_BUCK_VRANGE_GAIN_SHIFT + i - 1)) & + PV88090_BUCK_VRANGE_GAIN_MASK; + index = ((range << 1) | conf2); + if (index > PV88090_ID_BUCK3) { + dev_err(chip->dev, + "Invalid index(%d)\n", index); + return -EINVAL; + } + + pv88090_regulator_info[i].desc.min_uV + = pv88090_buck_vol[index].min_uV; + pv88090_regulator_info[i].desc.uV_step + = pv88090_buck_vol[index].uV_step; + pv88090_regulator_info[i].desc.n_voltages + = ((pv88090_buck_vol[index].max_uV) + - (pv88090_buck_vol[index].min_uV)) + /(pv88090_buck_vol[index].uV_step) + 1; + } + + config.driver_data = (void *)&pv88090_regulator_info[i]; + chip->rdev[i] = devm_regulator_register(chip->dev, + &pv88090_regulator_info[i].desc, &config); + if (IS_ERR(chip->rdev[i])) { + dev_err(chip->dev, + "Failed to register PV88090 regulator\n"); + return PTR_ERR(chip->rdev[i]); + } + } + + return 0; +} + +static const struct i2c_device_id pv88090_i2c_id[] = { + {"pv88090", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, pv88090_i2c_id); + +#ifdef CONFIG_OF +static const struct of_device_id pv88090_dt_ids[] = { + { .compatible = "pvs,pv88090", .data = &pv88090_i2c_id[0] }, + {}, +}; +MODULE_DEVICE_TABLE(of, pv88090_dt_ids); +#endif + +static struct i2c_driver pv88090_regulator_driver = { + .driver = { + .name = "pv88090", + .of_match_table = of_match_ptr(pv88090_dt_ids), + }, + .probe = pv88090_i2c_probe, + .id_table = pv88090_i2c_id, +}; + +module_i2c_driver(pv88090_regulator_driver); + +MODULE_AUTHOR("James Ban <James.Ban.opensource@diasemi.com>"); +MODULE_DESCRIPTION("Regulator device driver for Powerventure PV88090"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/pv88090-regulator.h b/drivers/regulator/pv88090-regulator.h new file mode 100644 index 000000000..62d902927 --- /dev/null +++ b/drivers/regulator/pv88090-regulator.h @@ -0,0 +1,98 @@ +/* + * pv88090-regulator.h - Regulator definitions for PV88090 + * Copyright (C) 2015 Powerventure Semiconductor Ltd. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PV88090_REGISTERS_H__ +#define __PV88090_REGISTERS_H__ + +/* System Control and Event Registers */ +#define PV88090_REG_EVENT_A 0x03 +#define PV88090_REG_MASK_A 0x06 +#define PV88090_REG_MASK_B 0x07 + +/* Regulator Registers */ +#define PV88090_REG_BUCK1_CONF0 0x18 +#define PV88090_REG_BUCK1_CONF1 0x19 +#define PV88090_REG_BUCK1_CONF2 0x1a +#define PV88090_REG_BUCK2_CONF0 0x1b +#define PV88090_REG_BUCK2_CONF1 0x1c +#define PV88090_REG_BUCK2_CONF2 0x58 +#define PV88090_REG_BUCK3_CONF0 0x1d +#define PV88090_REG_BUCK3_CONF1 0x1e +#define PV88090_REG_BUCK3_CONF2 0x5c + +#define PV88090_REG_LDO1_CONT 0x1f +#define PV88090_REG_LDO2_CONT 0x20 +#define PV88090_REG_LDO3_CONT 0x21 +#define PV88090_REG_BUCK_FOLD_RANGE 0x61 + +/* PV88090_REG_EVENT_A (addr=0x03) */ +#define PV88090_E_VDD_FLT 0x01 +#define PV88090_E_OVER_TEMP 0x02 + +/* PV88090_REG_MASK_A (addr=0x06) */ +#define PV88090_M_VDD_FLT 0x01 +#define PV88090_M_OVER_TEMP 0x02 + +/* PV88090_REG_BUCK1_CONF0 (addr=0x18) */ +#define PV88090_BUCK1_EN 0x80 +#define PV88090_VBUCK1_MASK 0x7F +/* PV88090_REG_BUCK2_CONF0 (addr=0x1b) */ +#define PV88090_BUCK2_EN 0x80 +#define PV88090_VBUCK2_MASK 0x7F +/* PV88090_REG_BUCK3_CONF0 (addr=0x1d) */ +#define PV88090_BUCK3_EN 0x80 +#define PV88090_VBUCK3_MASK 0x7F +/* PV88090_REG_LDO1_CONT (addr=0x1f) */ +#define PV88090_LDO1_EN 0x40 +#define PV88090_VLDO1_MASK 0x3F +/* PV88090_REG_LDO2_CONT (addr=0x20) */ +#define PV88090_LDO2_EN 0x40 +#define PV88090_VLDO2_MASK 0x3F + +/* PV88090_REG_BUCK1_CONF1 (addr=0x19) */ +#define PV88090_BUCK1_ILIM_SHIFT 2 +#define PV88090_BUCK1_ILIM_MASK 0x7C +#define PV88090_BUCK1_MODE_MASK 0x03 + +/* PV88090_REG_BUCK2_CONF1 (addr=0x1c) */ +#define PV88090_BUCK2_ILIM_SHIFT 2 +#define PV88090_BUCK2_ILIM_MASK 0x0C +#define PV88090_BUCK2_MODE_MASK 0x03 + +/* PV88090_REG_BUCK3_CONF1 (addr=0x1e) */ +#define PV88090_BUCK3_ILIM_SHIFT 2 +#define PV88090_BUCK3_ILIM_MASK 0x0C +#define PV88090_BUCK3_MODE_MASK 0x03 + +#define PV88090_BUCK_MODE_SLEEP 0x00 +#define PV88090_BUCK_MODE_AUTO 0x01 +#define PV88090_BUCK_MODE_SYNC 0x02 + +/* PV88090_REG_BUCK2_CONF2 (addr=0x58) */ +/* PV88090_REG_BUCK3_CONF2 (addr=0x5c) */ +#define PV88090_BUCK_VDAC_RANGE_SHIFT 7 +#define PV88090_BUCK_VDAC_RANGE_MASK 0x01 + +#define PV88090_BUCK_VDAC_RANGE_1 0x00 +#define PV88090_BUCK_VDAC_RANGE_2 0x01 + +/* PV88090_REG_BUCK_FOLD_RANGE (addr=0x61) */ +#define PV88090_BUCK_VRANGE_GAIN_SHIFT 3 +#define PV88090_BUCK_VRANGE_GAIN_MASK 0x01 + +#define PV88090_BUCK_VRANGE_GAIN_1 0x00 +#define PV88090_BUCK_VRANGE_GAIN_2 0x01 + +#endif /* __PV88090_REGISTERS_H__ */ diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c new file mode 100644 index 000000000..34f3b9778 --- /dev/null +++ b/drivers/regulator/pwm-regulator.c @@ -0,0 +1,418 @@ +/* + * Regulator driver for PWM Regulators + * + * Copyright (C) 2014 - STMicroelectronics Inc. + * + * Author: Lee Jones <lee.jones@linaro.org> + * + * 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/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pwm.h> +#include <linux/gpio/consumer.h> + +struct pwm_continuous_reg_data { + unsigned int min_uV_dutycycle; + unsigned int max_uV_dutycycle; + unsigned int dutycycle_unit; +}; + +struct pwm_regulator_data { + /* Shared */ + struct pwm_device *pwm; + + /* Voltage table */ + struct pwm_voltages *duty_cycle_table; + + /* Continuous mode info */ + struct pwm_continuous_reg_data continuous; + + /* regulator descriptor */ + struct regulator_desc desc; + + /* Regulator ops */ + struct regulator_ops ops; + + int state; + + /* Enable GPIO */ + struct gpio_desc *enb_gpio; +}; + +struct pwm_voltages { + unsigned int uV; + unsigned int dutycycle; +}; + +/** + * Voltage table call-backs + */ +static void pwm_regulator_init_state(struct regulator_dev *rdev) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + struct pwm_state pwm_state; + unsigned int dutycycle; + int i; + + pwm_get_state(drvdata->pwm, &pwm_state); + dutycycle = pwm_get_relative_duty_cycle(&pwm_state, 100); + + for (i = 0; i < rdev->desc->n_voltages; i++) { + if (dutycycle == drvdata->duty_cycle_table[i].dutycycle) { + drvdata->state = i; + return; + } + } +} + +static int pwm_regulator_get_voltage_sel(struct regulator_dev *rdev) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + + if (drvdata->state < 0) + pwm_regulator_init_state(rdev); + + return drvdata->state; +} + +static int pwm_regulator_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + struct pwm_state pstate; + int ret; + + pwm_init_state(drvdata->pwm, &pstate); + pwm_set_relative_duty_cycle(&pstate, + drvdata->duty_cycle_table[selector].dutycycle, 100); + + ret = pwm_apply_state(drvdata->pwm, &pstate); + if (ret) { + dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret); + return ret; + } + + drvdata->state = selector; + + return 0; +} + +static int pwm_regulator_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + + if (selector >= rdev->desc->n_voltages) + return -EINVAL; + + return drvdata->duty_cycle_table[selector].uV; +} + +static int pwm_regulator_enable(struct regulator_dev *dev) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(dev); + + gpiod_set_value_cansleep(drvdata->enb_gpio, 1); + + return pwm_enable(drvdata->pwm); +} + +static int pwm_regulator_disable(struct regulator_dev *dev) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(dev); + + pwm_disable(drvdata->pwm); + + gpiod_set_value_cansleep(drvdata->enb_gpio, 0); + + return 0; +} + +static int pwm_regulator_is_enabled(struct regulator_dev *dev) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(dev); + + if (drvdata->enb_gpio && !gpiod_get_value_cansleep(drvdata->enb_gpio)) + return false; + + return pwm_is_enabled(drvdata->pwm); +} + +static int pwm_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + unsigned int min_uV_duty = drvdata->continuous.min_uV_dutycycle; + unsigned int max_uV_duty = drvdata->continuous.max_uV_dutycycle; + unsigned int duty_unit = drvdata->continuous.dutycycle_unit; + int min_uV = rdev->constraints->min_uV; + int max_uV = rdev->constraints->max_uV; + int diff_uV = max_uV - min_uV; + struct pwm_state pstate; + unsigned int diff_duty; + unsigned int voltage; + + pwm_get_state(drvdata->pwm, &pstate); + + voltage = pwm_get_relative_duty_cycle(&pstate, duty_unit); + + /* + * The dutycycle for min_uV might be greater than the one for max_uV. + * This is happening when the user needs an inversed polarity, but the + * PWM device does not support inversing it in hardware. + */ + if (max_uV_duty < min_uV_duty) { + voltage = min_uV_duty - voltage; + diff_duty = min_uV_duty - max_uV_duty; + } else { + voltage = voltage - min_uV_duty; + diff_duty = max_uV_duty - min_uV_duty; + } + + voltage = DIV_ROUND_CLOSEST_ULL((u64)voltage * diff_uV, diff_duty); + + return voltage + min_uV; +} + +static int pwm_regulator_set_voltage(struct regulator_dev *rdev, + int req_min_uV, int req_max_uV, + unsigned int *selector) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + unsigned int min_uV_duty = drvdata->continuous.min_uV_dutycycle; + unsigned int max_uV_duty = drvdata->continuous.max_uV_dutycycle; + unsigned int duty_unit = drvdata->continuous.dutycycle_unit; + int min_uV = rdev->constraints->min_uV; + int max_uV = rdev->constraints->max_uV; + int diff_uV = max_uV - min_uV; + struct pwm_state pstate; + unsigned int diff_duty; + unsigned int dutycycle; + int ret; + + pwm_init_state(drvdata->pwm, &pstate); + + /* + * The dutycycle for min_uV might be greater than the one for max_uV. + * This is happening when the user needs an inversed polarity, but the + * PWM device does not support inversing it in hardware. + */ + if (max_uV_duty < min_uV_duty) + diff_duty = min_uV_duty - max_uV_duty; + else + diff_duty = max_uV_duty - min_uV_duty; + + dutycycle = DIV_ROUND_CLOSEST_ULL((u64)(req_min_uV - min_uV) * + diff_duty, + diff_uV); + + if (max_uV_duty < min_uV_duty) + dutycycle = min_uV_duty - dutycycle; + else + dutycycle = min_uV_duty + dutycycle; + + pwm_set_relative_duty_cycle(&pstate, dutycycle, duty_unit); + + ret = pwm_apply_state(drvdata->pwm, &pstate); + if (ret) { + dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret); + return ret; + } + + return 0; +} + +static struct regulator_ops pwm_regulator_voltage_table_ops = { + .set_voltage_sel = pwm_regulator_set_voltage_sel, + .get_voltage_sel = pwm_regulator_get_voltage_sel, + .list_voltage = pwm_regulator_list_voltage, + .map_voltage = regulator_map_voltage_iterate, + .enable = pwm_regulator_enable, + .disable = pwm_regulator_disable, + .is_enabled = pwm_regulator_is_enabled, +}; + +static struct regulator_ops pwm_regulator_voltage_continuous_ops = { + .get_voltage = pwm_regulator_get_voltage, + .set_voltage = pwm_regulator_set_voltage, + .enable = pwm_regulator_enable, + .disable = pwm_regulator_disable, + .is_enabled = pwm_regulator_is_enabled, +}; + +static struct regulator_desc pwm_regulator_desc = { + .name = "pwm-regulator", + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .supply_name = "pwm", +}; + +static int pwm_regulator_init_table(struct platform_device *pdev, + struct pwm_regulator_data *drvdata) +{ + struct device_node *np = pdev->dev.of_node; + struct pwm_voltages *duty_cycle_table; + unsigned int length = 0; + int ret; + + of_find_property(np, "voltage-table", &length); + + if ((length < sizeof(*duty_cycle_table)) || + (length % sizeof(*duty_cycle_table))) { + dev_err(&pdev->dev, "voltage-table length(%d) is invalid\n", + length); + return -EINVAL; + } + + duty_cycle_table = devm_kzalloc(&pdev->dev, length, GFP_KERNEL); + if (!duty_cycle_table) + return -ENOMEM; + + ret = of_property_read_u32_array(np, "voltage-table", + (u32 *)duty_cycle_table, + length / sizeof(u32)); + if (ret) { + dev_err(&pdev->dev, "Failed to read voltage-table: %d\n", ret); + return ret; + } + + drvdata->state = -ENOTRECOVERABLE; + drvdata->duty_cycle_table = duty_cycle_table; + memcpy(&drvdata->ops, &pwm_regulator_voltage_table_ops, + sizeof(drvdata->ops)); + drvdata->desc.ops = &drvdata->ops; + drvdata->desc.n_voltages = length / sizeof(*duty_cycle_table); + + return 0; +} + +static int pwm_regulator_init_continuous(struct platform_device *pdev, + struct pwm_regulator_data *drvdata) +{ + u32 dutycycle_range[2] = { 0, 100 }; + u32 dutycycle_unit = 100; + + memcpy(&drvdata->ops, &pwm_regulator_voltage_continuous_ops, + sizeof(drvdata->ops)); + drvdata->desc.ops = &drvdata->ops; + drvdata->desc.continuous_voltage_range = true; + + of_property_read_u32_array(pdev->dev.of_node, + "pwm-dutycycle-range", + dutycycle_range, 2); + of_property_read_u32(pdev->dev.of_node, "pwm-dutycycle-unit", + &dutycycle_unit); + + if (dutycycle_range[0] > dutycycle_unit || + dutycycle_range[1] > dutycycle_unit) + return -EINVAL; + + drvdata->continuous.dutycycle_unit = dutycycle_unit; + drvdata->continuous.min_uV_dutycycle = dutycycle_range[0]; + drvdata->continuous.max_uV_dutycycle = dutycycle_range[1]; + + return 0; +} + +static int pwm_regulator_probe(struct platform_device *pdev) +{ + const struct regulator_init_data *init_data; + struct pwm_regulator_data *drvdata; + struct regulator_dev *regulator; + struct regulator_config config = { }; + struct device_node *np = pdev->dev.of_node; + enum gpiod_flags gpio_flags; + int ret; + + if (!np) { + dev_err(&pdev->dev, "Device Tree node missing\n"); + return -EINVAL; + } + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + memcpy(&drvdata->desc, &pwm_regulator_desc, sizeof(drvdata->desc)); + + if (of_find_property(np, "voltage-table", NULL)) + ret = pwm_regulator_init_table(pdev, drvdata); + else + ret = pwm_regulator_init_continuous(pdev, drvdata); + if (ret) + return ret; + + init_data = of_get_regulator_init_data(&pdev->dev, np, + &drvdata->desc); + if (!init_data) + return -ENOMEM; + + config.of_node = np; + config.dev = &pdev->dev; + config.driver_data = drvdata; + config.init_data = init_data; + + drvdata->pwm = devm_pwm_get(&pdev->dev, NULL); + if (IS_ERR(drvdata->pwm)) { + ret = PTR_ERR(drvdata->pwm); + dev_err(&pdev->dev, "Failed to get PWM: %d\n", ret); + return ret; + } + + if (init_data->constraints.boot_on || init_data->constraints.always_on) + gpio_flags = GPIOD_OUT_HIGH; + else + gpio_flags = GPIOD_OUT_LOW; + drvdata->enb_gpio = devm_gpiod_get_optional(&pdev->dev, "enable", + gpio_flags); + if (IS_ERR(drvdata->enb_gpio)) { + ret = PTR_ERR(drvdata->enb_gpio); + dev_err(&pdev->dev, "Failed to get enable GPIO: %d\n", ret); + return ret; + } + + ret = pwm_adjust_config(drvdata->pwm); + if (ret) + return ret; + + regulator = devm_regulator_register(&pdev->dev, + &drvdata->desc, &config); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + dev_err(&pdev->dev, "Failed to register regulator %s: %d\n", + drvdata->desc.name, ret); + return ret; + } + + return 0; +} + +static const struct of_device_id pwm_of_match[] = { + { .compatible = "pwm-regulator" }, + { }, +}; +MODULE_DEVICE_TABLE(of, pwm_of_match); + +static struct platform_driver pwm_regulator_driver = { + .driver = { + .name = "pwm-regulator", + .of_match_table = of_match_ptr(pwm_of_match), + }, + .probe = pwm_regulator_probe, +}; + +module_platform_driver(pwm_regulator_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lee Jones <lee.jones@linaro.org>"); +MODULE_DESCRIPTION("PWM Regulator Driver"); +MODULE_ALIAS("platform:pwm-regulator"); diff --git a/drivers/regulator/qcom-rpmh-regulator.c b/drivers/regulator/qcom-rpmh-regulator.c new file mode 100644 index 000000000..9f27daebd --- /dev/null +++ b/drivers/regulator/qcom-rpmh-regulator.c @@ -0,0 +1,769 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018, The Linux Foundation. All rights reserved. + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> + +#include <soc/qcom/cmd-db.h> +#include <soc/qcom/rpmh.h> + +#include <dt-bindings/regulator/qcom,rpmh-regulator.h> + +/** + * enum rpmh_regulator_type - supported RPMh accelerator types + * %VRM: RPMh VRM accelerator which supports voting on enable, voltage, + * and mode of LDO, SMPS, and BOB type PMIC regulators. + * %XOB: RPMh XOB accelerator which supports voting on the enable state + * of PMIC regulators. + */ +enum rpmh_regulator_type { + VRM, + XOB, +}; + +#define RPMH_REGULATOR_REG_VRM_VOLTAGE 0x0 +#define RPMH_REGULATOR_REG_ENABLE 0x4 +#define RPMH_REGULATOR_REG_VRM_MODE 0x8 + +#define PMIC4_LDO_MODE_RETENTION 4 +#define PMIC4_LDO_MODE_LPM 5 +#define PMIC4_LDO_MODE_HPM 7 + +#define PMIC4_SMPS_MODE_RETENTION 4 +#define PMIC4_SMPS_MODE_PFM 5 +#define PMIC4_SMPS_MODE_AUTO 6 +#define PMIC4_SMPS_MODE_PWM 7 + +#define PMIC4_BOB_MODE_PASS 0 +#define PMIC4_BOB_MODE_PFM 1 +#define PMIC4_BOB_MODE_AUTO 2 +#define PMIC4_BOB_MODE_PWM 3 + +/** + * struct rpmh_vreg_hw_data - RPMh regulator hardware configurations + * @regulator_type: RPMh accelerator type used to manage this + * regulator + * @ops: Pointer to regulator ops callback structure + * @voltage_range: The single range of voltages supported by this + * PMIC regulator type + * @n_voltages: The number of unique voltage set points defined + * by voltage_range + * @hpm_min_load_uA: Minimum load current in microamps that requires + * high power mode (HPM) operation. This is used + * for LDO hardware type regulators only. + * @pmic_mode_map: Array indexed by regulator framework mode + * containing PMIC hardware modes. Must be large + * enough to index all framework modes supported + * by this regulator hardware type. + * @of_map_mode: Maps an RPMH_REGULATOR_MODE_* mode value defined + * in device tree to a regulator framework mode + */ +struct rpmh_vreg_hw_data { + enum rpmh_regulator_type regulator_type; + const struct regulator_ops *ops; + const struct regulator_linear_range voltage_range; + int n_voltages; + int hpm_min_load_uA; + const int *pmic_mode_map; + unsigned int (*of_map_mode)(unsigned int mode); +}; + +/** + * struct rpmh_vreg - individual RPMh regulator data structure encapsulating a + * single regulator device + * @dev: Device pointer for the top-level PMIC RPMh + * regulator parent device. This is used as a + * handle in RPMh write requests. + * @addr: Base address of the regulator resource within + * an RPMh accelerator + * @rdesc: Regulator descriptor + * @hw_data: PMIC regulator configuration data for this RPMh + * regulator + * @always_wait_for_ack: Boolean flag indicating if a request must always + * wait for an ACK from RPMh before continuing even + * if it corresponds to a strictly lower power + * state (e.g. enabled --> disabled). + * @enabled: Flag indicating if the regulator is enabled or + * not + * @bypassed: Boolean indicating if the regulator is in + * bypass (pass-through) mode or not. This is + * only used by BOB rpmh-regulator resources. + * @voltage_selector: Selector used for get_voltage_sel() and + * set_voltage_sel() callbacks + * @mode: RPMh VRM regulator current framework mode + */ +struct rpmh_vreg { + struct device *dev; + u32 addr; + struct regulator_desc rdesc; + const struct rpmh_vreg_hw_data *hw_data; + bool always_wait_for_ack; + + int enabled; + bool bypassed; + int voltage_selector; + unsigned int mode; +}; + +/** + * struct rpmh_vreg_init_data - initialization data for an RPMh regulator + * @name: Name for the regulator which also corresponds + * to the device tree subnode name of the regulator + * @resource_name: RPMh regulator resource name format string. + * This must include exactly one field: '%s' which + * is filled at run-time with the PMIC ID provided + * by device tree property qcom,pmic-id. Example: + * "ldo%s1" for RPMh resource "ldoa1". + * @supply_name: Parent supply regulator name + * @hw_data: Configuration data for this PMIC regulator type + */ +struct rpmh_vreg_init_data { + const char *name; + const char *resource_name; + const char *supply_name; + const struct rpmh_vreg_hw_data *hw_data; +}; + +/** + * rpmh_regulator_send_request() - send the request to RPMh + * @vreg: Pointer to the RPMh regulator + * @cmd: Pointer to the RPMh command to send + * @wait_for_ack: Boolean indicating if execution must wait until the + * request has been acknowledged as complete + * + * Return: 0 on success, errno on failure + */ +static int rpmh_regulator_send_request(struct rpmh_vreg *vreg, + struct tcs_cmd *cmd, bool wait_for_ack) +{ + int ret; + + if (wait_for_ack || vreg->always_wait_for_ack) + ret = rpmh_write(vreg->dev, RPMH_ACTIVE_ONLY_STATE, cmd, 1); + else + ret = rpmh_write_async(vreg->dev, RPMH_ACTIVE_ONLY_STATE, cmd, + 1); + + return ret; +} + +static int _rpmh_regulator_vrm_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector, bool wait_for_ack) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_VRM_VOLTAGE, + }; + int ret; + + /* VRM voltage control register is set with voltage in millivolts. */ + cmd.data = DIV_ROUND_UP(regulator_list_voltage_linear_range(rdev, + selector), 1000); + + ret = rpmh_regulator_send_request(vreg, &cmd, wait_for_ack); + if (!ret) + vreg->voltage_selector = selector; + + return ret; +} + +static int rpmh_regulator_vrm_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + + if (vreg->enabled == -EINVAL) { + /* + * Cache the voltage and send it later when the regulator is + * enabled or disabled. + */ + vreg->voltage_selector = selector; + return 0; + } + + return _rpmh_regulator_vrm_set_voltage_sel(rdev, selector, + selector > vreg->voltage_selector); +} + +static int rpmh_regulator_vrm_get_voltage_sel(struct regulator_dev *rdev) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + + return vreg->voltage_selector; +} + +static int rpmh_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + + return vreg->enabled; +} + +static int rpmh_regulator_set_enable_state(struct regulator_dev *rdev, + bool enable) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_ENABLE, + .data = enable, + }; + int ret; + + if (vreg->enabled == -EINVAL && + vreg->voltage_selector != -ENOTRECOVERABLE) { + ret = _rpmh_regulator_vrm_set_voltage_sel(rdev, + vreg->voltage_selector, true); + if (ret < 0) + return ret; + } + + ret = rpmh_regulator_send_request(vreg, &cmd, enable); + if (!ret) + vreg->enabled = enable; + + return ret; +} + +static int rpmh_regulator_enable(struct regulator_dev *rdev) +{ + return rpmh_regulator_set_enable_state(rdev, true); +} + +static int rpmh_regulator_disable(struct regulator_dev *rdev) +{ + return rpmh_regulator_set_enable_state(rdev, false); +} + +static int rpmh_regulator_vrm_set_mode_bypass(struct rpmh_vreg *vreg, + unsigned int mode, bool bypassed) +{ + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_VRM_MODE, + }; + int pmic_mode; + + if (mode > REGULATOR_MODE_STANDBY) + return -EINVAL; + + pmic_mode = vreg->hw_data->pmic_mode_map[mode]; + if (pmic_mode < 0) + return pmic_mode; + + if (bypassed) + cmd.data = PMIC4_BOB_MODE_PASS; + else + cmd.data = pmic_mode; + + return rpmh_regulator_send_request(vreg, &cmd, true); +} + +static int rpmh_regulator_vrm_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + int ret; + + if (mode == vreg->mode) + return 0; + + ret = rpmh_regulator_vrm_set_mode_bypass(vreg, mode, vreg->bypassed); + if (!ret) + vreg->mode = mode; + + return ret; +} + +static unsigned int rpmh_regulator_vrm_get_mode(struct regulator_dev *rdev) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + + return vreg->mode; +} + +/** + * rpmh_regulator_vrm_set_load() - set the regulator mode based upon the load + * current requested + * @rdev: Regulator device pointer for the rpmh-regulator + * @load_uA: Aggregated load current in microamps + * + * This function is used in the regulator_ops for VRM type RPMh regulator + * devices. + * + * Return: 0 on success, errno on failure + */ +static int rpmh_regulator_vrm_set_load(struct regulator_dev *rdev, int load_uA) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mode; + + if (load_uA >= vreg->hw_data->hpm_min_load_uA) + mode = REGULATOR_MODE_NORMAL; + else + mode = REGULATOR_MODE_IDLE; + + return rpmh_regulator_vrm_set_mode(rdev, mode); +} + +static int rpmh_regulator_vrm_set_bypass(struct regulator_dev *rdev, + bool enable) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + int ret; + + if (vreg->bypassed == enable) + return 0; + + ret = rpmh_regulator_vrm_set_mode_bypass(vreg, vreg->mode, enable); + if (!ret) + vreg->bypassed = enable; + + return ret; +} + +static int rpmh_regulator_vrm_get_bypass(struct regulator_dev *rdev, + bool *enable) +{ + struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); + + *enable = vreg->bypassed; + + return 0; +} + +static const struct regulator_ops rpmh_regulator_vrm_ops = { + .enable = rpmh_regulator_enable, + .disable = rpmh_regulator_disable, + .is_enabled = rpmh_regulator_is_enabled, + .set_voltage_sel = rpmh_regulator_vrm_set_voltage_sel, + .get_voltage_sel = rpmh_regulator_vrm_get_voltage_sel, + .list_voltage = regulator_list_voltage_linear_range, + .set_mode = rpmh_regulator_vrm_set_mode, + .get_mode = rpmh_regulator_vrm_get_mode, +}; + +static const struct regulator_ops rpmh_regulator_vrm_drms_ops = { + .enable = rpmh_regulator_enable, + .disable = rpmh_regulator_disable, + .is_enabled = rpmh_regulator_is_enabled, + .set_voltage_sel = rpmh_regulator_vrm_set_voltage_sel, + .get_voltage_sel = rpmh_regulator_vrm_get_voltage_sel, + .list_voltage = regulator_list_voltage_linear_range, + .set_mode = rpmh_regulator_vrm_set_mode, + .get_mode = rpmh_regulator_vrm_get_mode, + .set_load = rpmh_regulator_vrm_set_load, +}; + +static const struct regulator_ops rpmh_regulator_vrm_bypass_ops = { + .enable = rpmh_regulator_enable, + .disable = rpmh_regulator_disable, + .is_enabled = rpmh_regulator_is_enabled, + .set_voltage_sel = rpmh_regulator_vrm_set_voltage_sel, + .get_voltage_sel = rpmh_regulator_vrm_get_voltage_sel, + .list_voltage = regulator_list_voltage_linear_range, + .set_mode = rpmh_regulator_vrm_set_mode, + .get_mode = rpmh_regulator_vrm_get_mode, + .set_bypass = rpmh_regulator_vrm_set_bypass, + .get_bypass = rpmh_regulator_vrm_get_bypass, +}; + +static const struct regulator_ops rpmh_regulator_xob_ops = { + .enable = rpmh_regulator_enable, + .disable = rpmh_regulator_disable, + .is_enabled = rpmh_regulator_is_enabled, +}; + +/** + * rpmh_regulator_init_vreg() - initialize all attributes of an rpmh-regulator + * vreg: Pointer to the individual rpmh-regulator resource + * dev: Pointer to the top level rpmh-regulator PMIC device + * node: Pointer to the individual rpmh-regulator resource + * device node + * pmic_id: String used to identify the top level rpmh-regulator + * PMIC device on the board + * pmic_rpmh_data: Pointer to a null-terminated array of rpmh-regulator + * resources defined for the top level PMIC device + * + * Return: 0 on success, errno on failure + */ +static int rpmh_regulator_init_vreg(struct rpmh_vreg *vreg, struct device *dev, + struct device_node *node, const char *pmic_id, + const struct rpmh_vreg_init_data *pmic_rpmh_data) +{ + struct regulator_config reg_config = {}; + char rpmh_resource_name[20] = ""; + const struct rpmh_vreg_init_data *rpmh_data; + struct regulator_init_data *init_data; + struct regulator_dev *rdev; + int ret; + + vreg->dev = dev; + + for (rpmh_data = pmic_rpmh_data; rpmh_data->name; rpmh_data++) + if (!strcmp(rpmh_data->name, node->name)) + break; + + if (!rpmh_data->name) { + dev_err(dev, "Unknown regulator %s\n", node->name); + return -EINVAL; + } + + scnprintf(rpmh_resource_name, sizeof(rpmh_resource_name), + rpmh_data->resource_name, pmic_id); + + vreg->addr = cmd_db_read_addr(rpmh_resource_name); + if (!vreg->addr) { + dev_err(dev, "%s: could not find RPMh address for resource %s\n", + node->name, rpmh_resource_name); + return -ENODEV; + } + + vreg->rdesc.name = rpmh_data->name; + vreg->rdesc.supply_name = rpmh_data->supply_name; + vreg->hw_data = rpmh_data->hw_data; + + vreg->enabled = -EINVAL; + vreg->voltage_selector = -ENOTRECOVERABLE; + vreg->mode = REGULATOR_MODE_INVALID; + + if (rpmh_data->hw_data->n_voltages) { + vreg->rdesc.linear_ranges = &rpmh_data->hw_data->voltage_range; + vreg->rdesc.n_linear_ranges = 1; + vreg->rdesc.n_voltages = rpmh_data->hw_data->n_voltages; + } + + vreg->always_wait_for_ack = of_property_read_bool(node, + "qcom,always-wait-for-ack"); + + vreg->rdesc.owner = THIS_MODULE; + vreg->rdesc.type = REGULATOR_VOLTAGE; + vreg->rdesc.ops = vreg->hw_data->ops; + vreg->rdesc.of_map_mode = vreg->hw_data->of_map_mode; + + init_data = of_get_regulator_init_data(dev, node, &vreg->rdesc); + if (!init_data) + return -ENOMEM; + + if (rpmh_data->hw_data->regulator_type == XOB && + init_data->constraints.min_uV && + init_data->constraints.min_uV == init_data->constraints.max_uV) { + vreg->rdesc.fixed_uV = init_data->constraints.min_uV; + vreg->rdesc.n_voltages = 1; + } + + reg_config.dev = dev; + reg_config.init_data = init_data; + reg_config.of_node = node; + reg_config.driver_data = vreg; + + rdev = devm_regulator_register(dev, &vreg->rdesc, ®_config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(dev, "%s: devm_regulator_register() failed, ret=%d\n", + node->name, ret); + return ret; + } + + dev_dbg(dev, "%s regulator registered for RPMh resource %s @ 0x%05X\n", + node->name, rpmh_resource_name, vreg->addr); + + return 0; +} + +static const int pmic_mode_map_pmic4_ldo[REGULATOR_MODE_STANDBY + 1] = { + [REGULATOR_MODE_INVALID] = -EINVAL, + [REGULATOR_MODE_STANDBY] = PMIC4_LDO_MODE_RETENTION, + [REGULATOR_MODE_IDLE] = PMIC4_LDO_MODE_LPM, + [REGULATOR_MODE_NORMAL] = PMIC4_LDO_MODE_HPM, + [REGULATOR_MODE_FAST] = -EINVAL, +}; + +static unsigned int rpmh_regulator_pmic4_ldo_of_map_mode(unsigned int rpmh_mode) +{ + unsigned int mode; + + switch (rpmh_mode) { + case RPMH_REGULATOR_MODE_HPM: + mode = REGULATOR_MODE_NORMAL; + break; + case RPMH_REGULATOR_MODE_LPM: + mode = REGULATOR_MODE_IDLE; + break; + case RPMH_REGULATOR_MODE_RET: + mode = REGULATOR_MODE_STANDBY; + break; + default: + mode = REGULATOR_MODE_INVALID; + } + + return mode; +} + +static const int pmic_mode_map_pmic4_smps[REGULATOR_MODE_STANDBY + 1] = { + [REGULATOR_MODE_INVALID] = -EINVAL, + [REGULATOR_MODE_STANDBY] = PMIC4_SMPS_MODE_RETENTION, + [REGULATOR_MODE_IDLE] = PMIC4_SMPS_MODE_PFM, + [REGULATOR_MODE_NORMAL] = PMIC4_SMPS_MODE_AUTO, + [REGULATOR_MODE_FAST] = PMIC4_SMPS_MODE_PWM, +}; + +static unsigned int +rpmh_regulator_pmic4_smps_of_map_mode(unsigned int rpmh_mode) +{ + unsigned int mode; + + switch (rpmh_mode) { + case RPMH_REGULATOR_MODE_HPM: + mode = REGULATOR_MODE_FAST; + break; + case RPMH_REGULATOR_MODE_AUTO: + mode = REGULATOR_MODE_NORMAL; + break; + case RPMH_REGULATOR_MODE_LPM: + mode = REGULATOR_MODE_IDLE; + break; + case RPMH_REGULATOR_MODE_RET: + mode = REGULATOR_MODE_STANDBY; + break; + default: + mode = REGULATOR_MODE_INVALID; + } + + return mode; +} + +static const int pmic_mode_map_pmic4_bob[REGULATOR_MODE_STANDBY + 1] = { + [REGULATOR_MODE_INVALID] = -EINVAL, + [REGULATOR_MODE_STANDBY] = -EINVAL, + [REGULATOR_MODE_IDLE] = PMIC4_BOB_MODE_PFM, + [REGULATOR_MODE_NORMAL] = PMIC4_BOB_MODE_AUTO, + [REGULATOR_MODE_FAST] = PMIC4_BOB_MODE_PWM, +}; + +static unsigned int rpmh_regulator_pmic4_bob_of_map_mode(unsigned int rpmh_mode) +{ + unsigned int mode; + + switch (rpmh_mode) { + case RPMH_REGULATOR_MODE_HPM: + mode = REGULATOR_MODE_FAST; + break; + case RPMH_REGULATOR_MODE_AUTO: + mode = REGULATOR_MODE_NORMAL; + break; + case RPMH_REGULATOR_MODE_LPM: + mode = REGULATOR_MODE_IDLE; + break; + default: + mode = REGULATOR_MODE_INVALID; + } + + return mode; +} + +static const struct rpmh_vreg_hw_data pmic4_pldo = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_drms_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(1664000, 0, 255, 8000), + .n_voltages = 256, + .hpm_min_load_uA = 10000, + .pmic_mode_map = pmic_mode_map_pmic4_ldo, + .of_map_mode = rpmh_regulator_pmic4_ldo_of_map_mode, +}; + +static const struct rpmh_vreg_hw_data pmic4_pldo_lv = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_drms_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(1256000, 0, 127, 8000), + .n_voltages = 128, + .hpm_min_load_uA = 10000, + .pmic_mode_map = pmic_mode_map_pmic4_ldo, + .of_map_mode = rpmh_regulator_pmic4_ldo_of_map_mode, +}; + +static const struct rpmh_vreg_hw_data pmic4_nldo = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_drms_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(312000, 0, 127, 8000), + .n_voltages = 128, + .hpm_min_load_uA = 30000, + .pmic_mode_map = pmic_mode_map_pmic4_ldo, + .of_map_mode = rpmh_regulator_pmic4_ldo_of_map_mode, +}; + +static const struct rpmh_vreg_hw_data pmic4_hfsmps3 = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(320000, 0, 215, 8000), + .n_voltages = 216, + .pmic_mode_map = pmic_mode_map_pmic4_smps, + .of_map_mode = rpmh_regulator_pmic4_smps_of_map_mode, +}; + +static const struct rpmh_vreg_hw_data pmic4_ftsmps426 = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(320000, 0, 258, 4000), + .n_voltages = 259, + .pmic_mode_map = pmic_mode_map_pmic4_smps, + .of_map_mode = rpmh_regulator_pmic4_smps_of_map_mode, +}; + +static const struct rpmh_vreg_hw_data pmic4_bob = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_bypass_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(1824000, 0, 83, 32000), + .n_voltages = 84, + .pmic_mode_map = pmic_mode_map_pmic4_bob, + .of_map_mode = rpmh_regulator_pmic4_bob_of_map_mode, +}; + +static const struct rpmh_vreg_hw_data pmic4_lvs = { + .regulator_type = XOB, + .ops = &rpmh_regulator_xob_ops, + /* LVS hardware does not support voltage or mode configuration. */ +}; + +#define RPMH_VREG(_name, _resource_name, _hw_data, _supply_name) \ +{ \ + .name = _name, \ + .resource_name = _resource_name, \ + .hw_data = _hw_data, \ + .supply_name = _supply_name, \ +} + +static const struct rpmh_vreg_init_data pm8998_vreg_data[] = { + RPMH_VREG("smps1", "smp%s1", &pmic4_ftsmps426, "vdd-s1"), + RPMH_VREG("smps2", "smp%s2", &pmic4_ftsmps426, "vdd-s2"), + RPMH_VREG("smps3", "smp%s3", &pmic4_hfsmps3, "vdd-s3"), + RPMH_VREG("smps4", "smp%s4", &pmic4_hfsmps3, "vdd-s4"), + RPMH_VREG("smps5", "smp%s5", &pmic4_hfsmps3, "vdd-s5"), + RPMH_VREG("smps6", "smp%s6", &pmic4_ftsmps426, "vdd-s6"), + RPMH_VREG("smps7", "smp%s7", &pmic4_ftsmps426, "vdd-s7"), + RPMH_VREG("smps8", "smp%s8", &pmic4_ftsmps426, "vdd-s8"), + RPMH_VREG("smps9", "smp%s9", &pmic4_ftsmps426, "vdd-s9"), + RPMH_VREG("smps10", "smp%s10", &pmic4_ftsmps426, "vdd-s10"), + RPMH_VREG("smps11", "smp%s11", &pmic4_ftsmps426, "vdd-s11"), + RPMH_VREG("smps12", "smp%s12", &pmic4_ftsmps426, "vdd-s12"), + RPMH_VREG("smps13", "smp%s13", &pmic4_ftsmps426, "vdd-s13"), + RPMH_VREG("ldo1", "ldo%s1", &pmic4_nldo, "vdd-l1-l27"), + RPMH_VREG("ldo2", "ldo%s2", &pmic4_nldo, "vdd-l2-l8-l17"), + RPMH_VREG("ldo3", "ldo%s3", &pmic4_nldo, "vdd-l3-l11"), + RPMH_VREG("ldo4", "ldo%s4", &pmic4_nldo, "vdd-l4-l5"), + RPMH_VREG("ldo5", "ldo%s5", &pmic4_nldo, "vdd-l4-l5"), + RPMH_VREG("ldo6", "ldo%s6", &pmic4_pldo, "vdd-l6"), + RPMH_VREG("ldo7", "ldo%s7", &pmic4_pldo_lv, "vdd-l7-l12-l14-l15"), + RPMH_VREG("ldo8", "ldo%s8", &pmic4_nldo, "vdd-l2-l8-l17"), + RPMH_VREG("ldo9", "ldo%s9", &pmic4_pldo, "vdd-l9"), + RPMH_VREG("ldo10", "ldo%s10", &pmic4_pldo, "vdd-l10-l23-l25"), + RPMH_VREG("ldo11", "ldo%s11", &pmic4_nldo, "vdd-l3-l11"), + RPMH_VREG("ldo12", "ldo%s12", &pmic4_pldo_lv, "vdd-l7-l12-l14-l15"), + RPMH_VREG("ldo13", "ldo%s13", &pmic4_pldo, "vdd-l13-l19-l21"), + RPMH_VREG("ldo14", "ldo%s14", &pmic4_pldo_lv, "vdd-l7-l12-l14-l15"), + RPMH_VREG("ldo15", "ldo%s15", &pmic4_pldo_lv, "vdd-l7-l12-l14-l15"), + RPMH_VREG("ldo16", "ldo%s16", &pmic4_pldo, "vdd-l16-l28"), + RPMH_VREG("ldo17", "ldo%s17", &pmic4_nldo, "vdd-l2-l8-l17"), + RPMH_VREG("ldo18", "ldo%s18", &pmic4_pldo, "vdd-l18-l22"), + RPMH_VREG("ldo19", "ldo%s19", &pmic4_pldo, "vdd-l13-l19-l21"), + RPMH_VREG("ldo20", "ldo%s20", &pmic4_pldo, "vdd-l20-l24"), + RPMH_VREG("ldo21", "ldo%s21", &pmic4_pldo, "vdd-l13-l19-l21"), + RPMH_VREG("ldo22", "ldo%s22", &pmic4_pldo, "vdd-l18-l22"), + RPMH_VREG("ldo23", "ldo%s23", &pmic4_pldo, "vdd-l10-l23-l25"), + RPMH_VREG("ldo24", "ldo%s24", &pmic4_pldo, "vdd-l20-l24"), + RPMH_VREG("ldo25", "ldo%s25", &pmic4_pldo, "vdd-l10-l23-l25"), + RPMH_VREG("ldo26", "ldo%s26", &pmic4_nldo, "vdd-l26"), + RPMH_VREG("ldo27", "ldo%s27", &pmic4_nldo, "vdd-l1-l27"), + RPMH_VREG("ldo28", "ldo%s28", &pmic4_pldo, "vdd-l16-l28"), + RPMH_VREG("lvs1", "vs%s1", &pmic4_lvs, "vin-lvs-1-2"), + RPMH_VREG("lvs2", "vs%s2", &pmic4_lvs, "vin-lvs-1-2"), + {}, +}; + +static const struct rpmh_vreg_init_data pmi8998_vreg_data[] = { + RPMH_VREG("bob", "bob%s1", &pmic4_bob, "vdd-bob"), + {}, +}; + +static const struct rpmh_vreg_init_data pm8005_vreg_data[] = { + RPMH_VREG("smps1", "smp%s1", &pmic4_ftsmps426, "vdd-s1"), + RPMH_VREG("smps2", "smp%s2", &pmic4_ftsmps426, "vdd-s2"), + RPMH_VREG("smps3", "smp%s3", &pmic4_ftsmps426, "vdd-s3"), + RPMH_VREG("smps4", "smp%s4", &pmic4_ftsmps426, "vdd-s4"), + {}, +}; + +static int rpmh_regulator_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct rpmh_vreg_init_data *vreg_data; + struct device_node *node; + struct rpmh_vreg *vreg; + const char *pmic_id; + int ret; + + vreg_data = of_device_get_match_data(dev); + if (!vreg_data) + return -ENODEV; + + ret = of_property_read_string(dev->of_node, "qcom,pmic-id", &pmic_id); + if (ret < 0) { + dev_err(dev, "qcom,pmic-id missing in DT node\n"); + return ret; + } + + for_each_available_child_of_node(dev->of_node, node) { + vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL); + if (!vreg) { + of_node_put(node); + return -ENOMEM; + } + + ret = rpmh_regulator_init_vreg(vreg, dev, node, pmic_id, + vreg_data); + if (ret < 0) { + of_node_put(node); + return ret; + } + } + + return 0; +} + +static const struct of_device_id rpmh_regulator_match_table[] = { + { + .compatible = "qcom,pm8998-rpmh-regulators", + .data = pm8998_vreg_data, + }, + { + .compatible = "qcom,pmi8998-rpmh-regulators", + .data = pmi8998_vreg_data, + }, + { + .compatible = "qcom,pm8005-rpmh-regulators", + .data = pm8005_vreg_data, + }, + {} +}; +MODULE_DEVICE_TABLE(of, rpmh_regulator_match_table); + +static struct platform_driver rpmh_regulator_driver = { + .driver = { + .name = "qcom-rpmh-regulator", + .of_match_table = of_match_ptr(rpmh_regulator_match_table), + }, + .probe = rpmh_regulator_probe, +}; +module_platform_driver(rpmh_regulator_driver); + +MODULE_DESCRIPTION("Qualcomm RPMh regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/qcom_rpm-regulator.c b/drivers/regulator/qcom_rpm-regulator.c new file mode 100644 index 000000000..88dc0b0f0 --- /dev/null +++ b/drivers/regulator/qcom_rpm-regulator.c @@ -0,0 +1,1020 @@ +/* + * Copyright (c) 2014, Sony Mobile Communications AB. + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/qcom_rpm.h> + +#include <dt-bindings/mfd/qcom-rpm.h> + +#define MAX_REQUEST_LEN 2 + +struct request_member { + int word; + unsigned int mask; + int shift; +}; + +struct rpm_reg_parts { + struct request_member mV; /* used if voltage is in mV */ + struct request_member uV; /* used if voltage is in uV */ + struct request_member ip; /* peak current in mA */ + struct request_member pd; /* pull down enable */ + struct request_member ia; /* average current in mA */ + struct request_member fm; /* force mode */ + struct request_member pm; /* power mode */ + struct request_member pc; /* pin control */ + struct request_member pf; /* pin function */ + struct request_member enable_state; /* NCP and switch */ + struct request_member comp_mode; /* NCP */ + struct request_member freq; /* frequency: NCP and SMPS */ + struct request_member freq_clk_src; /* clock source: SMPS */ + struct request_member hpm; /* switch: control OCP and SS */ + int request_len; +}; + +#define FORCE_MODE_IS_2_BITS(reg) \ + (((reg)->parts->fm.mask >> (reg)->parts->fm.shift) == 3) + +struct qcom_rpm_reg { + struct qcom_rpm *rpm; + + struct mutex lock; + struct device *dev; + struct regulator_desc desc; + const struct rpm_reg_parts *parts; + + int resource; + u32 val[MAX_REQUEST_LEN]; + + int uV; + int is_enabled; + + bool supports_force_mode_auto; + bool supports_force_mode_bypass; +}; + +static const struct rpm_reg_parts rpm8660_ldo_parts = { + .request_len = 2, + .mV = { 0, 0x00000FFF, 0 }, + .ip = { 0, 0x00FFF000, 12 }, + .fm = { 0, 0x03000000, 24 }, + .pc = { 0, 0x3C000000, 26 }, + .pf = { 0, 0xC0000000, 30 }, + .pd = { 1, 0x00000001, 0 }, + .ia = { 1, 0x00001FFE, 1 }, +}; + +static const struct rpm_reg_parts rpm8660_smps_parts = { + .request_len = 2, + .mV = { 0, 0x00000FFF, 0 }, + .ip = { 0, 0x00FFF000, 12 }, + .fm = { 0, 0x03000000, 24 }, + .pc = { 0, 0x3C000000, 26 }, + .pf = { 0, 0xC0000000, 30 }, + .pd = { 1, 0x00000001, 0 }, + .ia = { 1, 0x00001FFE, 1 }, + .freq = { 1, 0x001FE000, 13 }, + .freq_clk_src = { 1, 0x00600000, 21 }, +}; + +static const struct rpm_reg_parts rpm8660_switch_parts = { + .request_len = 1, + .enable_state = { 0, 0x00000001, 0 }, + .pd = { 0, 0x00000002, 1 }, + .pc = { 0, 0x0000003C, 2 }, + .pf = { 0, 0x000000C0, 6 }, + .hpm = { 0, 0x00000300, 8 }, +}; + +static const struct rpm_reg_parts rpm8660_ncp_parts = { + .request_len = 1, + .mV = { 0, 0x00000FFF, 0 }, + .enable_state = { 0, 0x00001000, 12 }, + .comp_mode = { 0, 0x00002000, 13 }, + .freq = { 0, 0x003FC000, 14 }, +}; + +static const struct rpm_reg_parts rpm8960_ldo_parts = { + .request_len = 2, + .uV = { 0, 0x007FFFFF, 0 }, + .pd = { 0, 0x00800000, 23 }, + .pc = { 0, 0x0F000000, 24 }, + .pf = { 0, 0xF0000000, 28 }, + .ip = { 1, 0x000003FF, 0 }, + .ia = { 1, 0x000FFC00, 10 }, + .fm = { 1, 0x00700000, 20 }, +}; + +static const struct rpm_reg_parts rpm8960_smps_parts = { + .request_len = 2, + .uV = { 0, 0x007FFFFF, 0 }, + .pd = { 0, 0x00800000, 23 }, + .pc = { 0, 0x0F000000, 24 }, + .pf = { 0, 0xF0000000, 28 }, + .ip = { 1, 0x000003FF, 0 }, + .ia = { 1, 0x000FFC00, 10 }, + .fm = { 1, 0x00700000, 20 }, + .pm = { 1, 0x00800000, 23 }, + .freq = { 1, 0x1F000000, 24 }, + .freq_clk_src = { 1, 0x60000000, 29 }, +}; + +static const struct rpm_reg_parts rpm8960_switch_parts = { + .request_len = 1, + .enable_state = { 0, 0x00000001, 0 }, + .pd = { 0, 0x00000002, 1 }, + .pc = { 0, 0x0000003C, 2 }, + .pf = { 0, 0x000003C0, 6 }, + .hpm = { 0, 0x00000C00, 10 }, +}; + +static const struct rpm_reg_parts rpm8960_ncp_parts = { + .request_len = 1, + .uV = { 0, 0x007FFFFF, 0 }, + .enable_state = { 0, 0x00800000, 23 }, + .comp_mode = { 0, 0x01000000, 24 }, + .freq = { 0, 0x3E000000, 25 }, +}; + +/* + * Physically available PMIC regulator voltage ranges + */ +static const struct regulator_linear_range pldo_ranges[] = { + REGULATOR_LINEAR_RANGE( 750000, 0, 59, 12500), + REGULATOR_LINEAR_RANGE(1500000, 60, 123, 25000), + REGULATOR_LINEAR_RANGE(3100000, 124, 160, 50000), +}; + +static const struct regulator_linear_range nldo_ranges[] = { + REGULATOR_LINEAR_RANGE( 750000, 0, 63, 12500), +}; + +static const struct regulator_linear_range nldo1200_ranges[] = { + REGULATOR_LINEAR_RANGE( 375000, 0, 59, 6250), + REGULATOR_LINEAR_RANGE( 750000, 60, 123, 12500), +}; + +static const struct regulator_linear_range smps_ranges[] = { + REGULATOR_LINEAR_RANGE( 375000, 0, 29, 12500), + REGULATOR_LINEAR_RANGE( 750000, 30, 89, 12500), + REGULATOR_LINEAR_RANGE(1500000, 90, 153, 25000), +}; + +static const struct regulator_linear_range ftsmps_ranges[] = { + REGULATOR_LINEAR_RANGE( 350000, 0, 6, 50000), + REGULATOR_LINEAR_RANGE( 700000, 7, 63, 12500), + REGULATOR_LINEAR_RANGE(1500000, 64, 100, 50000), +}; + +static const struct regulator_linear_range smb208_ranges[] = { + REGULATOR_LINEAR_RANGE( 375000, 0, 29, 12500), + REGULATOR_LINEAR_RANGE( 750000, 30, 89, 12500), + REGULATOR_LINEAR_RANGE(1500000, 90, 153, 25000), + REGULATOR_LINEAR_RANGE(3100000, 154, 234, 25000), +}; + +static const struct regulator_linear_range ncp_ranges[] = { + REGULATOR_LINEAR_RANGE(1500000, 0, 31, 50000), +}; + +static int rpm_reg_write(struct qcom_rpm_reg *vreg, + const struct request_member *req, + const int value) +{ + if (WARN_ON((value << req->shift) & ~req->mask)) + return -EINVAL; + + vreg->val[req->word] &= ~req->mask; + vreg->val[req->word] |= value << req->shift; + + return qcom_rpm_write(vreg->rpm, + QCOM_RPM_ACTIVE_STATE, + vreg->resource, + vreg->val, + vreg->parts->request_len); +} + +static int rpm_reg_set_mV_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_reg_parts *parts = vreg->parts; + const struct request_member *req = &parts->mV; + int ret = 0; + int uV; + + if (req->mask == 0) + return -EINVAL; + + uV = regulator_list_voltage_linear_range(rdev, selector); + if (uV < 0) + return uV; + + mutex_lock(&vreg->lock); + if (vreg->is_enabled) + ret = rpm_reg_write(vreg, req, uV / 1000); + + if (!ret) + vreg->uV = uV; + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_set_uV_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_reg_parts *parts = vreg->parts; + const struct request_member *req = &parts->uV; + int ret = 0; + int uV; + + if (req->mask == 0) + return -EINVAL; + + uV = regulator_list_voltage_linear_range(rdev, selector); + if (uV < 0) + return uV; + + mutex_lock(&vreg->lock); + if (vreg->is_enabled) + ret = rpm_reg_write(vreg, req, uV); + + if (!ret) + vreg->uV = uV; + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_get_voltage(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + + return vreg->uV; +} + +static int rpm_reg_mV_enable(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_reg_parts *parts = vreg->parts; + const struct request_member *req = &parts->mV; + int ret; + + if (req->mask == 0) + return -EINVAL; + + mutex_lock(&vreg->lock); + ret = rpm_reg_write(vreg, req, vreg->uV / 1000); + if (!ret) + vreg->is_enabled = 1; + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_uV_enable(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_reg_parts *parts = vreg->parts; + const struct request_member *req = &parts->uV; + int ret; + + if (req->mask == 0) + return -EINVAL; + + mutex_lock(&vreg->lock); + ret = rpm_reg_write(vreg, req, vreg->uV); + if (!ret) + vreg->is_enabled = 1; + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_switch_enable(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_reg_parts *parts = vreg->parts; + const struct request_member *req = &parts->enable_state; + int ret; + + if (req->mask == 0) + return -EINVAL; + + mutex_lock(&vreg->lock); + ret = rpm_reg_write(vreg, req, 1); + if (!ret) + vreg->is_enabled = 1; + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_mV_disable(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_reg_parts *parts = vreg->parts; + const struct request_member *req = &parts->mV; + int ret; + + if (req->mask == 0) + return -EINVAL; + + mutex_lock(&vreg->lock); + ret = rpm_reg_write(vreg, req, 0); + if (!ret) + vreg->is_enabled = 0; + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_uV_disable(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_reg_parts *parts = vreg->parts; + const struct request_member *req = &parts->uV; + int ret; + + if (req->mask == 0) + return -EINVAL; + + mutex_lock(&vreg->lock); + ret = rpm_reg_write(vreg, req, 0); + if (!ret) + vreg->is_enabled = 0; + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_switch_disable(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_reg_parts *parts = vreg->parts; + const struct request_member *req = &parts->enable_state; + int ret; + + if (req->mask == 0) + return -EINVAL; + + mutex_lock(&vreg->lock); + ret = rpm_reg_write(vreg, req, 0); + if (!ret) + vreg->is_enabled = 0; + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_is_enabled(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + + return vreg->is_enabled; +} + +static int rpm_reg_set_load(struct regulator_dev *rdev, int load_uA) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_reg_parts *parts = vreg->parts; + const struct request_member *req = &parts->ia; + int load_mA = load_uA / 1000; + int max_mA = req->mask >> req->shift; + int ret; + + if (req->mask == 0) + return -EINVAL; + + if (load_mA > max_mA) + load_mA = max_mA; + + mutex_lock(&vreg->lock); + ret = rpm_reg_write(vreg, req, load_mA); + mutex_unlock(&vreg->lock); + + return ret; +} + +static struct regulator_ops uV_ops = { + .list_voltage = regulator_list_voltage_linear_range, + + .set_voltage_sel = rpm_reg_set_uV_sel, + .get_voltage = rpm_reg_get_voltage, + + .enable = rpm_reg_uV_enable, + .disable = rpm_reg_uV_disable, + .is_enabled = rpm_reg_is_enabled, + + .set_load = rpm_reg_set_load, +}; + +static struct regulator_ops mV_ops = { + .list_voltage = regulator_list_voltage_linear_range, + + .set_voltage_sel = rpm_reg_set_mV_sel, + .get_voltage = rpm_reg_get_voltage, + + .enable = rpm_reg_mV_enable, + .disable = rpm_reg_mV_disable, + .is_enabled = rpm_reg_is_enabled, + + .set_load = rpm_reg_set_load, +}; + +static struct regulator_ops switch_ops = { + .enable = rpm_reg_switch_enable, + .disable = rpm_reg_switch_disable, + .is_enabled = rpm_reg_is_enabled, +}; + +/* + * PM8018 regulators + */ +static const struct qcom_rpm_reg pm8018_pldo = { + .desc.linear_ranges = pldo_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(pldo_ranges), + .desc.n_voltages = 161, + .desc.ops = &uV_ops, + .parts = &rpm8960_ldo_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = false, +}; + +static const struct qcom_rpm_reg pm8018_nldo = { + .desc.linear_ranges = nldo_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(nldo_ranges), + .desc.n_voltages = 64, + .desc.ops = &uV_ops, + .parts = &rpm8960_ldo_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = false, +}; + +static const struct qcom_rpm_reg pm8018_smps = { + .desc.linear_ranges = smps_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(smps_ranges), + .desc.n_voltages = 154, + .desc.ops = &uV_ops, + .parts = &rpm8960_smps_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = false, +}; + +static const struct qcom_rpm_reg pm8018_switch = { + .desc.ops = &switch_ops, + .parts = &rpm8960_switch_parts, +}; + +/* + * PM8058 regulators + */ +static const struct qcom_rpm_reg pm8058_pldo = { + .desc.linear_ranges = pldo_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(pldo_ranges), + .desc.n_voltages = 161, + .desc.ops = &mV_ops, + .parts = &rpm8660_ldo_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = false, +}; + +static const struct qcom_rpm_reg pm8058_nldo = { + .desc.linear_ranges = nldo_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(nldo_ranges), + .desc.n_voltages = 64, + .desc.ops = &mV_ops, + .parts = &rpm8660_ldo_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = false, +}; + +static const struct qcom_rpm_reg pm8058_smps = { + .desc.linear_ranges = smps_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(smps_ranges), + .desc.n_voltages = 154, + .desc.ops = &mV_ops, + .parts = &rpm8660_smps_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = false, +}; + +static const struct qcom_rpm_reg pm8058_ncp = { + .desc.linear_ranges = ncp_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(ncp_ranges), + .desc.n_voltages = 32, + .desc.ops = &mV_ops, + .parts = &rpm8660_ncp_parts, +}; + +static const struct qcom_rpm_reg pm8058_switch = { + .desc.ops = &switch_ops, + .parts = &rpm8660_switch_parts, +}; + +/* + * PM8901 regulators + */ +static const struct qcom_rpm_reg pm8901_pldo = { + .desc.linear_ranges = pldo_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(pldo_ranges), + .desc.n_voltages = 161, + .desc.ops = &mV_ops, + .parts = &rpm8660_ldo_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = true, +}; + +static const struct qcom_rpm_reg pm8901_nldo = { + .desc.linear_ranges = nldo_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(nldo_ranges), + .desc.n_voltages = 64, + .desc.ops = &mV_ops, + .parts = &rpm8660_ldo_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = true, +}; + +static const struct qcom_rpm_reg pm8901_ftsmps = { + .desc.linear_ranges = ftsmps_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(ftsmps_ranges), + .desc.n_voltages = 101, + .desc.ops = &mV_ops, + .parts = &rpm8660_smps_parts, + .supports_force_mode_auto = true, + .supports_force_mode_bypass = false, +}; + +static const struct qcom_rpm_reg pm8901_switch = { + .desc.ops = &switch_ops, + .parts = &rpm8660_switch_parts, +}; + +/* + * PM8921 regulators + */ +static const struct qcom_rpm_reg pm8921_pldo = { + .desc.linear_ranges = pldo_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(pldo_ranges), + .desc.n_voltages = 161, + .desc.ops = &uV_ops, + .parts = &rpm8960_ldo_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = true, +}; + +static const struct qcom_rpm_reg pm8921_nldo = { + .desc.linear_ranges = nldo_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(nldo_ranges), + .desc.n_voltages = 64, + .desc.ops = &uV_ops, + .parts = &rpm8960_ldo_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = true, +}; + +static const struct qcom_rpm_reg pm8921_nldo1200 = { + .desc.linear_ranges = nldo1200_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(nldo1200_ranges), + .desc.n_voltages = 124, + .desc.ops = &uV_ops, + .parts = &rpm8960_ldo_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = true, +}; + +static const struct qcom_rpm_reg pm8921_smps = { + .desc.linear_ranges = smps_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(smps_ranges), + .desc.n_voltages = 154, + .desc.ops = &uV_ops, + .parts = &rpm8960_smps_parts, + .supports_force_mode_auto = true, + .supports_force_mode_bypass = false, +}; + +static const struct qcom_rpm_reg pm8921_ftsmps = { + .desc.linear_ranges = ftsmps_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(ftsmps_ranges), + .desc.n_voltages = 101, + .desc.ops = &uV_ops, + .parts = &rpm8960_smps_parts, + .supports_force_mode_auto = true, + .supports_force_mode_bypass = false, +}; + +static const struct qcom_rpm_reg pm8921_ncp = { + .desc.linear_ranges = ncp_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(ncp_ranges), + .desc.n_voltages = 32, + .desc.ops = &uV_ops, + .parts = &rpm8960_ncp_parts, +}; + +static const struct qcom_rpm_reg pm8921_switch = { + .desc.ops = &switch_ops, + .parts = &rpm8960_switch_parts, +}; + +static const struct qcom_rpm_reg smb208_smps = { + .desc.linear_ranges = smb208_ranges, + .desc.n_linear_ranges = ARRAY_SIZE(smb208_ranges), + .desc.n_voltages = 235, + .desc.ops = &uV_ops, + .parts = &rpm8960_smps_parts, + .supports_force_mode_auto = false, + .supports_force_mode_bypass = false, +}; + +static int rpm_reg_set(struct qcom_rpm_reg *vreg, + const struct request_member *req, + const int value) +{ + if (req->mask == 0 || (value << req->shift) & ~req->mask) + return -EINVAL; + + vreg->val[req->word] &= ~req->mask; + vreg->val[req->word] |= value << req->shift; + + return 0; +} + +static int rpm_reg_of_parse_freq(struct device *dev, + struct device_node *node, + struct qcom_rpm_reg *vreg) +{ + static const int freq_table[] = { + 19200000, 9600000, 6400000, 4800000, 3840000, 3200000, 2740000, + 2400000, 2130000, 1920000, 1750000, 1600000, 1480000, 1370000, + 1280000, 1200000, + + }; + const char *key; + u32 freq; + int ret; + int i; + + key = "qcom,switch-mode-frequency"; + ret = of_property_read_u32(node, key, &freq); + if (ret) { + dev_err(dev, "regulator requires %s property\n", key); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + if (freq == freq_table[i]) { + rpm_reg_set(vreg, &vreg->parts->freq, i + 1); + return 0; + } + } + + dev_err(dev, "invalid frequency %d\n", freq); + return -EINVAL; +} + +static int rpm_reg_of_parse(struct device_node *node, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct qcom_rpm_reg *vreg = config->driver_data; + struct device *dev = config->dev; + const char *key; + u32 force_mode; + bool pwm; + u32 val; + int ret; + + key = "bias-pull-down"; + if (of_property_read_bool(node, key)) { + ret = rpm_reg_set(vreg, &vreg->parts->pd, 1); + if (ret) { + dev_err(dev, "%s is invalid", key); + return ret; + } + } + + if (vreg->parts->freq.mask) { + ret = rpm_reg_of_parse_freq(dev, node, vreg); + if (ret < 0) + return ret; + } + + if (vreg->parts->pm.mask) { + key = "qcom,power-mode-hysteretic"; + pwm = !of_property_read_bool(node, key); + + ret = rpm_reg_set(vreg, &vreg->parts->pm, pwm); + if (ret) { + dev_err(dev, "failed to set power mode\n"); + return ret; + } + } + + if (vreg->parts->fm.mask) { + force_mode = -1; + + key = "qcom,force-mode"; + ret = of_property_read_u32(node, key, &val); + if (ret == -EINVAL) { + val = QCOM_RPM_FORCE_MODE_NONE; + } else if (ret < 0) { + dev_err(dev, "failed to read %s\n", key); + return ret; + } + + /* + * If force-mode is encoded as 2 bits then the + * possible register values are: + * NONE, LPM, HPM + * otherwise: + * NONE, LPM, AUTO, HPM, BYPASS + */ + switch (val) { + case QCOM_RPM_FORCE_MODE_NONE: + force_mode = 0; + break; + case QCOM_RPM_FORCE_MODE_LPM: + force_mode = 1; + break; + case QCOM_RPM_FORCE_MODE_HPM: + if (FORCE_MODE_IS_2_BITS(vreg)) + force_mode = 2; + else + force_mode = 3; + break; + case QCOM_RPM_FORCE_MODE_AUTO: + if (vreg->supports_force_mode_auto) + force_mode = 2; + break; + case QCOM_RPM_FORCE_MODE_BYPASS: + if (vreg->supports_force_mode_bypass) + force_mode = 4; + break; + } + + if (force_mode == -1) { + dev_err(dev, "invalid force mode\n"); + return -EINVAL; + } + + ret = rpm_reg_set(vreg, &vreg->parts->fm, force_mode); + if (ret) { + dev_err(dev, "failed to set force mode\n"); + return ret; + } + } + + return 0; +} + +struct rpm_regulator_data { + const char *name; + int resource; + const struct qcom_rpm_reg *template; + const char *supply; +}; + +static const struct rpm_regulator_data rpm_pm8018_regulators[] = { + { "s1", QCOM_RPM_PM8018_SMPS1, &pm8018_smps, "vdd_s1" }, + { "s2", QCOM_RPM_PM8018_SMPS2, &pm8018_smps, "vdd_s2" }, + { "s3", QCOM_RPM_PM8018_SMPS3, &pm8018_smps, "vdd_s3" }, + { "s4", QCOM_RPM_PM8018_SMPS4, &pm8018_smps, "vdd_s4" }, + { "s5", QCOM_RPM_PM8018_SMPS5, &pm8018_smps, "vdd_s5" }, + + { "l2", QCOM_RPM_PM8018_LDO2, &pm8018_pldo, "vdd_l2" }, + { "l3", QCOM_RPM_PM8018_LDO3, &pm8018_pldo, "vdd_l3" }, + { "l4", QCOM_RPM_PM8018_LDO4, &pm8018_pldo, "vdd_l4" }, + { "l5", QCOM_RPM_PM8018_LDO5, &pm8018_pldo, "vdd_l5" }, + { "l6", QCOM_RPM_PM8018_LDO6, &pm8018_pldo, "vdd_l7" }, + { "l7", QCOM_RPM_PM8018_LDO7, &pm8018_pldo, "vdd_l7" }, + { "l8", QCOM_RPM_PM8018_LDO8, &pm8018_nldo, "vdd_l8" }, + { "l9", QCOM_RPM_PM8018_LDO9, &pm8921_nldo1200, + "vdd_l9_l10_l11_l12" }, + { "l10", QCOM_RPM_PM8018_LDO10, &pm8018_nldo, "vdd_l9_l10_l11_l12" }, + { "l11", QCOM_RPM_PM8018_LDO11, &pm8018_nldo, "vdd_l9_l10_l11_l12" }, + { "l12", QCOM_RPM_PM8018_LDO12, &pm8018_nldo, "vdd_l9_l10_l11_l12" }, + { "l14", QCOM_RPM_PM8018_LDO14, &pm8018_pldo, "vdd_l14" }, + + { "lvs1", QCOM_RPM_PM8018_LVS1, &pm8018_switch, "lvs1_in" }, + + { } +}; + +static const struct rpm_regulator_data rpm_pm8058_regulators[] = { + { "l0", QCOM_RPM_PM8058_LDO0, &pm8058_nldo, "vdd_l0_l1_lvs" }, + { "l1", QCOM_RPM_PM8058_LDO1, &pm8058_nldo, "vdd_l0_l1_lvs" }, + { "l2", QCOM_RPM_PM8058_LDO2, &pm8058_pldo, "vdd_l2_l11_l12" }, + { "l3", QCOM_RPM_PM8058_LDO3, &pm8058_pldo, "vdd_l3_l4_l5" }, + { "l4", QCOM_RPM_PM8058_LDO4, &pm8058_pldo, "vdd_l3_l4_l5" }, + { "l5", QCOM_RPM_PM8058_LDO5, &pm8058_pldo, "vdd_l3_l4_l5" }, + { "l6", QCOM_RPM_PM8058_LDO6, &pm8058_pldo, "vdd_l6_l7" }, + { "l7", QCOM_RPM_PM8058_LDO7, &pm8058_pldo, "vdd_l6_l7" }, + { "l8", QCOM_RPM_PM8058_LDO8, &pm8058_pldo, "vdd_l8" }, + { "l9", QCOM_RPM_PM8058_LDO9, &pm8058_pldo, "vdd_l9" }, + { "l10", QCOM_RPM_PM8058_LDO10, &pm8058_pldo, "vdd_l10" }, + { "l11", QCOM_RPM_PM8058_LDO11, &pm8058_pldo, "vdd_l2_l11_l12" }, + { "l12", QCOM_RPM_PM8058_LDO12, &pm8058_pldo, "vdd_l2_l11_l12" }, + { "l13", QCOM_RPM_PM8058_LDO13, &pm8058_pldo, "vdd_l13_l16" }, + { "l14", QCOM_RPM_PM8058_LDO14, &pm8058_pldo, "vdd_l14_l15" }, + { "l15", QCOM_RPM_PM8058_LDO15, &pm8058_pldo, "vdd_l14_l15" }, + { "l16", QCOM_RPM_PM8058_LDO16, &pm8058_pldo, "vdd_l13_l16" }, + { "l17", QCOM_RPM_PM8058_LDO17, &pm8058_pldo, "vdd_l17_l18" }, + { "l18", QCOM_RPM_PM8058_LDO18, &pm8058_pldo, "vdd_l17_l18" }, + { "l19", QCOM_RPM_PM8058_LDO19, &pm8058_pldo, "vdd_l19_l20" }, + { "l20", QCOM_RPM_PM8058_LDO20, &pm8058_pldo, "vdd_l19_l20" }, + { "l21", QCOM_RPM_PM8058_LDO21, &pm8058_nldo, "vdd_l21" }, + { "l22", QCOM_RPM_PM8058_LDO22, &pm8058_nldo, "vdd_l22" }, + { "l23", QCOM_RPM_PM8058_LDO23, &pm8058_nldo, "vdd_l23_l24_l25" }, + { "l24", QCOM_RPM_PM8058_LDO24, &pm8058_nldo, "vdd_l23_l24_l25" }, + { "l25", QCOM_RPM_PM8058_LDO25, &pm8058_nldo, "vdd_l23_l24_l25" }, + + { "s0", QCOM_RPM_PM8058_SMPS0, &pm8058_smps, "vdd_s0" }, + { "s1", QCOM_RPM_PM8058_SMPS1, &pm8058_smps, "vdd_s1" }, + { "s2", QCOM_RPM_PM8058_SMPS2, &pm8058_smps, "vdd_s2" }, + { "s3", QCOM_RPM_PM8058_SMPS3, &pm8058_smps, "vdd_s3" }, + { "s4", QCOM_RPM_PM8058_SMPS4, &pm8058_smps, "vdd_s4" }, + + { "lvs0", QCOM_RPM_PM8058_LVS0, &pm8058_switch, "vdd_l0_l1_lvs" }, + { "lvs1", QCOM_RPM_PM8058_LVS1, &pm8058_switch, "vdd_l0_l1_lvs" }, + + { "ncp", QCOM_RPM_PM8058_NCP, &pm8058_ncp, "vdd_ncp" }, + { } +}; + +static const struct rpm_regulator_data rpm_pm8901_regulators[] = { + { "l0", QCOM_RPM_PM8901_LDO0, &pm8901_nldo, "vdd_l0" }, + { "l1", QCOM_RPM_PM8901_LDO1, &pm8901_pldo, "vdd_l1" }, + { "l2", QCOM_RPM_PM8901_LDO2, &pm8901_pldo, "vdd_l2" }, + { "l3", QCOM_RPM_PM8901_LDO3, &pm8901_pldo, "vdd_l3" }, + { "l4", QCOM_RPM_PM8901_LDO4, &pm8901_pldo, "vdd_l4" }, + { "l5", QCOM_RPM_PM8901_LDO5, &pm8901_pldo, "vdd_l5" }, + { "l6", QCOM_RPM_PM8901_LDO6, &pm8901_pldo, "vdd_l6" }, + + { "s0", QCOM_RPM_PM8901_SMPS0, &pm8901_ftsmps, "vdd_s0" }, + { "s1", QCOM_RPM_PM8901_SMPS1, &pm8901_ftsmps, "vdd_s1" }, + { "s2", QCOM_RPM_PM8901_SMPS2, &pm8901_ftsmps, "vdd_s2" }, + { "s3", QCOM_RPM_PM8901_SMPS3, &pm8901_ftsmps, "vdd_s3" }, + { "s4", QCOM_RPM_PM8901_SMPS4, &pm8901_ftsmps, "vdd_s4" }, + + { "lvs0", QCOM_RPM_PM8901_LVS0, &pm8901_switch, "lvs0_in" }, + { "lvs1", QCOM_RPM_PM8901_LVS1, &pm8901_switch, "lvs1_in" }, + { "lvs2", QCOM_RPM_PM8901_LVS2, &pm8901_switch, "lvs2_in" }, + { "lvs3", QCOM_RPM_PM8901_LVS3, &pm8901_switch, "lvs3_in" }, + + { "mvs", QCOM_RPM_PM8901_MVS, &pm8901_switch, "mvs_in" }, + { } +}; + +static const struct rpm_regulator_data rpm_pm8921_regulators[] = { + { "s1", QCOM_RPM_PM8921_SMPS1, &pm8921_smps, "vdd_s1" }, + { "s2", QCOM_RPM_PM8921_SMPS2, &pm8921_smps, "vdd_s2" }, + { "s3", QCOM_RPM_PM8921_SMPS3, &pm8921_smps }, + { "s4", QCOM_RPM_PM8921_SMPS4, &pm8921_smps, "vdd_s4" }, + { "s7", QCOM_RPM_PM8921_SMPS7, &pm8921_smps, "vdd_s7" }, + { "s8", QCOM_RPM_PM8921_SMPS8, &pm8921_smps, "vdd_s8" }, + + { "l1", QCOM_RPM_PM8921_LDO1, &pm8921_nldo, "vdd_l1_l2_l12_l18" }, + { "l2", QCOM_RPM_PM8921_LDO2, &pm8921_nldo, "vdd_l1_l2_l12_l18" }, + { "l3", QCOM_RPM_PM8921_LDO3, &pm8921_pldo, "vdd_l3_l15_l17" }, + { "l4", QCOM_RPM_PM8921_LDO4, &pm8921_pldo, "vdd_l4_l14" }, + { "l5", QCOM_RPM_PM8921_LDO5, &pm8921_pldo, "vdd_l5_l8_l16" }, + { "l6", QCOM_RPM_PM8921_LDO6, &pm8921_pldo, "vdd_l6_l7" }, + { "l7", QCOM_RPM_PM8921_LDO7, &pm8921_pldo, "vdd_l6_l7" }, + { "l8", QCOM_RPM_PM8921_LDO8, &pm8921_pldo, "vdd_l5_l8_l16" }, + { "l9", QCOM_RPM_PM8921_LDO9, &pm8921_pldo, "vdd_l9_l11" }, + { "l10", QCOM_RPM_PM8921_LDO10, &pm8921_pldo, "vdd_l10_l22" }, + { "l11", QCOM_RPM_PM8921_LDO11, &pm8921_pldo, "vdd_l9_l11" }, + { "l12", QCOM_RPM_PM8921_LDO12, &pm8921_nldo, "vdd_l1_l2_l12_l18" }, + { "l14", QCOM_RPM_PM8921_LDO14, &pm8921_pldo, "vdd_l4_l14" }, + { "l15", QCOM_RPM_PM8921_LDO15, &pm8921_pldo, "vdd_l3_l15_l17" }, + { "l16", QCOM_RPM_PM8921_LDO16, &pm8921_pldo, "vdd_l5_l8_l16" }, + { "l17", QCOM_RPM_PM8921_LDO17, &pm8921_pldo, "vdd_l3_l15_l17" }, + { "l18", QCOM_RPM_PM8921_LDO18, &pm8921_nldo, "vdd_l1_l2_l12_l18" }, + { "l21", QCOM_RPM_PM8921_LDO21, &pm8921_pldo, "vdd_l21_l23_l29" }, + { "l22", QCOM_RPM_PM8921_LDO22, &pm8921_pldo, "vdd_l10_l22" }, + { "l23", QCOM_RPM_PM8921_LDO23, &pm8921_pldo, "vdd_l21_l23_l29" }, + { "l24", QCOM_RPM_PM8921_LDO24, &pm8921_nldo1200, "vdd_l24" }, + { "l25", QCOM_RPM_PM8921_LDO25, &pm8921_nldo1200, "vdd_l25" }, + { "l26", QCOM_RPM_PM8921_LDO26, &pm8921_nldo1200, "vdd_l26" }, + { "l27", QCOM_RPM_PM8921_LDO27, &pm8921_nldo1200, "vdd_l27" }, + { "l28", QCOM_RPM_PM8921_LDO28, &pm8921_nldo1200, "vdd_l28" }, + { "l29", QCOM_RPM_PM8921_LDO29, &pm8921_pldo, "vdd_l21_l23_l29" }, + + { "lvs1", QCOM_RPM_PM8921_LVS1, &pm8921_switch, "vin_lvs1_3_6" }, + { "lvs2", QCOM_RPM_PM8921_LVS2, &pm8921_switch, "vin_lvs2" }, + { "lvs3", QCOM_RPM_PM8921_LVS3, &pm8921_switch, "vin_lvs1_3_6" }, + { "lvs4", QCOM_RPM_PM8921_LVS4, &pm8921_switch, "vin_lvs4_5_7" }, + { "lvs5", QCOM_RPM_PM8921_LVS5, &pm8921_switch, "vin_lvs4_5_7" }, + { "lvs6", QCOM_RPM_PM8921_LVS6, &pm8921_switch, "vin_lvs1_3_6" }, + { "lvs7", QCOM_RPM_PM8921_LVS7, &pm8921_switch, "vin_lvs4_5_7" }, + + { "usb-switch", QCOM_RPM_USB_OTG_SWITCH, &pm8921_switch, "vin_5vs" }, + { "hdmi-switch", QCOM_RPM_HDMI_SWITCH, &pm8921_switch, "vin_5vs" }, + { "ncp", QCOM_RPM_PM8921_NCP, &pm8921_ncp, "vdd_ncp" }, + { } +}; + +static const struct of_device_id rpm_of_match[] = { + { .compatible = "qcom,rpm-pm8018-regulators", + .data = &rpm_pm8018_regulators }, + { .compatible = "qcom,rpm-pm8058-regulators", .data = &rpm_pm8058_regulators }, + { .compatible = "qcom,rpm-pm8901-regulators", .data = &rpm_pm8901_regulators }, + { .compatible = "qcom,rpm-pm8921-regulators", .data = &rpm_pm8921_regulators }, + { } +}; +MODULE_DEVICE_TABLE(of, rpm_of_match); + +static int rpm_reg_probe(struct platform_device *pdev) +{ + const struct rpm_regulator_data *reg; + const struct of_device_id *match; + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct qcom_rpm_reg *vreg; + struct qcom_rpm *rpm; + + rpm = dev_get_drvdata(pdev->dev.parent); + if (!rpm) { + dev_err(&pdev->dev, "unable to retrieve handle to rpm\n"); + return -ENODEV; + } + + match = of_match_device(rpm_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "failed to match device\n"); + return -ENODEV; + } + + for (reg = match->data; reg->name; reg++) { + vreg = devm_kmalloc(&pdev->dev, sizeof(*vreg), GFP_KERNEL); + if (!vreg) + return -ENOMEM; + + memcpy(vreg, reg->template, sizeof(*vreg)); + mutex_init(&vreg->lock); + + vreg->dev = &pdev->dev; + vreg->resource = reg->resource; + vreg->rpm = rpm; + + vreg->desc.id = -1; + vreg->desc.owner = THIS_MODULE; + vreg->desc.type = REGULATOR_VOLTAGE; + vreg->desc.name = reg->name; + vreg->desc.supply_name = reg->supply; + vreg->desc.of_match = reg->name; + vreg->desc.of_parse_cb = rpm_reg_of_parse; + + config.dev = &pdev->dev; + config.driver_data = vreg; + rdev = devm_regulator_register(&pdev->dev, &vreg->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", reg->name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static struct platform_driver rpm_reg_driver = { + .probe = rpm_reg_probe, + .driver = { + .name = "qcom_rpm_reg", + .of_match_table = of_match_ptr(rpm_of_match), + }, +}; + +static int __init rpm_reg_init(void) +{ + return platform_driver_register(&rpm_reg_driver); +} +subsys_initcall(rpm_reg_init); + +static void __exit rpm_reg_exit(void) +{ + platform_driver_unregister(&rpm_reg_driver); +} +module_exit(rpm_reg_exit) + +MODULE_DESCRIPTION("Qualcomm RPM regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/qcom_smd-regulator.c b/drivers/regulator/qcom_smd-regulator.c new file mode 100644 index 000000000..3c6d6afd4 --- /dev/null +++ b/drivers/regulator/qcom_smd-regulator.c @@ -0,0 +1,791 @@ +/* + * Copyright (c) 2015, Sony Mobile Communications AB. + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/soc/qcom/smd-rpm.h> + +struct qcom_rpm_reg { + struct device *dev; + + struct qcom_smd_rpm *rpm; + + u32 type; + u32 id; + + struct regulator_desc desc; + + int is_enabled; + int uV; +}; + +struct rpm_regulator_req { + __le32 key; + __le32 nbytes; + __le32 value; +}; + +#define RPM_KEY_SWEN 0x6e657773 /* "swen" */ +#define RPM_KEY_UV 0x00007675 /* "uv" */ +#define RPM_KEY_MA 0x0000616d /* "ma" */ + +static int rpm_reg_write_active(struct qcom_rpm_reg *vreg, + struct rpm_regulator_req *req, + size_t size) +{ + return qcom_rpm_smd_write(vreg->rpm, + QCOM_SMD_RPM_ACTIVE_STATE, + vreg->type, + vreg->id, + req, size); +} + +static int rpm_reg_enable(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + struct rpm_regulator_req req; + int ret; + + req.key = cpu_to_le32(RPM_KEY_SWEN); + req.nbytes = cpu_to_le32(sizeof(u32)); + req.value = cpu_to_le32(1); + + ret = rpm_reg_write_active(vreg, &req, sizeof(req)); + if (!ret) + vreg->is_enabled = 1; + + return ret; +} + +static int rpm_reg_is_enabled(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + + return vreg->is_enabled; +} + +static int rpm_reg_disable(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + struct rpm_regulator_req req; + int ret; + + req.key = cpu_to_le32(RPM_KEY_SWEN); + req.nbytes = cpu_to_le32(sizeof(u32)); + req.value = 0; + + ret = rpm_reg_write_active(vreg, &req, sizeof(req)); + if (!ret) + vreg->is_enabled = 0; + + return ret; +} + +static int rpm_reg_get_voltage(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + + return vreg->uV; +} + +static int rpm_reg_set_voltage(struct regulator_dev *rdev, + int min_uV, + int max_uV, + unsigned *selector) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + struct rpm_regulator_req req; + int ret = 0; + + req.key = cpu_to_le32(RPM_KEY_UV); + req.nbytes = cpu_to_le32(sizeof(u32)); + req.value = cpu_to_le32(min_uV); + + ret = rpm_reg_write_active(vreg, &req, sizeof(req)); + if (!ret) + vreg->uV = min_uV; + + return ret; +} + +static int rpm_reg_set_load(struct regulator_dev *rdev, int load_uA) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + struct rpm_regulator_req req; + + req.key = cpu_to_le32(RPM_KEY_MA); + req.nbytes = cpu_to_le32(sizeof(u32)); + req.value = cpu_to_le32(load_uA / 1000); + + return rpm_reg_write_active(vreg, &req, sizeof(req)); +} + +static const struct regulator_ops rpm_smps_ldo_ops = { + .enable = rpm_reg_enable, + .disable = rpm_reg_disable, + .is_enabled = rpm_reg_is_enabled, + .list_voltage = regulator_list_voltage_linear_range, + + .get_voltage = rpm_reg_get_voltage, + .set_voltage = rpm_reg_set_voltage, + + .set_load = rpm_reg_set_load, +}; + +static const struct regulator_ops rpm_smps_ldo_ops_fixed = { + .enable = rpm_reg_enable, + .disable = rpm_reg_disable, + .is_enabled = rpm_reg_is_enabled, + + .get_voltage = rpm_reg_get_voltage, + .set_voltage = rpm_reg_set_voltage, + + .set_load = rpm_reg_set_load, +}; + +static const struct regulator_ops rpm_switch_ops = { + .enable = rpm_reg_enable, + .disable = rpm_reg_disable, + .is_enabled = rpm_reg_is_enabled, +}; + +static const struct regulator_ops rpm_bob_ops = { + .enable = rpm_reg_enable, + .disable = rpm_reg_disable, + .is_enabled = rpm_reg_is_enabled, + + .get_voltage = rpm_reg_get_voltage, + .set_voltage = rpm_reg_set_voltage, +}; + +static const struct regulator_desc pma8084_hfsmps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(375000, 0, 95, 12500), + REGULATOR_LINEAR_RANGE(1550000, 96, 158, 25000), + }, + .n_linear_ranges = 2, + .n_voltages = 159, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pma8084_ftsmps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(350000, 0, 184, 5000), + REGULATOR_LINEAR_RANGE(1280000, 185, 261, 10000), + }, + .n_linear_ranges = 2, + .n_voltages = 262, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pma8084_pldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE( 750000, 0, 63, 12500), + REGULATOR_LINEAR_RANGE(1550000, 64, 126, 25000), + REGULATOR_LINEAR_RANGE(3100000, 127, 163, 50000), + }, + .n_linear_ranges = 3, + .n_voltages = 164, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pma8084_nldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(750000, 0, 63, 12500), + }, + .n_linear_ranges = 1, + .n_voltages = 64, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pma8084_switch = { + .ops = &rpm_switch_ops, +}; + +static const struct regulator_desc pm8x41_hfsmps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE( 375000, 0, 95, 12500), + REGULATOR_LINEAR_RANGE(1575000, 96, 158, 25000), + }, + .n_linear_ranges = 2, + .n_voltages = 159, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8841_ftsmps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(350000, 0, 184, 5000), + REGULATOR_LINEAR_RANGE(1280000, 185, 261, 10000), + }, + .n_linear_ranges = 2, + .n_voltages = 262, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8941_boost = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(4000000, 0, 30, 50000), + }, + .n_linear_ranges = 1, + .n_voltages = 31, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8941_pldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE( 750000, 0, 63, 12500), + REGULATOR_LINEAR_RANGE(1550000, 64, 126, 25000), + REGULATOR_LINEAR_RANGE(3100000, 127, 163, 50000), + }, + .n_linear_ranges = 3, + .n_voltages = 164, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8941_nldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(750000, 0, 63, 12500), + }, + .n_linear_ranges = 1, + .n_voltages = 64, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8941_lnldo = { + .fixed_uV = 1740000, + .n_voltages = 1, + .ops = &rpm_smps_ldo_ops_fixed, +}; + +static const struct regulator_desc pm8941_switch = { + .ops = &rpm_switch_ops, +}; + +static const struct regulator_desc pm8916_pldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(750000, 0, 208, 12500), + }, + .n_linear_ranges = 1, + .n_voltages = 209, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8916_nldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(375000, 0, 93, 12500), + }, + .n_linear_ranges = 1, + .n_voltages = 94, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8916_buck_lvo_smps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(375000, 0, 95, 12500), + REGULATOR_LINEAR_RANGE(750000, 96, 127, 25000), + }, + .n_linear_ranges = 2, + .n_voltages = 128, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8916_buck_hvo_smps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(1550000, 0, 31, 25000), + }, + .n_linear_ranges = 1, + .n_voltages = 32, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8994_hfsmps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE( 375000, 0, 95, 12500), + REGULATOR_LINEAR_RANGE(1550000, 96, 158, 25000), + }, + .n_linear_ranges = 2, + .n_voltages = 159, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8994_ftsmps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(350000, 0, 199, 5000), + REGULATOR_LINEAR_RANGE(700000, 200, 349, 10000), + }, + .n_linear_ranges = 2, + .n_voltages = 350, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8994_nldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(750000, 0, 63, 12500), + }, + .n_linear_ranges = 1, + .n_voltages = 64, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8994_pldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE( 750000, 0, 63, 12500), + REGULATOR_LINEAR_RANGE(1550000, 64, 126, 25000), + REGULATOR_LINEAR_RANGE(3100000, 127, 163, 50000), + }, + .n_linear_ranges = 3, + .n_voltages = 164, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8994_switch = { + .ops = &rpm_switch_ops, +}; + +static const struct regulator_desc pm8994_lnldo = { + .fixed_uV = 1740000, + .n_voltages = 1, + .ops = &rpm_smps_ldo_ops_fixed, +}; + +static const struct regulator_desc pm8998_ftsmps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(320000, 0, 258, 4000), + }, + .n_linear_ranges = 1, + .n_voltages = 259, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8998_hfsmps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(320000, 0, 215, 8000), + }, + .n_linear_ranges = 1, + .n_voltages = 216, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8998_nldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(312000, 0, 127, 8000), + }, + .n_linear_ranges = 1, + .n_voltages = 128, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8998_pldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(1664000, 0, 255, 8000), + }, + .n_linear_ranges = 1, + .n_voltages = 256, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8998_pldo_lv = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(1256000, 0, 127, 8000), + }, + .n_linear_ranges = 1, + .n_voltages = 128, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8998_switch = { + .ops = &rpm_switch_ops, +}; + +static const struct regulator_desc pmi8998_bob = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(1824000, 0, 83, 32000), + }, + .n_linear_ranges = 1, + .n_voltages = 84, + .ops = &rpm_bob_ops, +}; + +struct rpm_regulator_data { + const char *name; + u32 type; + u32 id; + const struct regulator_desc *desc; + const char *supply; +}; + +static const struct rpm_regulator_data rpm_pm8841_regulators[] = { + { "s1", QCOM_SMD_RPM_SMPB, 1, &pm8x41_hfsmps, "vdd_s1" }, + { "s2", QCOM_SMD_RPM_SMPB, 2, &pm8841_ftsmps, "vdd_s2" }, + { "s3", QCOM_SMD_RPM_SMPB, 3, &pm8x41_hfsmps, "vdd_s3" }, + { "s4", QCOM_SMD_RPM_SMPB, 4, &pm8841_ftsmps, "vdd_s4" }, + { "s5", QCOM_SMD_RPM_SMPB, 5, &pm8841_ftsmps, "vdd_s5" }, + { "s6", QCOM_SMD_RPM_SMPB, 6, &pm8841_ftsmps, "vdd_s6" }, + { "s7", QCOM_SMD_RPM_SMPB, 7, &pm8841_ftsmps, "vdd_s7" }, + { "s8", QCOM_SMD_RPM_SMPB, 8, &pm8841_ftsmps, "vdd_s8" }, + {} +}; + +static const struct rpm_regulator_data rpm_pm8916_regulators[] = { + { "s1", QCOM_SMD_RPM_SMPA, 1, &pm8916_buck_lvo_smps, "vdd_s1" }, + { "s2", QCOM_SMD_RPM_SMPA, 2, &pm8916_buck_lvo_smps, "vdd_s2" }, + { "s3", QCOM_SMD_RPM_SMPA, 3, &pm8916_buck_lvo_smps, "vdd_s3" }, + { "s4", QCOM_SMD_RPM_SMPA, 4, &pm8916_buck_hvo_smps, "vdd_s4" }, + { "l1", QCOM_SMD_RPM_LDOA, 1, &pm8916_nldo, "vdd_l1_l2_l3" }, + { "l2", QCOM_SMD_RPM_LDOA, 2, &pm8916_nldo, "vdd_l1_l2_l3" }, + { "l3", QCOM_SMD_RPM_LDOA, 3, &pm8916_nldo, "vdd_l1_l2_l3" }, + { "l4", QCOM_SMD_RPM_LDOA, 4, &pm8916_pldo, "vdd_l4_l5_l6" }, + { "l5", QCOM_SMD_RPM_LDOA, 5, &pm8916_pldo, "vdd_l4_l5_l6" }, + { "l6", QCOM_SMD_RPM_LDOA, 6, &pm8916_pldo, "vdd_l4_l5_l6" }, + { "l7", QCOM_SMD_RPM_LDOA, 7, &pm8916_pldo, "vdd_l7" }, + { "l8", QCOM_SMD_RPM_LDOA, 8, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18" }, + { "l9", QCOM_SMD_RPM_LDOA, 9, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18" }, + { "l10", QCOM_SMD_RPM_LDOA, 10, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l11", QCOM_SMD_RPM_LDOA, 11, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l12", QCOM_SMD_RPM_LDOA, 12, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l13", QCOM_SMD_RPM_LDOA, 13, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l14", QCOM_SMD_RPM_LDOA, 14, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l15", QCOM_SMD_RPM_LDOA, 15, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l16", QCOM_SMD_RPM_LDOA, 16, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l17", QCOM_SMD_RPM_LDOA, 17, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l18", QCOM_SMD_RPM_LDOA, 18, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + {} +}; + +static const struct rpm_regulator_data rpm_pm8941_regulators[] = { + { "s1", QCOM_SMD_RPM_SMPA, 1, &pm8x41_hfsmps, "vdd_s1" }, + { "s2", QCOM_SMD_RPM_SMPA, 2, &pm8x41_hfsmps, "vdd_s2" }, + { "s3", QCOM_SMD_RPM_SMPA, 3, &pm8x41_hfsmps, "vdd_s3" }, + { "s4", QCOM_SMD_RPM_BOOST, 1, &pm8941_boost }, + + { "l1", QCOM_SMD_RPM_LDOA, 1, &pm8941_nldo, "vdd_l1_l3" }, + { "l2", QCOM_SMD_RPM_LDOA, 2, &pm8941_nldo, "vdd_l2_lvs1_2_3" }, + { "l3", QCOM_SMD_RPM_LDOA, 3, &pm8941_nldo, "vdd_l1_l3" }, + { "l4", QCOM_SMD_RPM_LDOA, 4, &pm8941_nldo, "vdd_l4_l11" }, + { "l5", QCOM_SMD_RPM_LDOA, 5, &pm8941_lnldo, "vdd_l5_l7" }, + { "l6", QCOM_SMD_RPM_LDOA, 6, &pm8941_pldo, "vdd_l6_l12_l14_l15" }, + { "l7", QCOM_SMD_RPM_LDOA, 7, &pm8941_lnldo, "vdd_l5_l7" }, + { "l8", QCOM_SMD_RPM_LDOA, 8, &pm8941_pldo, "vdd_l8_l16_l18_l19" }, + { "l9", QCOM_SMD_RPM_LDOA, 9, &pm8941_pldo, "vdd_l9_l10_l17_l22" }, + { "l10", QCOM_SMD_RPM_LDOA, 10, &pm8941_pldo, "vdd_l9_l10_l17_l22" }, + { "l11", QCOM_SMD_RPM_LDOA, 11, &pm8941_nldo, "vdd_l4_l11" }, + { "l12", QCOM_SMD_RPM_LDOA, 12, &pm8941_pldo, "vdd_l6_l12_l14_l15" }, + { "l13", QCOM_SMD_RPM_LDOA, 13, &pm8941_pldo, "vdd_l13_l20_l23_l24" }, + { "l14", QCOM_SMD_RPM_LDOA, 14, &pm8941_pldo, "vdd_l6_l12_l14_l15" }, + { "l15", QCOM_SMD_RPM_LDOA, 15, &pm8941_pldo, "vdd_l6_l12_l14_l15" }, + { "l16", QCOM_SMD_RPM_LDOA, 16, &pm8941_pldo, "vdd_l8_l16_l18_l19" }, + { "l17", QCOM_SMD_RPM_LDOA, 17, &pm8941_pldo, "vdd_l9_l10_l17_l22" }, + { "l18", QCOM_SMD_RPM_LDOA, 18, &pm8941_pldo, "vdd_l8_l16_l18_l19" }, + { "l19", QCOM_SMD_RPM_LDOA, 19, &pm8941_pldo, "vdd_l8_l16_l18_l19" }, + { "l20", QCOM_SMD_RPM_LDOA, 20, &pm8941_pldo, "vdd_l13_l20_l23_l24" }, + { "l21", QCOM_SMD_RPM_LDOA, 21, &pm8941_pldo, "vdd_l21" }, + { "l22", QCOM_SMD_RPM_LDOA, 22, &pm8941_pldo, "vdd_l9_l10_l17_l22" }, + { "l23", QCOM_SMD_RPM_LDOA, 23, &pm8941_pldo, "vdd_l13_l20_l23_l24" }, + { "l24", QCOM_SMD_RPM_LDOA, 24, &pm8941_pldo, "vdd_l13_l20_l23_l24" }, + + { "lvs1", QCOM_SMD_RPM_VSA, 1, &pm8941_switch, "vdd_l2_lvs1_2_3" }, + { "lvs2", QCOM_SMD_RPM_VSA, 2, &pm8941_switch, "vdd_l2_lvs1_2_3" }, + { "lvs3", QCOM_SMD_RPM_VSA, 3, &pm8941_switch, "vdd_l2_lvs1_2_3" }, + + { "5vs1", QCOM_SMD_RPM_VSA, 4, &pm8941_switch, "vin_5vs" }, + { "5vs2", QCOM_SMD_RPM_VSA, 5, &pm8941_switch, "vin_5vs" }, + + {} +}; + +static const struct rpm_regulator_data rpm_pma8084_regulators[] = { + { "s1", QCOM_SMD_RPM_SMPA, 1, &pma8084_ftsmps, "vdd_s1" }, + { "s2", QCOM_SMD_RPM_SMPA, 2, &pma8084_ftsmps, "vdd_s2" }, + { "s3", QCOM_SMD_RPM_SMPA, 3, &pma8084_hfsmps, "vdd_s3" }, + { "s4", QCOM_SMD_RPM_SMPA, 4, &pma8084_hfsmps, "vdd_s4" }, + { "s5", QCOM_SMD_RPM_SMPA, 5, &pma8084_hfsmps, "vdd_s5" }, + { "s6", QCOM_SMD_RPM_SMPA, 6, &pma8084_ftsmps, "vdd_s6" }, + { "s7", QCOM_SMD_RPM_SMPA, 7, &pma8084_ftsmps, "vdd_s7" }, + { "s8", QCOM_SMD_RPM_SMPA, 8, &pma8084_ftsmps, "vdd_s8" }, + { "s9", QCOM_SMD_RPM_SMPA, 9, &pma8084_ftsmps, "vdd_s9" }, + { "s10", QCOM_SMD_RPM_SMPA, 10, &pma8084_ftsmps, "vdd_s10" }, + { "s11", QCOM_SMD_RPM_SMPA, 11, &pma8084_ftsmps, "vdd_s11" }, + { "s12", QCOM_SMD_RPM_SMPA, 12, &pma8084_ftsmps, "vdd_s12" }, + + { "l1", QCOM_SMD_RPM_LDOA, 1, &pma8084_nldo, "vdd_l1_l11" }, + { "l2", QCOM_SMD_RPM_LDOA, 2, &pma8084_nldo, "vdd_l2_l3_l4_l27" }, + { "l3", QCOM_SMD_RPM_LDOA, 3, &pma8084_nldo, "vdd_l2_l3_l4_l27" }, + { "l4", QCOM_SMD_RPM_LDOA, 4, &pma8084_nldo, "vdd_l2_l3_l4_l27" }, + { "l5", QCOM_SMD_RPM_LDOA, 5, &pma8084_pldo, "vdd_l5_l7" }, + { "l6", QCOM_SMD_RPM_LDOA, 6, &pma8084_pldo, "vdd_l6_l12_l14_l15_l26" }, + { "l7", QCOM_SMD_RPM_LDOA, 7, &pma8084_pldo, "vdd_l5_l7" }, + { "l8", QCOM_SMD_RPM_LDOA, 8, &pma8084_pldo, "vdd_l8" }, + { "l9", QCOM_SMD_RPM_LDOA, 9, &pma8084_pldo, "vdd_l9_l10_l13_l20_l23_l24" }, + { "l10", QCOM_SMD_RPM_LDOA, 10, &pma8084_pldo, "vdd_l9_l10_l13_l20_l23_l24" }, + { "l11", QCOM_SMD_RPM_LDOA, 11, &pma8084_nldo, "vdd_l1_l11" }, + { "l12", QCOM_SMD_RPM_LDOA, 12, &pma8084_pldo, "vdd_l6_l12_l14_l15_l26" }, + { "l13", QCOM_SMD_RPM_LDOA, 13, &pma8084_pldo, "vdd_l9_l10_l13_l20_l23_l24" }, + { "l14", QCOM_SMD_RPM_LDOA, 14, &pma8084_pldo, "vdd_l6_l12_l14_l15_l26" }, + { "l15", QCOM_SMD_RPM_LDOA, 15, &pma8084_pldo, "vdd_l6_l12_l14_l15_l26" }, + { "l16", QCOM_SMD_RPM_LDOA, 16, &pma8084_pldo, "vdd_l16_l25" }, + { "l17", QCOM_SMD_RPM_LDOA, 17, &pma8084_pldo, "vdd_l17" }, + { "l18", QCOM_SMD_RPM_LDOA, 18, &pma8084_pldo, "vdd_l18" }, + { "l19", QCOM_SMD_RPM_LDOA, 19, &pma8084_pldo, "vdd_l19" }, + { "l20", QCOM_SMD_RPM_LDOA, 20, &pma8084_pldo, "vdd_l9_l10_l13_l20_l23_l24" }, + { "l21", QCOM_SMD_RPM_LDOA, 21, &pma8084_pldo, "vdd_l21" }, + { "l22", QCOM_SMD_RPM_LDOA, 22, &pma8084_pldo, "vdd_l22" }, + { "l23", QCOM_SMD_RPM_LDOA, 23, &pma8084_pldo, "vdd_l9_l10_l13_l20_l23_l24" }, + { "l24", QCOM_SMD_RPM_LDOA, 24, &pma8084_pldo, "vdd_l9_l10_l13_l20_l23_l24" }, + { "l25", QCOM_SMD_RPM_LDOA, 25, &pma8084_pldo, "vdd_l16_l25" }, + { "l26", QCOM_SMD_RPM_LDOA, 26, &pma8084_pldo, "vdd_l6_l12_l14_l15_l26" }, + { "l27", QCOM_SMD_RPM_LDOA, 27, &pma8084_nldo, "vdd_l2_l3_l4_l27" }, + + { "lvs1", QCOM_SMD_RPM_VSA, 1, &pma8084_switch }, + { "lvs2", QCOM_SMD_RPM_VSA, 2, &pma8084_switch }, + { "lvs3", QCOM_SMD_RPM_VSA, 3, &pma8084_switch }, + { "lvs4", QCOM_SMD_RPM_VSA, 4, &pma8084_switch }, + { "5vs1", QCOM_SMD_RPM_VSA, 5, &pma8084_switch }, + + {} +}; + +static const struct rpm_regulator_data rpm_pm8994_regulators[] = { + { "s1", QCOM_SMD_RPM_SMPA, 1, &pm8994_ftsmps, "vdd_s1" }, + { "s2", QCOM_SMD_RPM_SMPA, 2, &pm8994_ftsmps, "vdd_s2" }, + { "s3", QCOM_SMD_RPM_SMPA, 3, &pm8994_hfsmps, "vdd_s3" }, + { "s4", QCOM_SMD_RPM_SMPA, 4, &pm8994_hfsmps, "vdd_s4" }, + { "s5", QCOM_SMD_RPM_SMPA, 5, &pm8994_hfsmps, "vdd_s5" }, + { "s6", QCOM_SMD_RPM_SMPA, 6, &pm8994_ftsmps, "vdd_s6" }, + { "s7", QCOM_SMD_RPM_SMPA, 7, &pm8994_hfsmps, "vdd_s7" }, + { "s8", QCOM_SMD_RPM_SMPA, 8, &pm8994_ftsmps, "vdd_s8" }, + { "s9", QCOM_SMD_RPM_SMPA, 9, &pm8994_ftsmps, "vdd_s9" }, + { "s10", QCOM_SMD_RPM_SMPA, 10, &pm8994_ftsmps, "vdd_s10" }, + { "s11", QCOM_SMD_RPM_SMPA, 11, &pm8994_ftsmps, "vdd_s11" }, + { "s12", QCOM_SMD_RPM_SMPA, 12, &pm8994_ftsmps, "vdd_s12" }, + { "l1", QCOM_SMD_RPM_LDOA, 1, &pm8994_nldo, "vdd_l1" }, + { "l2", QCOM_SMD_RPM_LDOA, 2, &pm8994_nldo, "vdd_l2_l26_l28" }, + { "l3", QCOM_SMD_RPM_LDOA, 3, &pm8994_nldo, "vdd_l3_l11" }, + { "l4", QCOM_SMD_RPM_LDOA, 4, &pm8994_nldo, "vdd_l4_l27_l31" }, + { "l5", QCOM_SMD_RPM_LDOA, 5, &pm8994_lnldo, "vdd_l5_l7" }, + { "l6", QCOM_SMD_RPM_LDOA, 6, &pm8994_pldo, "vdd_l6_l12_l32" }, + { "l7", QCOM_SMD_RPM_LDOA, 7, &pm8994_lnldo, "vdd_l5_l7" }, + { "l8", QCOM_SMD_RPM_LDOA, 8, &pm8994_pldo, "vdd_l8_l16_l30" }, + { "l9", QCOM_SMD_RPM_LDOA, 9, &pm8994_pldo, "vdd_l9_l10_l18_l22" }, + { "l10", QCOM_SMD_RPM_LDOA, 10, &pm8994_pldo, "vdd_l9_l10_l18_l22" }, + { "l11", QCOM_SMD_RPM_LDOA, 11, &pm8994_nldo, "vdd_l3_l11" }, + { "l12", QCOM_SMD_RPM_LDOA, 12, &pm8994_pldo, "vdd_l6_l12_l32" }, + { "l13", QCOM_SMD_RPM_LDOA, 13, &pm8994_pldo, "vdd_l13_l19_l23_l24" }, + { "l14", QCOM_SMD_RPM_LDOA, 14, &pm8994_pldo, "vdd_l14_l15" }, + { "l15", QCOM_SMD_RPM_LDOA, 15, &pm8994_pldo, "vdd_l14_l15" }, + { "l16", QCOM_SMD_RPM_LDOA, 16, &pm8994_pldo, "vdd_l8_l16_l30" }, + { "l17", QCOM_SMD_RPM_LDOA, 17, &pm8994_pldo, "vdd_l17_l29" }, + { "l18", QCOM_SMD_RPM_LDOA, 18, &pm8994_pldo, "vdd_l9_l10_l18_l22" }, + { "l19", QCOM_SMD_RPM_LDOA, 19, &pm8994_pldo, "vdd_l13_l19_l23_l24" }, + { "l20", QCOM_SMD_RPM_LDOA, 20, &pm8994_pldo, "vdd_l20_l21" }, + { "l21", QCOM_SMD_RPM_LDOA, 21, &pm8994_pldo, "vdd_l20_l21" }, + { "l22", QCOM_SMD_RPM_LDOA, 22, &pm8994_pldo, "vdd_l9_l10_l18_l22" }, + { "l23", QCOM_SMD_RPM_LDOA, 23, &pm8994_pldo, "vdd_l13_l19_l23_l24" }, + { "l24", QCOM_SMD_RPM_LDOA, 24, &pm8994_pldo, "vdd_l13_l19_l23_l24" }, + { "l25", QCOM_SMD_RPM_LDOA, 25, &pm8994_pldo, "vdd_l25" }, + { "l26", QCOM_SMD_RPM_LDOA, 26, &pm8994_nldo, "vdd_l2_l26_l28" }, + { "l27", QCOM_SMD_RPM_LDOA, 27, &pm8994_nldo, "vdd_l4_l27_l31" }, + { "l28", QCOM_SMD_RPM_LDOA, 28, &pm8994_nldo, "vdd_l2_l26_l28" }, + { "l29", QCOM_SMD_RPM_LDOA, 29, &pm8994_pldo, "vdd_l17_l29" }, + { "l30", QCOM_SMD_RPM_LDOA, 30, &pm8994_pldo, "vdd_l8_l16_l30" }, + { "l31", QCOM_SMD_RPM_LDOA, 31, &pm8994_nldo, "vdd_l4_l27_l31" }, + { "l32", QCOM_SMD_RPM_LDOA, 32, &pm8994_pldo, "vdd_l6_l12_l32" }, + { "lvs1", QCOM_SMD_RPM_VSA, 1, &pm8994_switch, "vdd_lvs1_2" }, + { "lvs2", QCOM_SMD_RPM_VSA, 2, &pm8994_switch, "vdd_lvs1_2" }, + + {} +}; + +static const struct rpm_regulator_data rpm_pm8998_regulators[] = { + { "s1", QCOM_SMD_RPM_SMPA, 1, &pm8998_ftsmps, "vdd_s1" }, + { "s2", QCOM_SMD_RPM_SMPA, 2, &pm8998_ftsmps, "vdd_s2" }, + { "s3", QCOM_SMD_RPM_SMPA, 3, &pm8998_hfsmps, "vdd_s3" }, + { "s4", QCOM_SMD_RPM_SMPA, 4, &pm8998_hfsmps, "vdd_s4" }, + { "s5", QCOM_SMD_RPM_SMPA, 5, &pm8998_hfsmps, "vdd_s5" }, + { "s6", QCOM_SMD_RPM_SMPA, 6, &pm8998_ftsmps, "vdd_s6" }, + { "s7", QCOM_SMD_RPM_SMPA, 7, &pm8998_ftsmps, "vdd_s7" }, + { "s8", QCOM_SMD_RPM_SMPA, 8, &pm8998_ftsmps, "vdd_s8" }, + { "s9", QCOM_SMD_RPM_SMPA, 9, &pm8998_ftsmps, "vdd_s9" }, + { "s10", QCOM_SMD_RPM_SMPA, 10, &pm8998_ftsmps, "vdd_s10" }, + { "s11", QCOM_SMD_RPM_SMPA, 11, &pm8998_ftsmps, "vdd_s11" }, + { "s12", QCOM_SMD_RPM_SMPA, 12, &pm8998_ftsmps, "vdd_s12" }, + { "s13", QCOM_SMD_RPM_SMPA, 13, &pm8998_ftsmps, "vdd_s13" }, + { "l1", QCOM_SMD_RPM_LDOA, 1, &pm8998_nldo, "vdd_l1_l27" }, + { "l2", QCOM_SMD_RPM_LDOA, 2, &pm8998_nldo, "vdd_l2_l8_l17" }, + { "l3", QCOM_SMD_RPM_LDOA, 3, &pm8998_nldo, "vdd_l3_l11" }, + { "l4", QCOM_SMD_RPM_LDOA, 4, &pm8998_nldo, "vdd_l4_l5" }, + { "l5", QCOM_SMD_RPM_LDOA, 5, &pm8998_nldo, "vdd_l4_l5" }, + { "l6", QCOM_SMD_RPM_LDOA, 6, &pm8998_pldo, "vdd_l6" }, + { "l7", QCOM_SMD_RPM_LDOA, 7, &pm8998_pldo_lv, "vdd_l7_l12_l14_l15" }, + { "l8", QCOM_SMD_RPM_LDOA, 8, &pm8998_nldo, "vdd_l2_l8_l17" }, + { "l9", QCOM_SMD_RPM_LDOA, 9, &pm8998_pldo, "vdd_l9" }, + { "l10", QCOM_SMD_RPM_LDOA, 10, &pm8998_pldo, "vdd_l10_l23_l25" }, + { "l11", QCOM_SMD_RPM_LDOA, 11, &pm8998_nldo, "vdd_l3_l11" }, + { "l12", QCOM_SMD_RPM_LDOA, 12, &pm8998_pldo_lv, "vdd_l7_l12_l14_l15" }, + { "l13", QCOM_SMD_RPM_LDOA, 13, &pm8998_pldo, "vdd_l13_l19_l21" }, + { "l14", QCOM_SMD_RPM_LDOA, 14, &pm8998_pldo_lv, "vdd_l7_l12_l14_l15" }, + { "l15", QCOM_SMD_RPM_LDOA, 15, &pm8998_pldo_lv, "vdd_l7_l12_l14_l15" }, + { "l16", QCOM_SMD_RPM_LDOA, 16, &pm8998_pldo, "vdd_l16_l28" }, + { "l17", QCOM_SMD_RPM_LDOA, 17, &pm8998_nldo, "vdd_l2_l8_l17" }, + { "l18", QCOM_SMD_RPM_LDOA, 18, &pm8998_pldo, "vdd_l18_l22" }, + { "l19", QCOM_SMD_RPM_LDOA, 19, &pm8998_pldo, "vdd_l13_l19_l21" }, + { "l20", QCOM_SMD_RPM_LDOA, 20, &pm8998_pldo, "vdd_l20_l24" }, + { "l21", QCOM_SMD_RPM_LDOA, 21, &pm8998_pldo, "vdd_l13_l19_l21" }, + { "l22", QCOM_SMD_RPM_LDOA, 22, &pm8998_pldo, "vdd_l18_l22" }, + { "l23", QCOM_SMD_RPM_LDOA, 23, &pm8998_pldo, "vdd_l10_l23_l25" }, + { "l24", QCOM_SMD_RPM_LDOA, 24, &pm8998_pldo, "vdd_l20_l24" }, + { "l25", QCOM_SMD_RPM_LDOA, 25, &pm8998_pldo, "vdd_l10_l23_l25" }, + { "l26", QCOM_SMD_RPM_LDOA, 26, &pm8998_nldo, "vdd_l26" }, + { "l27", QCOM_SMD_RPM_LDOA, 27, &pm8998_nldo, "vdd_l1_l27" }, + { "l28", QCOM_SMD_RPM_LDOA, 28, &pm8998_pldo, "vdd_l16_l28" }, + { "lvs1", QCOM_SMD_RPM_VSA, 1, &pm8998_switch, "vdd_lvs1_lvs2" }, + { "lvs2", QCOM_SMD_RPM_VSA, 2, &pm8998_switch, "vdd_lvs1_lvs2" }, + {} +}; + +static const struct rpm_regulator_data rpm_pmi8998_regulators[] = { + { "bob", QCOM_SMD_RPM_BOBB, 1, &pmi8998_bob, "vdd_bob" }, + {} +}; + +static const struct of_device_id rpm_of_match[] = { + { .compatible = "qcom,rpm-pm8841-regulators", .data = &rpm_pm8841_regulators }, + { .compatible = "qcom,rpm-pm8916-regulators", .data = &rpm_pm8916_regulators }, + { .compatible = "qcom,rpm-pm8941-regulators", .data = &rpm_pm8941_regulators }, + { .compatible = "qcom,rpm-pm8994-regulators", .data = &rpm_pm8994_regulators }, + { .compatible = "qcom,rpm-pm8998-regulators", .data = &rpm_pm8998_regulators }, + { .compatible = "qcom,rpm-pma8084-regulators", .data = &rpm_pma8084_regulators }, + { .compatible = "qcom,rpm-pmi8998-regulators", .data = &rpm_pmi8998_regulators }, + {} +}; +MODULE_DEVICE_TABLE(of, rpm_of_match); + +/** + * rpm_regulator_init_vreg() - initialize all attributes of a qcom_smd-regulator + * @vreg: Pointer to the individual qcom_smd-regulator resource + * @dev: Pointer to the top level qcom_smd-regulator PMIC device + * @node: Pointer to the individual qcom_smd-regulator resource + * device node + * @rpm: Pointer to the rpm bus node + * @pmic_rpm_data: Pointer to a null-terminated array of qcom_smd-regulator + * resources defined for the top level PMIC device + * + * Return: 0 on success, errno on failure + */ +static int rpm_regulator_init_vreg(struct qcom_rpm_reg *vreg, struct device *dev, + struct device_node *node, struct qcom_smd_rpm *rpm, + const struct rpm_regulator_data *pmic_rpm_data) +{ + struct regulator_config config = {}; + const struct rpm_regulator_data *rpm_data; + struct regulator_dev *rdev; + int ret; + + for (rpm_data = pmic_rpm_data; rpm_data->name; rpm_data++) + if (of_node_name_eq(node, rpm_data->name)) + break; + + if (!rpm_data->name) { + dev_err(dev, "Unknown regulator %pOFn\n", node); + return -EINVAL; + } + + vreg->dev = dev; + vreg->rpm = rpm; + vreg->type = rpm_data->type; + vreg->id = rpm_data->id; + + memcpy(&vreg->desc, rpm_data->desc, sizeof(vreg->desc)); + vreg->desc.name = rpm_data->name; + vreg->desc.supply_name = rpm_data->supply; + vreg->desc.owner = THIS_MODULE; + vreg->desc.type = REGULATOR_VOLTAGE; + vreg->desc.of_match = rpm_data->name; + + config.dev = dev; + config.of_node = node; + config.driver_data = vreg; + + rdev = devm_regulator_register(dev, &vreg->desc, &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(dev, "%pOFn: devm_regulator_register() failed, ret=%d\n", node, ret); + return ret; + } + + return 0; +} + +static int rpm_reg_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct rpm_regulator_data *vreg_data; + struct device_node *node; + struct qcom_rpm_reg *vreg; + struct qcom_smd_rpm *rpm; + int ret; + + rpm = dev_get_drvdata(pdev->dev.parent); + if (!rpm) { + dev_err(&pdev->dev, "Unable to retrieve handle to rpm\n"); + return -ENODEV; + } + + vreg_data = of_device_get_match_data(dev); + if (!vreg_data) + return -ENODEV; + + for_each_available_child_of_node(dev->of_node, node) { + vreg = devm_kzalloc(&pdev->dev, sizeof(*vreg), GFP_KERNEL); + if (!vreg) { + of_node_put(node); + return -ENOMEM; + } + + ret = rpm_regulator_init_vreg(vreg, dev, node, rpm, vreg_data); + + if (ret < 0) { + of_node_put(node); + return ret; + } + } + + return 0; +} + +static struct platform_driver rpm_reg_driver = { + .probe = rpm_reg_probe, + .driver = { + .name = "qcom_rpm_smd_regulator", + .of_match_table = rpm_of_match, + }, +}; + +static int __init rpm_reg_init(void) +{ + return platform_driver_register(&rpm_reg_driver); +} +subsys_initcall(rpm_reg_init); + +static void __exit rpm_reg_exit(void) +{ + platform_driver_unregister(&rpm_reg_driver); +} +module_exit(rpm_reg_exit) + +MODULE_DESCRIPTION("Qualcomm RPM regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/qcom_spmi-regulator.c b/drivers/regulator/qcom_spmi-regulator.c new file mode 100644 index 000000000..53a61fb65 --- /dev/null +++ b/drivers/regulator/qcom_spmi-regulator.c @@ -0,0 +1,1883 @@ +/* + * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/bitops.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/ktime.h> +#include <linux/regulator/driver.h> +#include <linux/regmap.h> +#include <linux/list.h> +#include <linux/mfd/syscon.h> +#include <linux/io.h> + +/* Pin control enable input pins. */ +#define SPMI_REGULATOR_PIN_CTRL_ENABLE_NONE 0x00 +#define SPMI_REGULATOR_PIN_CTRL_ENABLE_EN0 0x01 +#define SPMI_REGULATOR_PIN_CTRL_ENABLE_EN1 0x02 +#define SPMI_REGULATOR_PIN_CTRL_ENABLE_EN2 0x04 +#define SPMI_REGULATOR_PIN_CTRL_ENABLE_EN3 0x08 +#define SPMI_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT 0x10 + +/* Pin control high power mode input pins. */ +#define SPMI_REGULATOR_PIN_CTRL_HPM_NONE 0x00 +#define SPMI_REGULATOR_PIN_CTRL_HPM_EN0 0x01 +#define SPMI_REGULATOR_PIN_CTRL_HPM_EN1 0x02 +#define SPMI_REGULATOR_PIN_CTRL_HPM_EN2 0x04 +#define SPMI_REGULATOR_PIN_CTRL_HPM_EN3 0x08 +#define SPMI_REGULATOR_PIN_CTRL_HPM_SLEEP_B 0x10 +#define SPMI_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT 0x20 + +/* + * Used with enable parameters to specify that hardware default register values + * should be left unaltered. + */ +#define SPMI_REGULATOR_USE_HW_DEFAULT 2 + +/* Soft start strength of a voltage switch type regulator */ +enum spmi_vs_soft_start_str { + SPMI_VS_SOFT_START_STR_0P05_UA = 0, + SPMI_VS_SOFT_START_STR_0P25_UA, + SPMI_VS_SOFT_START_STR_0P55_UA, + SPMI_VS_SOFT_START_STR_0P75_UA, + SPMI_VS_SOFT_START_STR_HW_DEFAULT, +}; + +/** + * struct spmi_regulator_init_data - spmi-regulator initialization data + * @pin_ctrl_enable: Bit mask specifying which hardware pins should be + * used to enable the regulator, if any + * Value should be an ORing of + * SPMI_REGULATOR_PIN_CTRL_ENABLE_* constants. If + * the bit specified by + * SPMI_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT is + * set, then pin control enable hardware registers + * will not be modified. + * @pin_ctrl_hpm: Bit mask specifying which hardware pins should be + * used to force the regulator into high power + * mode, if any + * Value should be an ORing of + * SPMI_REGULATOR_PIN_CTRL_HPM_* constants. If + * the bit specified by + * SPMI_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT is + * set, then pin control mode hardware registers + * will not be modified. + * @vs_soft_start_strength: This parameter sets the soft start strength for + * voltage switch type regulators. Its value + * should be one of SPMI_VS_SOFT_START_STR_*. If + * its value is SPMI_VS_SOFT_START_STR_HW_DEFAULT, + * then the soft start strength will be left at its + * default hardware value. + */ +struct spmi_regulator_init_data { + unsigned pin_ctrl_enable; + unsigned pin_ctrl_hpm; + enum spmi_vs_soft_start_str vs_soft_start_strength; +}; + +/* These types correspond to unique register layouts. */ +enum spmi_regulator_logical_type { + SPMI_REGULATOR_LOGICAL_TYPE_SMPS, + SPMI_REGULATOR_LOGICAL_TYPE_LDO, + SPMI_REGULATOR_LOGICAL_TYPE_VS, + SPMI_REGULATOR_LOGICAL_TYPE_BOOST, + SPMI_REGULATOR_LOGICAL_TYPE_FTSMPS, + SPMI_REGULATOR_LOGICAL_TYPE_BOOST_BYP, + SPMI_REGULATOR_LOGICAL_TYPE_LN_LDO, + SPMI_REGULATOR_LOGICAL_TYPE_ULT_LO_SMPS, + SPMI_REGULATOR_LOGICAL_TYPE_ULT_HO_SMPS, + SPMI_REGULATOR_LOGICAL_TYPE_ULT_LDO, +}; + +enum spmi_regulator_type { + SPMI_REGULATOR_TYPE_BUCK = 0x03, + SPMI_REGULATOR_TYPE_LDO = 0x04, + SPMI_REGULATOR_TYPE_VS = 0x05, + SPMI_REGULATOR_TYPE_BOOST = 0x1b, + SPMI_REGULATOR_TYPE_FTS = 0x1c, + SPMI_REGULATOR_TYPE_BOOST_BYP = 0x1f, + SPMI_REGULATOR_TYPE_ULT_LDO = 0x21, + SPMI_REGULATOR_TYPE_ULT_BUCK = 0x22, +}; + +enum spmi_regulator_subtype { + SPMI_REGULATOR_SUBTYPE_GP_CTL = 0x08, + SPMI_REGULATOR_SUBTYPE_RF_CTL = 0x09, + SPMI_REGULATOR_SUBTYPE_N50 = 0x01, + SPMI_REGULATOR_SUBTYPE_N150 = 0x02, + SPMI_REGULATOR_SUBTYPE_N300 = 0x03, + SPMI_REGULATOR_SUBTYPE_N600 = 0x04, + SPMI_REGULATOR_SUBTYPE_N1200 = 0x05, + SPMI_REGULATOR_SUBTYPE_N600_ST = 0x06, + SPMI_REGULATOR_SUBTYPE_N1200_ST = 0x07, + SPMI_REGULATOR_SUBTYPE_N900_ST = 0x14, + SPMI_REGULATOR_SUBTYPE_N300_ST = 0x15, + SPMI_REGULATOR_SUBTYPE_P50 = 0x08, + SPMI_REGULATOR_SUBTYPE_P150 = 0x09, + SPMI_REGULATOR_SUBTYPE_P300 = 0x0a, + SPMI_REGULATOR_SUBTYPE_P600 = 0x0b, + SPMI_REGULATOR_SUBTYPE_P1200 = 0x0c, + SPMI_REGULATOR_SUBTYPE_LN = 0x10, + SPMI_REGULATOR_SUBTYPE_LV_P50 = 0x28, + SPMI_REGULATOR_SUBTYPE_LV_P150 = 0x29, + SPMI_REGULATOR_SUBTYPE_LV_P300 = 0x2a, + SPMI_REGULATOR_SUBTYPE_LV_P600 = 0x2b, + SPMI_REGULATOR_SUBTYPE_LV_P1200 = 0x2c, + SPMI_REGULATOR_SUBTYPE_LV_P450 = 0x2d, + SPMI_REGULATOR_SUBTYPE_LV100 = 0x01, + SPMI_REGULATOR_SUBTYPE_LV300 = 0x02, + SPMI_REGULATOR_SUBTYPE_MV300 = 0x08, + SPMI_REGULATOR_SUBTYPE_MV500 = 0x09, + SPMI_REGULATOR_SUBTYPE_HDMI = 0x10, + SPMI_REGULATOR_SUBTYPE_OTG = 0x11, + SPMI_REGULATOR_SUBTYPE_5V_BOOST = 0x01, + SPMI_REGULATOR_SUBTYPE_FTS_CTL = 0x08, + SPMI_REGULATOR_SUBTYPE_FTS2p5_CTL = 0x09, + SPMI_REGULATOR_SUBTYPE_BB_2A = 0x01, + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL1 = 0x0d, + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL2 = 0x0e, + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL3 = 0x0f, + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL4 = 0x10, +}; + +enum spmi_common_regulator_registers { + SPMI_COMMON_REG_DIG_MAJOR_REV = 0x01, + SPMI_COMMON_REG_TYPE = 0x04, + SPMI_COMMON_REG_SUBTYPE = 0x05, + SPMI_COMMON_REG_VOLTAGE_RANGE = 0x40, + SPMI_COMMON_REG_VOLTAGE_SET = 0x41, + SPMI_COMMON_REG_MODE = 0x45, + SPMI_COMMON_REG_ENABLE = 0x46, + SPMI_COMMON_REG_PULL_DOWN = 0x48, + SPMI_COMMON_REG_SOFT_START = 0x4c, + SPMI_COMMON_REG_STEP_CTRL = 0x61, +}; + +enum spmi_vs_registers { + SPMI_VS_REG_OCP = 0x4a, + SPMI_VS_REG_SOFT_START = 0x4c, +}; + +enum spmi_boost_registers { + SPMI_BOOST_REG_CURRENT_LIMIT = 0x4a, +}; + +enum spmi_boost_byp_registers { + SPMI_BOOST_BYP_REG_CURRENT_LIMIT = 0x4b, +}; + +enum spmi_saw3_registers { + SAW3_SECURE = 0x00, + SAW3_ID = 0x04, + SAW3_SPM_STS = 0x0C, + SAW3_AVS_STS = 0x10, + SAW3_PMIC_STS = 0x14, + SAW3_RST = 0x18, + SAW3_VCTL = 0x1C, + SAW3_AVS_CTL = 0x20, + SAW3_AVS_LIMIT = 0x24, + SAW3_AVS_DLY = 0x28, + SAW3_AVS_HYSTERESIS = 0x2C, + SAW3_SPM_STS2 = 0x38, + SAW3_SPM_PMIC_DATA_3 = 0x4C, + SAW3_VERSION = 0xFD0, +}; + +/* Used for indexing into ctrl_reg. These are offets from 0x40 */ +enum spmi_common_control_register_index { + SPMI_COMMON_IDX_VOLTAGE_RANGE = 0, + SPMI_COMMON_IDX_VOLTAGE_SET = 1, + SPMI_COMMON_IDX_MODE = 5, + SPMI_COMMON_IDX_ENABLE = 6, +}; + +/* Common regulator control register layout */ +#define SPMI_COMMON_ENABLE_MASK 0x80 +#define SPMI_COMMON_ENABLE 0x80 +#define SPMI_COMMON_DISABLE 0x00 +#define SPMI_COMMON_ENABLE_FOLLOW_HW_EN3_MASK 0x08 +#define SPMI_COMMON_ENABLE_FOLLOW_HW_EN2_MASK 0x04 +#define SPMI_COMMON_ENABLE_FOLLOW_HW_EN1_MASK 0x02 +#define SPMI_COMMON_ENABLE_FOLLOW_HW_EN0_MASK 0x01 +#define SPMI_COMMON_ENABLE_FOLLOW_ALL_MASK 0x0f + +/* Common regulator mode register layout */ +#define SPMI_COMMON_MODE_HPM_MASK 0x80 +#define SPMI_COMMON_MODE_AUTO_MASK 0x40 +#define SPMI_COMMON_MODE_BYPASS_MASK 0x20 +#define SPMI_COMMON_MODE_FOLLOW_AWAKE_MASK 0x10 +#define SPMI_COMMON_MODE_FOLLOW_HW_EN3_MASK 0x08 +#define SPMI_COMMON_MODE_FOLLOW_HW_EN2_MASK 0x04 +#define SPMI_COMMON_MODE_FOLLOW_HW_EN1_MASK 0x02 +#define SPMI_COMMON_MODE_FOLLOW_HW_EN0_MASK 0x01 +#define SPMI_COMMON_MODE_FOLLOW_ALL_MASK 0x1f + +/* Common regulator pull down control register layout */ +#define SPMI_COMMON_PULL_DOWN_ENABLE_MASK 0x80 + +/* LDO regulator current limit control register layout */ +#define SPMI_LDO_CURRENT_LIMIT_ENABLE_MASK 0x80 + +/* LDO regulator soft start control register layout */ +#define SPMI_LDO_SOFT_START_ENABLE_MASK 0x80 + +/* VS regulator over current protection control register layout */ +#define SPMI_VS_OCP_OVERRIDE 0x01 +#define SPMI_VS_OCP_NO_OVERRIDE 0x00 + +/* VS regulator soft start control register layout */ +#define SPMI_VS_SOFT_START_ENABLE_MASK 0x80 +#define SPMI_VS_SOFT_START_SEL_MASK 0x03 + +/* Boost regulator current limit control register layout */ +#define SPMI_BOOST_CURRENT_LIMIT_ENABLE_MASK 0x80 +#define SPMI_BOOST_CURRENT_LIMIT_MASK 0x07 + +#define SPMI_VS_OCP_DEFAULT_MAX_RETRIES 10 +#define SPMI_VS_OCP_DEFAULT_RETRY_DELAY_MS 30 +#define SPMI_VS_OCP_FALL_DELAY_US 90 +#define SPMI_VS_OCP_FAULT_DELAY_US 20000 + +#define SPMI_FTSMPS_STEP_CTRL_STEP_MASK 0x18 +#define SPMI_FTSMPS_STEP_CTRL_STEP_SHIFT 3 +#define SPMI_FTSMPS_STEP_CTRL_DELAY_MASK 0x07 +#define SPMI_FTSMPS_STEP_CTRL_DELAY_SHIFT 0 + +/* Clock rate in kHz of the FTSMPS regulator reference clock. */ +#define SPMI_FTSMPS_CLOCK_RATE 19200 + +/* Minimum voltage stepper delay for each step. */ +#define SPMI_FTSMPS_STEP_DELAY 8 +#define SPMI_DEFAULT_STEP_DELAY 20 + +/* + * The ratio SPMI_FTSMPS_STEP_MARGIN_NUM/SPMI_FTSMPS_STEP_MARGIN_DEN is used to + * adjust the step rate in order to account for oscillator variance. + */ +#define SPMI_FTSMPS_STEP_MARGIN_NUM 4 +#define SPMI_FTSMPS_STEP_MARGIN_DEN 5 + +/* VSET value to decide the range of ULT SMPS */ +#define ULT_SMPS_RANGE_SPLIT 0x60 + +/** + * struct spmi_voltage_range - regulator set point voltage mapping description + * @min_uV: Minimum programmable output voltage resulting from + * set point register value 0x00 + * @max_uV: Maximum programmable output voltage + * @step_uV: Output voltage increase resulting from the set point + * register value increasing by 1 + * @set_point_min_uV: Minimum allowed voltage + * @set_point_max_uV: Maximum allowed voltage. This may be tweaked in order + * to pick which range should be used in the case of + * overlapping set points. + * @n_voltages: Number of preferred voltage set points present in this + * range + * @range_sel: Voltage range register value corresponding to this range + * + * The following relationships must be true for the values used in this struct: + * (max_uV - min_uV) % step_uV == 0 + * (set_point_min_uV - min_uV) % step_uV == 0* + * (set_point_max_uV - min_uV) % step_uV == 0* + * n_voltages = (set_point_max_uV - set_point_min_uV) / step_uV + 1 + * + * *Note, set_point_min_uV == set_point_max_uV == 0 is allowed in order to + * specify that the voltage range has meaning, but is not preferred. + */ +struct spmi_voltage_range { + int min_uV; + int max_uV; + int step_uV; + int set_point_min_uV; + int set_point_max_uV; + unsigned n_voltages; + u8 range_sel; +}; + +/* + * The ranges specified in the spmi_voltage_set_points struct must be listed + * so that range[i].set_point_max_uV < range[i+1].set_point_min_uV. + */ +struct spmi_voltage_set_points { + struct spmi_voltage_range *range; + int count; + unsigned n_voltages; +}; + +struct spmi_regulator { + struct regulator_desc desc; + struct device *dev; + struct delayed_work ocp_work; + struct regmap *regmap; + struct spmi_voltage_set_points *set_points; + enum spmi_regulator_logical_type logical_type; + int ocp_irq; + int ocp_count; + int ocp_max_retries; + int ocp_retry_delay_ms; + int hpm_min_load; + int slew_rate; + ktime_t vs_enable_time; + u16 base; + struct list_head node; +}; + +struct spmi_regulator_mapping { + enum spmi_regulator_type type; + enum spmi_regulator_subtype subtype; + enum spmi_regulator_logical_type logical_type; + u32 revision_min; + u32 revision_max; + struct regulator_ops *ops; + struct spmi_voltage_set_points *set_points; + int hpm_min_load; +}; + +struct spmi_regulator_data { + const char *name; + u16 base; + const char *supply; + const char *ocp; + u16 force_type; +}; + +#define SPMI_VREG(_type, _subtype, _dig_major_min, _dig_major_max, \ + _logical_type, _ops_val, _set_points_val, _hpm_min_load) \ + { \ + .type = SPMI_REGULATOR_TYPE_##_type, \ + .subtype = SPMI_REGULATOR_SUBTYPE_##_subtype, \ + .revision_min = _dig_major_min, \ + .revision_max = _dig_major_max, \ + .logical_type = SPMI_REGULATOR_LOGICAL_TYPE_##_logical_type, \ + .ops = &spmi_##_ops_val##_ops, \ + .set_points = &_set_points_val##_set_points, \ + .hpm_min_load = _hpm_min_load, \ + } + +#define SPMI_VREG_VS(_subtype, _dig_major_min, _dig_major_max) \ + { \ + .type = SPMI_REGULATOR_TYPE_VS, \ + .subtype = SPMI_REGULATOR_SUBTYPE_##_subtype, \ + .revision_min = _dig_major_min, \ + .revision_max = _dig_major_max, \ + .logical_type = SPMI_REGULATOR_LOGICAL_TYPE_VS, \ + .ops = &spmi_vs_ops, \ + } + +#define SPMI_VOLTAGE_RANGE(_range_sel, _min_uV, _set_point_min_uV, \ + _set_point_max_uV, _max_uV, _step_uV) \ + { \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .set_point_min_uV = _set_point_min_uV, \ + .set_point_max_uV = _set_point_max_uV, \ + .step_uV = _step_uV, \ + .range_sel = _range_sel, \ + } + +#define DEFINE_SPMI_SET_POINTS(name) \ +struct spmi_voltage_set_points name##_set_points = { \ + .range = name##_ranges, \ + .count = ARRAY_SIZE(name##_ranges), \ +} + +/* + * These tables contain the physically available PMIC regulator voltage setpoint + * ranges. Where two ranges overlap in hardware, one of the ranges is trimmed + * to ensure that the setpoints available to software are monotonically + * increasing and unique. The set_voltage callback functions expect these + * properties to hold. + */ +static struct spmi_voltage_range pldo_ranges[] = { + SPMI_VOLTAGE_RANGE(2, 750000, 750000, 1537500, 1537500, 12500), + SPMI_VOLTAGE_RANGE(3, 1500000, 1550000, 3075000, 3075000, 25000), + SPMI_VOLTAGE_RANGE(4, 1750000, 3100000, 4900000, 4900000, 50000), +}; + +static struct spmi_voltage_range nldo1_ranges[] = { + SPMI_VOLTAGE_RANGE(2, 750000, 750000, 1537500, 1537500, 12500), +}; + +static struct spmi_voltage_range nldo2_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 375000, 0, 0, 1537500, 12500), + SPMI_VOLTAGE_RANGE(1, 375000, 375000, 768750, 768750, 6250), + SPMI_VOLTAGE_RANGE(2, 750000, 775000, 1537500, 1537500, 12500), +}; + +static struct spmi_voltage_range nldo3_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 375000, 375000, 1537500, 1537500, 12500), + SPMI_VOLTAGE_RANGE(1, 375000, 0, 0, 1537500, 12500), + SPMI_VOLTAGE_RANGE(2, 750000, 0, 0, 1537500, 12500), +}; + +static struct spmi_voltage_range ln_ldo_ranges[] = { + SPMI_VOLTAGE_RANGE(1, 690000, 690000, 1110000, 1110000, 60000), + SPMI_VOLTAGE_RANGE(0, 1380000, 1380000, 2220000, 2220000, 120000), +}; + +static struct spmi_voltage_range smps_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 375000, 375000, 1562500, 1562500, 12500), + SPMI_VOLTAGE_RANGE(1, 1550000, 1575000, 3125000, 3125000, 25000), +}; + +static struct spmi_voltage_range ftsmps_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 0, 350000, 1275000, 1275000, 5000), + SPMI_VOLTAGE_RANGE(1, 0, 1280000, 2040000, 2040000, 10000), +}; + +static struct spmi_voltage_range ftsmps2p5_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 80000, 350000, 1355000, 1355000, 5000), + SPMI_VOLTAGE_RANGE(1, 160000, 1360000, 2200000, 2200000, 10000), +}; + +static struct spmi_voltage_range boost_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 4000000, 4000000, 5550000, 5550000, 50000), +}; + +static struct spmi_voltage_range boost_byp_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 2500000, 2500000, 5200000, 5650000, 50000), +}; + +static struct spmi_voltage_range ult_lo_smps_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 375000, 375000, 1562500, 1562500, 12500), + SPMI_VOLTAGE_RANGE(1, 750000, 0, 0, 1525000, 25000), +}; + +static struct spmi_voltage_range ult_ho_smps_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 1550000, 1550000, 2325000, 2325000, 25000), +}; + +static struct spmi_voltage_range ult_nldo_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 375000, 375000, 1537500, 1537500, 12500), +}; + +static struct spmi_voltage_range ult_pldo_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 1750000, 1750000, 3337500, 3337500, 12500), +}; + +static DEFINE_SPMI_SET_POINTS(pldo); +static DEFINE_SPMI_SET_POINTS(nldo1); +static DEFINE_SPMI_SET_POINTS(nldo2); +static DEFINE_SPMI_SET_POINTS(nldo3); +static DEFINE_SPMI_SET_POINTS(ln_ldo); +static DEFINE_SPMI_SET_POINTS(smps); +static DEFINE_SPMI_SET_POINTS(ftsmps); +static DEFINE_SPMI_SET_POINTS(ftsmps2p5); +static DEFINE_SPMI_SET_POINTS(boost); +static DEFINE_SPMI_SET_POINTS(boost_byp); +static DEFINE_SPMI_SET_POINTS(ult_lo_smps); +static DEFINE_SPMI_SET_POINTS(ult_ho_smps); +static DEFINE_SPMI_SET_POINTS(ult_nldo); +static DEFINE_SPMI_SET_POINTS(ult_pldo); + +static inline int spmi_vreg_read(struct spmi_regulator *vreg, u16 addr, u8 *buf, + int len) +{ + return regmap_bulk_read(vreg->regmap, vreg->base + addr, buf, len); +} + +static inline int spmi_vreg_write(struct spmi_regulator *vreg, u16 addr, + u8 *buf, int len) +{ + return regmap_bulk_write(vreg->regmap, vreg->base + addr, buf, len); +} + +static int spmi_vreg_update_bits(struct spmi_regulator *vreg, u16 addr, u8 val, + u8 mask) +{ + return regmap_update_bits(vreg->regmap, vreg->base + addr, mask, val); +} + +static int spmi_regulator_vs_enable(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + + if (vreg->ocp_irq) { + vreg->ocp_count = 0; + vreg->vs_enable_time = ktime_get(); + } + + return regulator_enable_regmap(rdev); +} + +static int spmi_regulator_vs_ocp(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 reg = SPMI_VS_OCP_OVERRIDE; + + return spmi_vreg_write(vreg, SPMI_VS_REG_OCP, ®, 1); +} + +static int spmi_regulator_select_voltage(struct spmi_regulator *vreg, + int min_uV, int max_uV) +{ + const struct spmi_voltage_range *range; + int uV = min_uV; + int lim_min_uV, lim_max_uV, i, range_id, range_max_uV; + int selector, voltage_sel; + + /* Check if request voltage is outside of physically settable range. */ + lim_min_uV = vreg->set_points->range[0].set_point_min_uV; + lim_max_uV = + vreg->set_points->range[vreg->set_points->count - 1].set_point_max_uV; + + if (uV < lim_min_uV && max_uV >= lim_min_uV) + uV = lim_min_uV; + + if (uV < lim_min_uV || uV > lim_max_uV) { + dev_err(vreg->dev, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, lim_min_uV, lim_max_uV); + return -EINVAL; + } + + /* Find the range which uV is inside of. */ + for (i = vreg->set_points->count - 1; i > 0; i--) { + range_max_uV = vreg->set_points->range[i - 1].set_point_max_uV; + if (uV > range_max_uV && range_max_uV > 0) + break; + } + + range_id = i; + range = &vreg->set_points->range[range_id]; + + /* + * Force uV to be an allowed set point by applying a ceiling function to + * the uV value. + */ + voltage_sel = DIV_ROUND_UP(uV - range->min_uV, range->step_uV); + uV = voltage_sel * range->step_uV + range->min_uV; + + if (uV > max_uV) { + dev_err(vreg->dev, + "request v=[%d, %d] cannot be met by any set point; " + "next set point: %d\n", + min_uV, max_uV, uV); + return -EINVAL; + } + + selector = 0; + for (i = 0; i < range_id; i++) + selector += vreg->set_points->range[i].n_voltages; + selector += (uV - range->set_point_min_uV) / range->step_uV; + + return selector; +} + +static int spmi_sw_selector_to_hw(struct spmi_regulator *vreg, + unsigned selector, u8 *range_sel, + u8 *voltage_sel) +{ + const struct spmi_voltage_range *range, *end; + unsigned offset; + + range = vreg->set_points->range; + end = range + vreg->set_points->count; + + for (; range < end; range++) { + if (selector < range->n_voltages) { + /* + * hardware selectors between set point min and real + * min are invalid so we ignore them + */ + offset = range->set_point_min_uV - range->min_uV; + offset /= range->step_uV; + *voltage_sel = selector + offset; + *range_sel = range->range_sel; + return 0; + } + + selector -= range->n_voltages; + } + + return -EINVAL; +} + +static int spmi_hw_selector_to_sw(struct spmi_regulator *vreg, u8 hw_sel, + const struct spmi_voltage_range *range) +{ + unsigned sw_sel = 0; + unsigned offset, max_hw_sel; + const struct spmi_voltage_range *r = vreg->set_points->range; + const struct spmi_voltage_range *end = r + vreg->set_points->count; + + for (; r < end; r++) { + if (r == range && range->n_voltages) { + /* + * hardware selectors between set point min and real + * min and between set point max and real max are + * invalid so we return an error if they're + * programmed into the hardware + */ + offset = range->set_point_min_uV - range->min_uV; + offset /= range->step_uV; + if (hw_sel < offset) + return -EINVAL; + + max_hw_sel = range->set_point_max_uV - range->min_uV; + max_hw_sel /= range->step_uV; + if (hw_sel > max_hw_sel) + return -EINVAL; + + return sw_sel + hw_sel - offset; + } + sw_sel += r->n_voltages; + } + + return -EINVAL; +} + +static const struct spmi_voltage_range * +spmi_regulator_find_range(struct spmi_regulator *vreg) +{ + u8 range_sel; + const struct spmi_voltage_range *range, *end; + + range = vreg->set_points->range; + end = range + vreg->set_points->count; + + spmi_vreg_read(vreg, SPMI_COMMON_REG_VOLTAGE_RANGE, &range_sel, 1); + + for (; range < end; range++) + if (range->range_sel == range_sel) + return range; + + return NULL; +} + +static int spmi_regulator_select_voltage_same_range(struct spmi_regulator *vreg, + int min_uV, int max_uV) +{ + const struct spmi_voltage_range *range; + int uV = min_uV; + int i, selector; + + range = spmi_regulator_find_range(vreg); + if (!range) + goto different_range; + + if (uV < range->min_uV && max_uV >= range->min_uV) + uV = range->min_uV; + + if (uV < range->min_uV || uV > range->max_uV) { + /* Current range doesn't support the requested voltage. */ + goto different_range; + } + + /* + * Force uV to be an allowed set point by applying a ceiling function to + * the uV value. + */ + uV = DIV_ROUND_UP(uV - range->min_uV, range->step_uV); + uV = uV * range->step_uV + range->min_uV; + + if (uV > max_uV) { + /* + * No set point in the current voltage range is within the + * requested min_uV to max_uV range. + */ + goto different_range; + } + + selector = 0; + for (i = 0; i < vreg->set_points->count; i++) { + if (uV >= vreg->set_points->range[i].set_point_min_uV + && uV <= vreg->set_points->range[i].set_point_max_uV) { + selector += + (uV - vreg->set_points->range[i].set_point_min_uV) + / vreg->set_points->range[i].step_uV; + break; + } + + selector += vreg->set_points->range[i].n_voltages; + } + + if (selector >= vreg->set_points->n_voltages) + goto different_range; + + return selector; + +different_range: + return spmi_regulator_select_voltage(vreg, min_uV, max_uV); +} + +static int spmi_regulator_common_map_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + + /* + * Favor staying in the current voltage range if possible. This avoids + * voltage spikes that occur when changing the voltage range. + */ + return spmi_regulator_select_voltage_same_range(vreg, min_uV, max_uV); +} + +static int +spmi_regulator_common_set_voltage(struct regulator_dev *rdev, unsigned selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + int ret; + u8 buf[2]; + u8 range_sel, voltage_sel; + + ret = spmi_sw_selector_to_hw(vreg, selector, &range_sel, &voltage_sel); + if (ret) + return ret; + + buf[0] = range_sel; + buf[1] = voltage_sel; + return spmi_vreg_write(vreg, SPMI_COMMON_REG_VOLTAGE_RANGE, buf, 2); +} + +static int spmi_regulator_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, unsigned int new_selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + const struct spmi_voltage_range *range; + int diff_uV; + + range = spmi_regulator_find_range(vreg); + if (!range) + return -EINVAL; + + diff_uV = abs(new_selector - old_selector) * range->step_uV; + + return DIV_ROUND_UP(diff_uV, vreg->slew_rate); +} + +static int spmi_regulator_common_get_voltage(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + const struct spmi_voltage_range *range; + u8 voltage_sel; + + spmi_vreg_read(vreg, SPMI_COMMON_REG_VOLTAGE_SET, &voltage_sel, 1); + + range = spmi_regulator_find_range(vreg); + if (!range) + return -EINVAL; + + return spmi_hw_selector_to_sw(vreg, voltage_sel, range); +} + +static int spmi_regulator_single_map_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + + return spmi_regulator_select_voltage(vreg, min_uV, max_uV); +} + +static int spmi_regulator_single_range_set_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 sel = selector; + + /* + * Certain types of regulators do not have a range select register so + * only voltage set register needs to be written. + */ + return spmi_vreg_write(vreg, SPMI_COMMON_REG_VOLTAGE_SET, &sel, 1); +} + +static int spmi_regulator_single_range_get_voltage(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 selector; + int ret; + + ret = spmi_vreg_read(vreg, SPMI_COMMON_REG_VOLTAGE_SET, &selector, 1); + if (ret) + return ret; + + return selector; +} + +static int spmi_regulator_ult_lo_smps_set_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + int ret; + u8 range_sel, voltage_sel; + + ret = spmi_sw_selector_to_hw(vreg, selector, &range_sel, &voltage_sel); + if (ret) + return ret; + + /* + * Calculate VSET based on range + * In case of range 0: voltage_sel is a 7 bit value, can be written + * witout any modification. + * In case of range 1: voltage_sel is a 5 bit value, bits[7-5] set to + * [011]. + */ + if (range_sel == 1) + voltage_sel |= ULT_SMPS_RANGE_SPLIT; + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_VOLTAGE_SET, + voltage_sel, 0xff); +} + +static int spmi_regulator_ult_lo_smps_get_voltage(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + const struct spmi_voltage_range *range; + u8 voltage_sel; + + spmi_vreg_read(vreg, SPMI_COMMON_REG_VOLTAGE_SET, &voltage_sel, 1); + + range = spmi_regulator_find_range(vreg); + if (!range) + return -EINVAL; + + if (range->range_sel == 1) + voltage_sel &= ~ULT_SMPS_RANGE_SPLIT; + + return spmi_hw_selector_to_sw(vreg, voltage_sel, range); +} + +static int spmi_regulator_common_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + int uV = 0; + int i; + + if (selector >= vreg->set_points->n_voltages) + return 0; + + for (i = 0; i < vreg->set_points->count; i++) { + if (selector < vreg->set_points->range[i].n_voltages) { + uV = selector * vreg->set_points->range[i].step_uV + + vreg->set_points->range[i].set_point_min_uV; + break; + } + + selector -= vreg->set_points->range[i].n_voltages; + } + + return uV; +} + +static int +spmi_regulator_common_set_bypass(struct regulator_dev *rdev, bool enable) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 mask = SPMI_COMMON_MODE_BYPASS_MASK; + u8 val = 0; + + if (enable) + val = mask; + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_MODE, val, mask); +} + +static int +spmi_regulator_common_get_bypass(struct regulator_dev *rdev, bool *enable) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 val; + int ret; + + ret = spmi_vreg_read(vreg, SPMI_COMMON_REG_MODE, &val, 1); + *enable = val & SPMI_COMMON_MODE_BYPASS_MASK; + + return ret; +} + +static unsigned int spmi_regulator_common_get_mode(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 reg; + + spmi_vreg_read(vreg, SPMI_COMMON_REG_MODE, ®, 1); + + if (reg & SPMI_COMMON_MODE_HPM_MASK) + return REGULATOR_MODE_NORMAL; + + if (reg & SPMI_COMMON_MODE_AUTO_MASK) + return REGULATOR_MODE_FAST; + + return REGULATOR_MODE_IDLE; +} + +static int +spmi_regulator_common_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 mask = SPMI_COMMON_MODE_HPM_MASK | SPMI_COMMON_MODE_AUTO_MASK; + u8 val = 0; + + if (mode == REGULATOR_MODE_NORMAL) + val = SPMI_COMMON_MODE_HPM_MASK; + else if (mode == REGULATOR_MODE_FAST) + val = SPMI_COMMON_MODE_AUTO_MASK; + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_MODE, val, mask); +} + +static int +spmi_regulator_common_set_load(struct regulator_dev *rdev, int load_uA) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + unsigned int mode; + + if (load_uA >= vreg->hpm_min_load) + mode = REGULATOR_MODE_NORMAL; + else + mode = REGULATOR_MODE_IDLE; + + return spmi_regulator_common_set_mode(rdev, mode); +} + +static int spmi_regulator_common_set_pull_down(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + unsigned int mask = SPMI_COMMON_PULL_DOWN_ENABLE_MASK; + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_PULL_DOWN, + mask, mask); +} + +static int spmi_regulator_common_set_soft_start(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + unsigned int mask = SPMI_LDO_SOFT_START_ENABLE_MASK; + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_SOFT_START, + mask, mask); +} + +static int spmi_regulator_set_ilim(struct regulator_dev *rdev, int ilim_uA) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + enum spmi_regulator_logical_type type = vreg->logical_type; + unsigned int current_reg; + u8 reg; + u8 mask = SPMI_BOOST_CURRENT_LIMIT_MASK | + SPMI_BOOST_CURRENT_LIMIT_ENABLE_MASK; + int max = (SPMI_BOOST_CURRENT_LIMIT_MASK + 1) * 500; + + if (type == SPMI_REGULATOR_LOGICAL_TYPE_BOOST) + current_reg = SPMI_BOOST_REG_CURRENT_LIMIT; + else + current_reg = SPMI_BOOST_BYP_REG_CURRENT_LIMIT; + + if (ilim_uA > max || ilim_uA <= 0) + return -EINVAL; + + reg = (ilim_uA - 1) / 500; + reg |= SPMI_BOOST_CURRENT_LIMIT_ENABLE_MASK; + + return spmi_vreg_update_bits(vreg, current_reg, reg, mask); +} + +static int spmi_regulator_vs_clear_ocp(struct spmi_regulator *vreg) +{ + int ret; + + ret = spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_ENABLE, + SPMI_COMMON_DISABLE, SPMI_COMMON_ENABLE_MASK); + + vreg->vs_enable_time = ktime_get(); + + ret = spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_ENABLE, + SPMI_COMMON_ENABLE, SPMI_COMMON_ENABLE_MASK); + + return ret; +} + +static void spmi_regulator_vs_ocp_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct spmi_regulator *vreg + = container_of(dwork, struct spmi_regulator, ocp_work); + + spmi_regulator_vs_clear_ocp(vreg); +} + +static irqreturn_t spmi_regulator_vs_ocp_isr(int irq, void *data) +{ + struct spmi_regulator *vreg = data; + ktime_t ocp_irq_time; + s64 ocp_trigger_delay_us; + + ocp_irq_time = ktime_get(); + ocp_trigger_delay_us = ktime_us_delta(ocp_irq_time, + vreg->vs_enable_time); + + /* + * Reset the OCP count if there is a large delay between switch enable + * and when OCP triggers. This is indicative of a hotplug event as + * opposed to a fault. + */ + if (ocp_trigger_delay_us > SPMI_VS_OCP_FAULT_DELAY_US) + vreg->ocp_count = 0; + + /* Wait for switch output to settle back to 0 V after OCP triggered. */ + udelay(SPMI_VS_OCP_FALL_DELAY_US); + + vreg->ocp_count++; + + if (vreg->ocp_count == 1) { + /* Immediately clear the over current condition. */ + spmi_regulator_vs_clear_ocp(vreg); + } else if (vreg->ocp_count <= vreg->ocp_max_retries) { + /* Schedule the over current clear task to run later. */ + schedule_delayed_work(&vreg->ocp_work, + msecs_to_jiffies(vreg->ocp_retry_delay_ms) + 1); + } else { + dev_err(vreg->dev, + "OCP triggered %d times; no further retries\n", + vreg->ocp_count); + } + + return IRQ_HANDLED; +} + +#define SAW3_VCTL_DATA_MASK 0xFF +#define SAW3_VCTL_CLEAR_MASK 0x700FF +#define SAW3_AVS_CTL_EN_MASK 0x1 +#define SAW3_AVS_CTL_TGGL_MASK 0x8000000 +#define SAW3_AVS_CTL_CLEAR_MASK 0x7efc00 + +static struct regmap *saw_regmap; + +static void spmi_saw_set_vdd(void *data) +{ + u32 vctl, data3, avs_ctl, pmic_sts; + bool avs_enabled = false; + unsigned long timeout; + u8 voltage_sel = *(u8 *)data; + + regmap_read(saw_regmap, SAW3_AVS_CTL, &avs_ctl); + regmap_read(saw_regmap, SAW3_VCTL, &vctl); + regmap_read(saw_regmap, SAW3_SPM_PMIC_DATA_3, &data3); + + /* select the band */ + vctl &= ~SAW3_VCTL_CLEAR_MASK; + vctl |= (u32)voltage_sel; + + data3 &= ~SAW3_VCTL_CLEAR_MASK; + data3 |= (u32)voltage_sel; + + /* If AVS is enabled, switch it off during the voltage change */ + avs_enabled = SAW3_AVS_CTL_EN_MASK & avs_ctl; + if (avs_enabled) { + avs_ctl &= ~SAW3_AVS_CTL_TGGL_MASK; + regmap_write(saw_regmap, SAW3_AVS_CTL, avs_ctl); + } + + regmap_write(saw_regmap, SAW3_RST, 1); + regmap_write(saw_regmap, SAW3_VCTL, vctl); + regmap_write(saw_regmap, SAW3_SPM_PMIC_DATA_3, data3); + + timeout = jiffies + usecs_to_jiffies(100); + do { + regmap_read(saw_regmap, SAW3_PMIC_STS, &pmic_sts); + pmic_sts &= SAW3_VCTL_DATA_MASK; + if (pmic_sts == (u32)voltage_sel) + break; + + cpu_relax(); + + } while (time_before(jiffies, timeout)); + + /* After successful voltage change, switch the AVS back on */ + if (avs_enabled) { + pmic_sts &= 0x3f; + avs_ctl &= ~SAW3_AVS_CTL_CLEAR_MASK; + avs_ctl |= ((pmic_sts - 4) << 10); + avs_ctl |= (pmic_sts << 17); + avs_ctl |= SAW3_AVS_CTL_TGGL_MASK; + regmap_write(saw_regmap, SAW3_AVS_CTL, avs_ctl); + } +} + +static int +spmi_regulator_saw_set_voltage(struct regulator_dev *rdev, unsigned selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + int ret; + u8 range_sel, voltage_sel; + + ret = spmi_sw_selector_to_hw(vreg, selector, &range_sel, &voltage_sel); + if (ret) + return ret; + + if (0 != range_sel) { + dev_dbg(&rdev->dev, "range_sel = %02X voltage_sel = %02X", \ + range_sel, voltage_sel); + return -EINVAL; + } + + /* Always do the SAW register writes on the first CPU */ + return smp_call_function_single(0, spmi_saw_set_vdd, \ + &voltage_sel, true); +} + +static struct regulator_ops spmi_saw_ops = {}; + +static struct regulator_ops spmi_smps_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = spmi_regulator_common_set_voltage, + .set_voltage_time_sel = spmi_regulator_set_voltage_time_sel, + .get_voltage_sel = spmi_regulator_common_get_voltage, + .map_voltage = spmi_regulator_common_map_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_pull_down = spmi_regulator_common_set_pull_down, +}; + +static struct regulator_ops spmi_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = spmi_regulator_common_set_voltage, + .get_voltage_sel = spmi_regulator_common_get_voltage, + .map_voltage = spmi_regulator_common_map_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_bypass = spmi_regulator_common_set_bypass, + .get_bypass = spmi_regulator_common_get_bypass, + .set_pull_down = spmi_regulator_common_set_pull_down, + .set_soft_start = spmi_regulator_common_set_soft_start, +}; + +static struct regulator_ops spmi_ln_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = spmi_regulator_common_set_voltage, + .get_voltage_sel = spmi_regulator_common_get_voltage, + .map_voltage = spmi_regulator_common_map_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_bypass = spmi_regulator_common_set_bypass, + .get_bypass = spmi_regulator_common_get_bypass, +}; + +static struct regulator_ops spmi_vs_ops = { + .enable = spmi_regulator_vs_enable, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_pull_down = spmi_regulator_common_set_pull_down, + .set_soft_start = spmi_regulator_common_set_soft_start, + .set_over_current_protection = spmi_regulator_vs_ocp, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, +}; + +static struct regulator_ops spmi_boost_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = spmi_regulator_single_range_set_voltage, + .get_voltage_sel = spmi_regulator_single_range_get_voltage, + .map_voltage = spmi_regulator_single_map_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_input_current_limit = spmi_regulator_set_ilim, +}; + +static struct regulator_ops spmi_ftsmps_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = spmi_regulator_common_set_voltage, + .set_voltage_time_sel = spmi_regulator_set_voltage_time_sel, + .get_voltage_sel = spmi_regulator_common_get_voltage, + .map_voltage = spmi_regulator_common_map_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_pull_down = spmi_regulator_common_set_pull_down, +}; + +static struct regulator_ops spmi_ult_lo_smps_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = spmi_regulator_ult_lo_smps_set_voltage, + .set_voltage_time_sel = spmi_regulator_set_voltage_time_sel, + .get_voltage_sel = spmi_regulator_ult_lo_smps_get_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_pull_down = spmi_regulator_common_set_pull_down, +}; + +static struct regulator_ops spmi_ult_ho_smps_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = spmi_regulator_single_range_set_voltage, + .set_voltage_time_sel = spmi_regulator_set_voltage_time_sel, + .get_voltage_sel = spmi_regulator_single_range_get_voltage, + .map_voltage = spmi_regulator_single_map_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_pull_down = spmi_regulator_common_set_pull_down, +}; + +static struct regulator_ops spmi_ult_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = spmi_regulator_single_range_set_voltage, + .get_voltage_sel = spmi_regulator_single_range_get_voltage, + .map_voltage = spmi_regulator_single_map_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_bypass = spmi_regulator_common_set_bypass, + .get_bypass = spmi_regulator_common_get_bypass, + .set_pull_down = spmi_regulator_common_set_pull_down, + .set_soft_start = spmi_regulator_common_set_soft_start, +}; + +/* Maximum possible digital major revision value */ +#define INF 0xFF + +static const struct spmi_regulator_mapping supported_regulators[] = { + /* type subtype dig_min dig_max ltype ops setpoints hpm_min */ + SPMI_VREG(BUCK, GP_CTL, 0, INF, SMPS, smps, smps, 100000), + SPMI_VREG(LDO, N300, 0, INF, LDO, ldo, nldo1, 10000), + SPMI_VREG(LDO, N600, 0, 0, LDO, ldo, nldo2, 10000), + SPMI_VREG(LDO, N1200, 0, 0, LDO, ldo, nldo2, 10000), + SPMI_VREG(LDO, N600, 1, INF, LDO, ldo, nldo3, 10000), + SPMI_VREG(LDO, N1200, 1, INF, LDO, ldo, nldo3, 10000), + SPMI_VREG(LDO, N600_ST, 0, 0, LDO, ldo, nldo2, 10000), + SPMI_VREG(LDO, N1200_ST, 0, 0, LDO, ldo, nldo2, 10000), + SPMI_VREG(LDO, N600_ST, 1, INF, LDO, ldo, nldo3, 10000), + SPMI_VREG(LDO, N1200_ST, 1, INF, LDO, ldo, nldo3, 10000), + SPMI_VREG(LDO, P50, 0, INF, LDO, ldo, pldo, 5000), + SPMI_VREG(LDO, P150, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, P300, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, P600, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, P1200, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, LN, 0, INF, LN_LDO, ln_ldo, ln_ldo, 0), + SPMI_VREG(LDO, LV_P50, 0, INF, LDO, ldo, pldo, 5000), + SPMI_VREG(LDO, LV_P150, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, LV_P300, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, LV_P600, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, LV_P1200, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG_VS(LV100, 0, INF), + SPMI_VREG_VS(LV300, 0, INF), + SPMI_VREG_VS(MV300, 0, INF), + SPMI_VREG_VS(MV500, 0, INF), + SPMI_VREG_VS(HDMI, 0, INF), + SPMI_VREG_VS(OTG, 0, INF), + SPMI_VREG(BOOST, 5V_BOOST, 0, INF, BOOST, boost, boost, 0), + SPMI_VREG(FTS, FTS_CTL, 0, INF, FTSMPS, ftsmps, ftsmps, 100000), + SPMI_VREG(FTS, FTS2p5_CTL, 0, INF, FTSMPS, ftsmps, ftsmps2p5, 100000), + SPMI_VREG(BOOST_BYP, BB_2A, 0, INF, BOOST_BYP, boost, boost_byp, 0), + SPMI_VREG(ULT_BUCK, ULT_HF_CTL1, 0, INF, ULT_LO_SMPS, ult_lo_smps, + ult_lo_smps, 100000), + SPMI_VREG(ULT_BUCK, ULT_HF_CTL2, 0, INF, ULT_LO_SMPS, ult_lo_smps, + ult_lo_smps, 100000), + SPMI_VREG(ULT_BUCK, ULT_HF_CTL3, 0, INF, ULT_LO_SMPS, ult_lo_smps, + ult_lo_smps, 100000), + SPMI_VREG(ULT_BUCK, ULT_HF_CTL4, 0, INF, ULT_HO_SMPS, ult_ho_smps, + ult_ho_smps, 100000), + SPMI_VREG(ULT_LDO, N300_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo, 10000), + SPMI_VREG(ULT_LDO, N600_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo, 10000), + SPMI_VREG(ULT_LDO, N900_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo, 10000), + SPMI_VREG(ULT_LDO, N1200_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo, 10000), + SPMI_VREG(ULT_LDO, LV_P150, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 10000), + SPMI_VREG(ULT_LDO, LV_P300, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 10000), + SPMI_VREG(ULT_LDO, LV_P450, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 10000), + SPMI_VREG(ULT_LDO, P600, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 10000), + SPMI_VREG(ULT_LDO, P150, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 10000), + SPMI_VREG(ULT_LDO, P50, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 5000), +}; + +static void spmi_calculate_num_voltages(struct spmi_voltage_set_points *points) +{ + unsigned int n; + struct spmi_voltage_range *range = points->range; + + for (; range < points->range + points->count; range++) { + n = 0; + if (range->set_point_max_uV) { + n = range->set_point_max_uV - range->set_point_min_uV; + n = (n / range->step_uV) + 1; + } + range->n_voltages = n; + points->n_voltages += n; + } +} + +static int spmi_regulator_match(struct spmi_regulator *vreg, u16 force_type) +{ + const struct spmi_regulator_mapping *mapping; + int ret, i; + u32 dig_major_rev; + u8 version[SPMI_COMMON_REG_SUBTYPE - SPMI_COMMON_REG_DIG_MAJOR_REV + 1]; + u8 type, subtype; + + ret = spmi_vreg_read(vreg, SPMI_COMMON_REG_DIG_MAJOR_REV, version, + ARRAY_SIZE(version)); + if (ret) { + dev_dbg(vreg->dev, "could not read version registers\n"); + return ret; + } + dig_major_rev = version[SPMI_COMMON_REG_DIG_MAJOR_REV + - SPMI_COMMON_REG_DIG_MAJOR_REV]; + + if (!force_type) { + type = version[SPMI_COMMON_REG_TYPE - + SPMI_COMMON_REG_DIG_MAJOR_REV]; + subtype = version[SPMI_COMMON_REG_SUBTYPE - + SPMI_COMMON_REG_DIG_MAJOR_REV]; + } else { + type = force_type >> 8; + subtype = force_type; + } + + for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) { + mapping = &supported_regulators[i]; + if (mapping->type == type && mapping->subtype == subtype + && mapping->revision_min <= dig_major_rev + && mapping->revision_max >= dig_major_rev) + goto found; + } + + dev_err(vreg->dev, + "unsupported regulator: name=%s type=0x%02X, subtype=0x%02X, dig major rev=0x%02X\n", + vreg->desc.name, type, subtype, dig_major_rev); + + return -ENODEV; + +found: + vreg->logical_type = mapping->logical_type; + vreg->set_points = mapping->set_points; + vreg->hpm_min_load = mapping->hpm_min_load; + vreg->desc.ops = mapping->ops; + + if (mapping->set_points) { + if (!mapping->set_points->n_voltages) + spmi_calculate_num_voltages(mapping->set_points); + vreg->desc.n_voltages = mapping->set_points->n_voltages; + } + + return 0; +} + +static int spmi_regulator_init_slew_rate(struct spmi_regulator *vreg) +{ + int ret; + u8 reg = 0; + int step, delay, slew_rate, step_delay; + const struct spmi_voltage_range *range; + + ret = spmi_vreg_read(vreg, SPMI_COMMON_REG_STEP_CTRL, ®, 1); + if (ret) { + dev_err(vreg->dev, "spmi read failed, ret=%d\n", ret); + return ret; + } + + range = spmi_regulator_find_range(vreg); + if (!range) + return -EINVAL; + + switch (vreg->logical_type) { + case SPMI_REGULATOR_LOGICAL_TYPE_FTSMPS: + step_delay = SPMI_FTSMPS_STEP_DELAY; + break; + default: + step_delay = SPMI_DEFAULT_STEP_DELAY; + break; + } + + step = reg & SPMI_FTSMPS_STEP_CTRL_STEP_MASK; + step >>= SPMI_FTSMPS_STEP_CTRL_STEP_SHIFT; + + delay = reg & SPMI_FTSMPS_STEP_CTRL_DELAY_MASK; + delay >>= SPMI_FTSMPS_STEP_CTRL_DELAY_SHIFT; + + /* slew_rate has units of uV/us */ + slew_rate = SPMI_FTSMPS_CLOCK_RATE * range->step_uV * (1 << step); + slew_rate /= 1000 * (step_delay << delay); + slew_rate *= SPMI_FTSMPS_STEP_MARGIN_NUM; + slew_rate /= SPMI_FTSMPS_STEP_MARGIN_DEN; + + /* Ensure that the slew rate is greater than 0 */ + vreg->slew_rate = max(slew_rate, 1); + + return ret; +} + +static int spmi_regulator_init_registers(struct spmi_regulator *vreg, + const struct spmi_regulator_init_data *data) +{ + int ret; + enum spmi_regulator_logical_type type; + u8 ctrl_reg[8], reg, mask; + + type = vreg->logical_type; + + ret = spmi_vreg_read(vreg, SPMI_COMMON_REG_VOLTAGE_RANGE, ctrl_reg, 8); + if (ret) + return ret; + + /* Set up enable pin control. */ + if ((type == SPMI_REGULATOR_LOGICAL_TYPE_SMPS + || type == SPMI_REGULATOR_LOGICAL_TYPE_LDO + || type == SPMI_REGULATOR_LOGICAL_TYPE_VS) + && !(data->pin_ctrl_enable + & SPMI_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT)) { + ctrl_reg[SPMI_COMMON_IDX_ENABLE] &= + ~SPMI_COMMON_ENABLE_FOLLOW_ALL_MASK; + ctrl_reg[SPMI_COMMON_IDX_ENABLE] |= + data->pin_ctrl_enable & SPMI_COMMON_ENABLE_FOLLOW_ALL_MASK; + } + + /* Set up mode pin control. */ + if ((type == SPMI_REGULATOR_LOGICAL_TYPE_SMPS + || type == SPMI_REGULATOR_LOGICAL_TYPE_LDO) + && !(data->pin_ctrl_hpm + & SPMI_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT)) { + ctrl_reg[SPMI_COMMON_IDX_MODE] &= + ~SPMI_COMMON_MODE_FOLLOW_ALL_MASK; + ctrl_reg[SPMI_COMMON_IDX_MODE] |= + data->pin_ctrl_hpm & SPMI_COMMON_MODE_FOLLOW_ALL_MASK; + } + + if (type == SPMI_REGULATOR_LOGICAL_TYPE_VS + && !(data->pin_ctrl_hpm & SPMI_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT)) { + ctrl_reg[SPMI_COMMON_IDX_MODE] &= + ~SPMI_COMMON_MODE_FOLLOW_AWAKE_MASK; + ctrl_reg[SPMI_COMMON_IDX_MODE] |= + data->pin_ctrl_hpm & SPMI_COMMON_MODE_FOLLOW_AWAKE_MASK; + } + + if ((type == SPMI_REGULATOR_LOGICAL_TYPE_ULT_LO_SMPS + || type == SPMI_REGULATOR_LOGICAL_TYPE_ULT_HO_SMPS + || type == SPMI_REGULATOR_LOGICAL_TYPE_ULT_LDO) + && !(data->pin_ctrl_hpm + & SPMI_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT)) { + ctrl_reg[SPMI_COMMON_IDX_MODE] &= + ~SPMI_COMMON_MODE_FOLLOW_AWAKE_MASK; + ctrl_reg[SPMI_COMMON_IDX_MODE] |= + data->pin_ctrl_hpm & SPMI_COMMON_MODE_FOLLOW_AWAKE_MASK; + } + + /* Write back any control register values that were modified. */ + ret = spmi_vreg_write(vreg, SPMI_COMMON_REG_VOLTAGE_RANGE, ctrl_reg, 8); + if (ret) + return ret; + + /* Set soft start strength and over current protection for VS. */ + if (type == SPMI_REGULATOR_LOGICAL_TYPE_VS) { + if (data->vs_soft_start_strength + != SPMI_VS_SOFT_START_STR_HW_DEFAULT) { + reg = data->vs_soft_start_strength + & SPMI_VS_SOFT_START_SEL_MASK; + mask = SPMI_VS_SOFT_START_SEL_MASK; + return spmi_vreg_update_bits(vreg, + SPMI_VS_REG_SOFT_START, + reg, mask); + } + } + + return 0; +} + +static void spmi_regulator_get_dt_config(struct spmi_regulator *vreg, + struct device_node *node, struct spmi_regulator_init_data *data) +{ + /* + * Initialize configuration parameters to use hardware default in case + * no value is specified via device tree. + */ + data->pin_ctrl_enable = SPMI_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT; + data->pin_ctrl_hpm = SPMI_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT; + data->vs_soft_start_strength = SPMI_VS_SOFT_START_STR_HW_DEFAULT; + + /* These bindings are optional, so it is okay if they aren't found. */ + of_property_read_u32(node, "qcom,ocp-max-retries", + &vreg->ocp_max_retries); + of_property_read_u32(node, "qcom,ocp-retry-delay", + &vreg->ocp_retry_delay_ms); + of_property_read_u32(node, "qcom,pin-ctrl-enable", + &data->pin_ctrl_enable); + of_property_read_u32(node, "qcom,pin-ctrl-hpm", &data->pin_ctrl_hpm); + of_property_read_u32(node, "qcom,vs-soft-start-strength", + &data->vs_soft_start_strength); +} + +static unsigned int spmi_regulator_of_map_mode(unsigned int mode) +{ + if (mode == 1) + return REGULATOR_MODE_NORMAL; + if (mode == 2) + return REGULATOR_MODE_FAST; + + return REGULATOR_MODE_IDLE; +} + +static int spmi_regulator_of_parse(struct device_node *node, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct spmi_regulator_init_data data = { }; + struct spmi_regulator *vreg = config->driver_data; + struct device *dev = config->dev; + int ret; + + spmi_regulator_get_dt_config(vreg, node, &data); + + if (!vreg->ocp_max_retries) + vreg->ocp_max_retries = SPMI_VS_OCP_DEFAULT_MAX_RETRIES; + if (!vreg->ocp_retry_delay_ms) + vreg->ocp_retry_delay_ms = SPMI_VS_OCP_DEFAULT_RETRY_DELAY_MS; + + ret = spmi_regulator_init_registers(vreg, &data); + if (ret) { + dev_err(dev, "common initialization failed, ret=%d\n", ret); + return ret; + } + + switch (vreg->logical_type) { + case SPMI_REGULATOR_LOGICAL_TYPE_FTSMPS: + case SPMI_REGULATOR_LOGICAL_TYPE_ULT_LO_SMPS: + case SPMI_REGULATOR_LOGICAL_TYPE_ULT_HO_SMPS: + case SPMI_REGULATOR_LOGICAL_TYPE_SMPS: + ret = spmi_regulator_init_slew_rate(vreg); + if (ret) + return ret; + default: + break; + } + + if (vreg->logical_type != SPMI_REGULATOR_LOGICAL_TYPE_VS) + vreg->ocp_irq = 0; + + if (vreg->ocp_irq) { + ret = devm_request_irq(dev, vreg->ocp_irq, + spmi_regulator_vs_ocp_isr, IRQF_TRIGGER_RISING, "ocp", + vreg); + if (ret < 0) { + dev_err(dev, "failed to request irq %d, ret=%d\n", + vreg->ocp_irq, ret); + return ret; + } + + INIT_DELAYED_WORK(&vreg->ocp_work, spmi_regulator_vs_ocp_work); + } + + return 0; +} + +static const struct spmi_regulator_data pm8941_regulators[] = { + { "s1", 0x1400, "vdd_s1", }, + { "s2", 0x1700, "vdd_s2", }, + { "s3", 0x1a00, "vdd_s3", }, + { "s4", 0xa000, }, + { "l1", 0x4000, "vdd_l1_l3", }, + { "l2", 0x4100, "vdd_l2_lvs_1_2_3", }, + { "l3", 0x4200, "vdd_l1_l3", }, + { "l4", 0x4300, "vdd_l4_l11", }, + { "l5", 0x4400, "vdd_l5_l7", NULL, 0x0410 }, + { "l6", 0x4500, "vdd_l6_l12_l14_l15", }, + { "l7", 0x4600, "vdd_l5_l7", NULL, 0x0410 }, + { "l8", 0x4700, "vdd_l8_l16_l18_19", }, + { "l9", 0x4800, "vdd_l9_l10_l17_l22", }, + { "l10", 0x4900, "vdd_l9_l10_l17_l22", }, + { "l11", 0x4a00, "vdd_l4_l11", }, + { "l12", 0x4b00, "vdd_l6_l12_l14_l15", }, + { "l13", 0x4c00, "vdd_l13_l20_l23_l24", }, + { "l14", 0x4d00, "vdd_l6_l12_l14_l15", }, + { "l15", 0x4e00, "vdd_l6_l12_l14_l15", }, + { "l16", 0x4f00, "vdd_l8_l16_l18_19", }, + { "l17", 0x5000, "vdd_l9_l10_l17_l22", }, + { "l18", 0x5100, "vdd_l8_l16_l18_19", }, + { "l19", 0x5200, "vdd_l8_l16_l18_19", }, + { "l20", 0x5300, "vdd_l13_l20_l23_l24", }, + { "l21", 0x5400, "vdd_l21", }, + { "l22", 0x5500, "vdd_l9_l10_l17_l22", }, + { "l23", 0x5600, "vdd_l13_l20_l23_l24", }, + { "l24", 0x5700, "vdd_l13_l20_l23_l24", }, + { "lvs1", 0x8000, "vdd_l2_lvs_1_2_3", }, + { "lvs2", 0x8100, "vdd_l2_lvs_1_2_3", }, + { "lvs3", 0x8200, "vdd_l2_lvs_1_2_3", }, + { "5vs1", 0x8300, "vin_5vs", "ocp-5vs1", }, + { "5vs2", 0x8400, "vin_5vs", "ocp-5vs2", }, + { } +}; + +static const struct spmi_regulator_data pm8841_regulators[] = { + { "s1", 0x1400, "vdd_s1", }, + { "s2", 0x1700, "vdd_s2", NULL, 0x1c08 }, + { "s3", 0x1a00, "vdd_s3", }, + { "s4", 0x1d00, "vdd_s4", NULL, 0x1c08 }, + { "s5", 0x2000, "vdd_s5", NULL, 0x1c08 }, + { "s6", 0x2300, "vdd_s6", NULL, 0x1c08 }, + { "s7", 0x2600, "vdd_s7", NULL, 0x1c08 }, + { "s8", 0x2900, "vdd_s8", NULL, 0x1c08 }, + { } +}; + +static const struct spmi_regulator_data pm8916_regulators[] = { + { "s1", 0x1400, "vdd_s1", }, + { "s2", 0x1700, "vdd_s2", }, + { "s3", 0x1a00, "vdd_s3", }, + { "s4", 0x1d00, "vdd_s4", }, + { "l1", 0x4000, "vdd_l1_l3", }, + { "l2", 0x4100, "vdd_l2", }, + { "l3", 0x4200, "vdd_l1_l3", }, + { "l4", 0x4300, "vdd_l4_l5_l6", }, + { "l5", 0x4400, "vdd_l4_l5_l6", }, + { "l6", 0x4500, "vdd_l4_l5_l6", }, + { "l7", 0x4600, "vdd_l7", }, + { "l8", 0x4700, "vdd_l8_l11_l14_l15_l16", }, + { "l9", 0x4800, "vdd_l9_l10_l12_l13_l17_l18", }, + { "l10", 0x4900, "vdd_l9_l10_l12_l13_l17_l18", }, + { "l11", 0x4a00, "vdd_l8_l11_l14_l15_l16", }, + { "l12", 0x4b00, "vdd_l9_l10_l12_l13_l17_l18", }, + { "l13", 0x4c00, "vdd_l9_l10_l12_l13_l17_l18", }, + { "l14", 0x4d00, "vdd_l8_l11_l14_l15_l16", }, + { "l15", 0x4e00, "vdd_l8_l11_l14_l15_l16", }, + { "l16", 0x4f00, "vdd_l8_l11_l14_l15_l16", }, + { "l17", 0x5000, "vdd_l9_l10_l12_l13_l17_l18", }, + { "l18", 0x5100, "vdd_l9_l10_l12_l13_l17_l18", }, + { } +}; + +static const struct spmi_regulator_data pm8994_regulators[] = { + { "s1", 0x1400, "vdd_s1", }, + { "s2", 0x1700, "vdd_s2", }, + { "s3", 0x1a00, "vdd_s3", }, + { "s4", 0x1d00, "vdd_s4", }, + { "s5", 0x2000, "vdd_s5", }, + { "s6", 0x2300, "vdd_s6", }, + { "s7", 0x2600, "vdd_s7", }, + { "s8", 0x2900, "vdd_s8", }, + { "s9", 0x2c00, "vdd_s9", }, + { "s10", 0x2f00, "vdd_s10", }, + { "s11", 0x3200, "vdd_s11", }, + { "s12", 0x3500, "vdd_s12", }, + { "l1", 0x4000, "vdd_l1", }, + { "l2", 0x4100, "vdd_l2_l26_l28", }, + { "l3", 0x4200, "vdd_l3_l11", }, + { "l4", 0x4300, "vdd_l4_l27_l31", }, + { "l5", 0x4400, "vdd_l5_l7", }, + { "l6", 0x4500, "vdd_l6_l12_l32", }, + { "l7", 0x4600, "vdd_l5_l7", }, + { "l8", 0x4700, "vdd_l8_l16_l30", }, + { "l9", 0x4800, "vdd_l9_l10_l18_l22", }, + { "l10", 0x4900, "vdd_l9_l10_l18_l22", }, + { "l11", 0x4a00, "vdd_l3_l11", }, + { "l12", 0x4b00, "vdd_l6_l12_l32", }, + { "l13", 0x4c00, "vdd_l13_l19_l23_l24", }, + { "l14", 0x4d00, "vdd_l14_l15", }, + { "l15", 0x4e00, "vdd_l14_l15", }, + { "l16", 0x4f00, "vdd_l8_l16_l30", }, + { "l17", 0x5000, "vdd_l17_l29", }, + { "l18", 0x5100, "vdd_l9_l10_l18_l22", }, + { "l19", 0x5200, "vdd_l13_l19_l23_l24", }, + { "l20", 0x5300, "vdd_l20_l21", }, + { "l21", 0x5400, "vdd_l20_l21", }, + { "l22", 0x5500, "vdd_l9_l10_l18_l22", }, + { "l23", 0x5600, "vdd_l13_l19_l23_l24", }, + { "l24", 0x5700, "vdd_l13_l19_l23_l24", }, + { "l25", 0x5800, "vdd_l25", }, + { "l26", 0x5900, "vdd_l2_l26_l28", }, + { "l27", 0x5a00, "vdd_l4_l27_l31", }, + { "l28", 0x5b00, "vdd_l2_l26_l28", }, + { "l29", 0x5c00, "vdd_l17_l29", }, + { "l30", 0x5d00, "vdd_l8_l16_l30", }, + { "l31", 0x5e00, "vdd_l4_l27_l31", }, + { "l32", 0x5f00, "vdd_l6_l12_l32", }, + { "lvs1", 0x8000, "vdd_lvs_1_2", }, + { "lvs2", 0x8100, "vdd_lvs_1_2", }, + { } +}; + +static const struct spmi_regulator_data pmi8994_regulators[] = { + { "s1", 0x1400, "vdd_s1", }, + { "s2", 0x1700, "vdd_s2", }, + { "s3", 0x1a00, "vdd_s3", }, + { "l1", 0x4000, "vdd_l1", }, + { } +}; + +static const struct of_device_id qcom_spmi_regulator_match[] = { + { .compatible = "qcom,pm8841-regulators", .data = &pm8841_regulators }, + { .compatible = "qcom,pm8916-regulators", .data = &pm8916_regulators }, + { .compatible = "qcom,pm8941-regulators", .data = &pm8941_regulators }, + { .compatible = "qcom,pm8994-regulators", .data = &pm8994_regulators }, + { .compatible = "qcom,pmi8994-regulators", .data = &pmi8994_regulators }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_spmi_regulator_match); + +static int qcom_spmi_regulator_probe(struct platform_device *pdev) +{ + const struct spmi_regulator_data *reg; + const struct of_device_id *match; + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct spmi_regulator *vreg; + struct regmap *regmap; + const char *name; + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct device_node *syscon, *reg_node; + struct property *reg_prop; + int ret, lenp; + struct list_head *vreg_list; + + vreg_list = devm_kzalloc(dev, sizeof(*vreg_list), GFP_KERNEL); + if (!vreg_list) + return -ENOMEM; + INIT_LIST_HEAD(vreg_list); + platform_set_drvdata(pdev, vreg_list); + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + match = of_match_device(qcom_spmi_regulator_match, &pdev->dev); + if (!match) + return -ENODEV; + + if (of_find_property(node, "qcom,saw-reg", &lenp)) { + syscon = of_parse_phandle(node, "qcom,saw-reg", 0); + saw_regmap = syscon_node_to_regmap(syscon); + of_node_put(syscon); + if (IS_ERR(saw_regmap)) + dev_err(dev, "ERROR reading SAW regmap\n"); + } + + for (reg = match->data; reg->name; reg++) { + + if (saw_regmap) { + reg_node = of_get_child_by_name(node, reg->name); + reg_prop = of_find_property(reg_node, "qcom,saw-slave", + &lenp); + of_node_put(reg_node); + if (reg_prop) + continue; + } + + vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL); + if (!vreg) + return -ENOMEM; + + vreg->dev = dev; + vreg->base = reg->base; + vreg->regmap = regmap; + if (reg->ocp) { + vreg->ocp_irq = platform_get_irq_byname(pdev, reg->ocp); + if (vreg->ocp_irq < 0) { + ret = vreg->ocp_irq; + goto err; + } + } + vreg->desc.id = -1; + vreg->desc.owner = THIS_MODULE; + vreg->desc.type = REGULATOR_VOLTAGE; + vreg->desc.enable_reg = reg->base + SPMI_COMMON_REG_ENABLE; + vreg->desc.enable_mask = SPMI_COMMON_ENABLE_MASK; + vreg->desc.enable_val = SPMI_COMMON_ENABLE; + vreg->desc.name = name = reg->name; + vreg->desc.supply_name = reg->supply; + vreg->desc.of_match = reg->name; + vreg->desc.of_parse_cb = spmi_regulator_of_parse; + vreg->desc.of_map_mode = spmi_regulator_of_map_mode; + + ret = spmi_regulator_match(vreg, reg->force_type); + if (ret) + continue; + + if (saw_regmap) { + reg_node = of_get_child_by_name(node, reg->name); + reg_prop = of_find_property(reg_node, "qcom,saw-leader", + &lenp); + of_node_put(reg_node); + if (reg_prop) { + spmi_saw_ops = *(vreg->desc.ops); + spmi_saw_ops.set_voltage_sel = + spmi_regulator_saw_set_voltage; + vreg->desc.ops = &spmi_saw_ops; + } + } + + config.dev = dev; + config.driver_data = vreg; + config.regmap = regmap; + rdev = devm_regulator_register(dev, &vreg->desc, &config); + if (IS_ERR(rdev)) { + dev_err(dev, "failed to register %s\n", name); + ret = PTR_ERR(rdev); + goto err; + } + + INIT_LIST_HEAD(&vreg->node); + list_add(&vreg->node, vreg_list); + } + + return 0; + +err: + list_for_each_entry(vreg, vreg_list, node) + if (vreg->ocp_irq) + cancel_delayed_work_sync(&vreg->ocp_work); + return ret; +} + +static int qcom_spmi_regulator_remove(struct platform_device *pdev) +{ + struct spmi_regulator *vreg; + struct list_head *vreg_list = platform_get_drvdata(pdev); + + list_for_each_entry(vreg, vreg_list, node) + if (vreg->ocp_irq) + cancel_delayed_work_sync(&vreg->ocp_work); + + return 0; +} + +static struct platform_driver qcom_spmi_regulator_driver = { + .driver = { + .name = "qcom-spmi-regulator", + .of_match_table = qcom_spmi_regulator_match, + }, + .probe = qcom_spmi_regulator_probe, + .remove = qcom_spmi_regulator_remove, +}; +module_platform_driver(qcom_spmi_regulator_driver); + +MODULE_DESCRIPTION("Qualcomm SPMI PMIC regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:qcom-spmi-regulator"); diff --git a/drivers/regulator/rc5t583-regulator.c b/drivers/regulator/rc5t583-regulator.c new file mode 100644 index 000000000..2ec51af43 --- /dev/null +++ b/drivers/regulator/rc5t583-regulator.c @@ -0,0 +1,203 @@ +/* + * Regulator driver for RICOH RC5T583 power management chip. + * + * Copyright (c) 2011-2012, NVIDIA CORPORATION. All rights reserved. + * Author: Laxman dewangan <ldewangan@nvidia.com> + * + * based on code + * Copyright (C) 2011 RICOH COMPANY,LTD + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/gpio.h> +#include <linux/mfd/rc5t583.h> + +struct rc5t583_regulator_info { + int deepsleep_id; + + /* Regulator register address.*/ + uint8_t reg_disc_reg; + uint8_t disc_bit; + uint8_t deepsleep_reg; + + /* Regulator specific turn-on delay and voltage settling time*/ + int enable_uv_per_us; + + /* Used by regulator core */ + struct regulator_desc desc; +}; + +struct rc5t583_regulator { + struct rc5t583_regulator_info *reg_info; + struct regulator_dev *rdev; +}; + +static int rc5t583_regulator_enable_time(struct regulator_dev *rdev) +{ + struct rc5t583_regulator *reg = rdev_get_drvdata(rdev); + int vsel = regulator_get_voltage_sel_regmap(rdev); + int curr_uV = regulator_list_voltage_linear(rdev, vsel); + + return DIV_ROUND_UP(curr_uV, reg->reg_info->enable_uv_per_us); +} + +static const struct regulator_ops rc5t583_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .enable_time = rc5t583_regulator_enable_time, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_voltage_time_sel = regulator_set_voltage_time_sel, +}; + +#define RC5T583_REG(_id, _en_reg, _en_bit, _disc_reg, _disc_bit, \ + _vout_mask, _min_mv, _max_mv, _step_uV, _enable_mv) \ +{ \ + .reg_disc_reg = RC5T583_REG_##_disc_reg, \ + .disc_bit = _disc_bit, \ + .deepsleep_reg = RC5T583_REG_##_id##DAC_DS, \ + .enable_uv_per_us = _enable_mv * 1000, \ + .deepsleep_id = RC5T583_DS_##_id, \ + .desc = { \ + .name = "rc5t583-regulator-"#_id, \ + .id = RC5T583_REGULATOR_##_id, \ + .n_voltages = (_max_mv - _min_mv) * 1000 / _step_uV + 1, \ + .ops = &rc5t583_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .vsel_reg = RC5T583_REG_##_id##DAC, \ + .vsel_mask = _vout_mask, \ + .enable_reg = RC5T583_REG_##_en_reg, \ + .enable_mask = BIT(_en_bit), \ + .min_uV = _min_mv * 1000, \ + .uV_step = _step_uV, \ + .ramp_delay = 40 * 1000, \ + }, \ +} + +static struct rc5t583_regulator_info rc5t583_reg_info[RC5T583_REGULATOR_MAX] = { + RC5T583_REG(DC0, DC0CTL, 0, DC0CTL, 1, 0x7F, 700, 1500, 12500, 4), + RC5T583_REG(DC1, DC1CTL, 0, DC1CTL, 1, 0x7F, 700, 1500, 12500, 14), + RC5T583_REG(DC2, DC2CTL, 0, DC2CTL, 1, 0x7F, 900, 2400, 12500, 14), + RC5T583_REG(DC3, DC3CTL, 0, DC3CTL, 1, 0x7F, 900, 2400, 12500, 14), + RC5T583_REG(LDO0, LDOEN2, 0, LDODIS2, 0, 0x7F, 900, 3400, 25000, 160), + RC5T583_REG(LDO1, LDOEN2, 1, LDODIS2, 1, 0x7F, 900, 3400, 25000, 160), + RC5T583_REG(LDO2, LDOEN2, 2, LDODIS2, 2, 0x7F, 900, 3400, 25000, 160), + RC5T583_REG(LDO3, LDOEN2, 3, LDODIS2, 3, 0x7F, 900, 3400, 25000, 160), + RC5T583_REG(LDO4, LDOEN2, 4, LDODIS2, 4, 0x3F, 750, 1500, 12500, 133), + RC5T583_REG(LDO5, LDOEN2, 5, LDODIS2, 5, 0x7F, 900, 3400, 25000, 267), + RC5T583_REG(LDO6, LDOEN2, 6, LDODIS2, 6, 0x7F, 900, 3400, 25000, 133), + RC5T583_REG(LDO7, LDOEN2, 7, LDODIS2, 7, 0x7F, 900, 3400, 25000, 233), + RC5T583_REG(LDO8, LDOEN1, 0, LDODIS1, 0, 0x7F, 900, 3400, 25000, 233), + RC5T583_REG(LDO9, LDOEN1, 1, LDODIS1, 1, 0x7F, 900, 3400, 25000, 133), +}; + +static int rc5t583_regulator_probe(struct platform_device *pdev) +{ + struct rc5t583 *rc5t583 = dev_get_drvdata(pdev->dev.parent); + struct rc5t583_platform_data *pdata = dev_get_platdata(rc5t583->dev); + struct regulator_config config = { }; + struct rc5t583_regulator *reg = NULL; + struct rc5t583_regulator *regs; + struct regulator_dev *rdev; + struct rc5t583_regulator_info *ri; + int ret; + int id; + + if (!pdata) { + dev_err(&pdev->dev, "No platform data, exiting...\n"); + return -ENODEV; + } + + regs = devm_kcalloc(&pdev->dev, + RC5T583_REGULATOR_MAX, + sizeof(struct rc5t583_regulator), + GFP_KERNEL); + if (!regs) + return -ENOMEM; + + + for (id = 0; id < RC5T583_REGULATOR_MAX; ++id) { + reg = ®s[id]; + ri = &rc5t583_reg_info[id]; + reg->reg_info = ri; + + if (ri->deepsleep_id == RC5T583_DS_NONE) + goto skip_ext_pwr_config; + + ret = rc5t583_ext_power_req_config(rc5t583->dev, + ri->deepsleep_id, + pdata->regulator_ext_pwr_control[id], + pdata->regulator_deepsleep_slot[id]); + /* + * Configuring external control is not a major issue, + * just give warning. + */ + if (ret < 0) + dev_warn(&pdev->dev, + "Failed to configure ext control %d\n", id); + +skip_ext_pwr_config: + config.dev = &pdev->dev; + config.init_data = pdata->reg_init_data[id]; + config.driver_data = reg; + config.regmap = rc5t583->regmap; + + rdev = devm_regulator_register(&pdev->dev, &ri->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "Failed to register regulator %s\n", + ri->desc.name); + return PTR_ERR(rdev); + } + reg->rdev = rdev; + } + platform_set_drvdata(pdev, regs); + return 0; +} + +static struct platform_driver rc5t583_regulator_driver = { + .driver = { + .name = "rc5t583-regulator", + }, + .probe = rc5t583_regulator_probe, +}; + +static int __init rc5t583_regulator_init(void) +{ + return platform_driver_register(&rc5t583_regulator_driver); +} +subsys_initcall(rc5t583_regulator_init); + +static void __exit rc5t583_regulator_exit(void) +{ + platform_driver_unregister(&rc5t583_regulator_driver); +} +module_exit(rc5t583_regulator_exit); + +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_DESCRIPTION("RC5T583 regulator driver"); +MODULE_ALIAS("platform:rc5t583-regulator"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/rk808-regulator.c b/drivers/regulator/rk808-regulator.c new file mode 100644 index 000000000..92498ac50 --- /dev/null +++ b/drivers/regulator/rk808-regulator.c @@ -0,0 +1,804 @@ +/* + * Regulator driver for Rockchip RK808/RK818 + * + * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd + * + * Author: Chris Zhong <zyw@rock-chips.com> + * Author: Zhang Qing <zhangqing@rock-chips.com> + * + * Copyright (C) 2016 PHYTEC Messtechnik GmbH + * + * Author: Wadim Egorov <w.egorov@phytec.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/mfd/rk808.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/gpio/consumer.h> + +/* Field Definitions */ +#define RK808_BUCK_VSEL_MASK 0x3f +#define RK808_BUCK4_VSEL_MASK 0xf +#define RK808_LDO_VSEL_MASK 0x1f + +#define RK818_BUCK_VSEL_MASK 0x3f +#define RK818_BUCK4_VSEL_MASK 0x1f +#define RK818_LDO_VSEL_MASK 0x1f +#define RK818_LDO3_ON_VSEL_MASK 0xf +#define RK818_BOOST_ON_VSEL_MASK 0xe0 + +/* Ramp rate definitions for buck1 / buck2 only */ +#define RK808_RAMP_RATE_OFFSET 3 +#define RK808_RAMP_RATE_MASK (3 << RK808_RAMP_RATE_OFFSET) +#define RK808_RAMP_RATE_2MV_PER_US (0 << RK808_RAMP_RATE_OFFSET) +#define RK808_RAMP_RATE_4MV_PER_US (1 << RK808_RAMP_RATE_OFFSET) +#define RK808_RAMP_RATE_6MV_PER_US (2 << RK808_RAMP_RATE_OFFSET) +#define RK808_RAMP_RATE_10MV_PER_US (3 << RK808_RAMP_RATE_OFFSET) + +#define RK808_DVS2_POL BIT(2) +#define RK808_DVS1_POL BIT(1) + +/* Offset from XXX_ON_VSEL to XXX_SLP_VSEL */ +#define RK808_SLP_REG_OFFSET 1 + +/* Offset from XXX_ON_VSEL to XXX_DVS_VSEL */ +#define RK808_DVS_REG_OFFSET 2 + +/* Offset from XXX_EN_REG to SLEEP_SET_OFF_XXX */ +#define RK808_SLP_SET_OFF_REG_OFFSET 2 + +/* max steps for increase voltage of Buck1/2, equal 100mv*/ +#define MAX_STEPS_ONE_TIME 8 + +#define RK805_DESC(_id, _match, _supply, _min, _max, _step, _vreg, \ + _vmask, _ereg, _emask, _etime) \ + [_id] = { \ + .name = (_match), \ + .supply_name = (_supply), \ + .of_match = of_match_ptr(_match), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = (_id), \ + .n_voltages = (((_max) - (_min)) / (_step) + 1), \ + .owner = THIS_MODULE, \ + .min_uV = (_min) * 1000, \ + .uV_step = (_step) * 1000, \ + .vsel_reg = (_vreg), \ + .vsel_mask = (_vmask), \ + .enable_reg = (_ereg), \ + .enable_mask = (_emask), \ + .enable_time = (_etime), \ + .ops = &rk805_reg_ops, \ + } + +#define RK8XX_DESC(_id, _match, _supply, _min, _max, _step, _vreg, \ + _vmask, _ereg, _emask, _etime) \ + [_id] = { \ + .name = (_match), \ + .supply_name = (_supply), \ + .of_match = of_match_ptr(_match), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = (_id), \ + .n_voltages = (((_max) - (_min)) / (_step) + 1), \ + .owner = THIS_MODULE, \ + .min_uV = (_min) * 1000, \ + .uV_step = (_step) * 1000, \ + .vsel_reg = (_vreg), \ + .vsel_mask = (_vmask), \ + .enable_reg = (_ereg), \ + .enable_mask = (_emask), \ + .enable_time = (_etime), \ + .ops = &rk808_reg_ops, \ + } + +#define RK8XX_DESC_SWITCH(_id, _match, _supply, _ereg, _emask) \ + [_id] = { \ + .name = (_match), \ + .supply_name = (_supply), \ + .of_match = of_match_ptr(_match), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = (_id), \ + .enable_reg = (_ereg), \ + .enable_mask = (_emask), \ + .owner = THIS_MODULE, \ + .ops = &rk808_switch_ops \ + } + + +struct rk808_regulator_data { + struct gpio_desc *dvs_gpio[2]; +}; + +static const int rk808_buck_config_regs[] = { + RK808_BUCK1_CONFIG_REG, + RK808_BUCK2_CONFIG_REG, + RK808_BUCK3_CONFIG_REG, + RK808_BUCK4_CONFIG_REG, +}; + +static const struct regulator_linear_range rk808_ldo3_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(800000, 0, 13, 100000), + REGULATOR_LINEAR_RANGE(2500000, 15, 15, 0), +}; + +static int rk808_buck1_2_get_voltage_sel_regmap(struct regulator_dev *rdev) +{ + struct rk808_regulator_data *pdata = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + struct gpio_desc *gpio = pdata->dvs_gpio[id]; + unsigned int val; + int ret; + + if (!gpio || gpiod_get_value(gpio) == 0) + return regulator_get_voltage_sel_regmap(rdev); + + ret = regmap_read(rdev->regmap, + rdev->desc->vsel_reg + RK808_DVS_REG_OFFSET, + &val); + if (ret != 0) + return ret; + + val &= rdev->desc->vsel_mask; + val >>= ffs(rdev->desc->vsel_mask) - 1; + + return val; +} + +static int rk808_buck1_2_i2c_set_voltage_sel(struct regulator_dev *rdev, + unsigned sel) +{ + int ret, delta_sel; + unsigned int old_sel, tmp, val, mask = rdev->desc->vsel_mask; + + ret = regmap_read(rdev->regmap, rdev->desc->vsel_reg, &val); + if (ret != 0) + return ret; + + tmp = val & ~mask; + old_sel = val & mask; + old_sel >>= ffs(mask) - 1; + delta_sel = sel - old_sel; + + /* + * If directly modify the register to change the voltage, we will face + * the risk of overshoot. Put it into a multi-step, can effectively + * avoid this problem, a step is 100mv here. + */ + while (delta_sel > MAX_STEPS_ONE_TIME) { + old_sel += MAX_STEPS_ONE_TIME; + val = old_sel << (ffs(mask) - 1); + val |= tmp; + + /* + * i2c is 400kHz (2.5us per bit) and we must transmit _at least_ + * 3 bytes (24 bits) plus start and stop so 26 bits. So we've + * got more than 65 us between each voltage change and thus + * won't ramp faster than ~1500 uV / us. + */ + ret = regmap_write(rdev->regmap, rdev->desc->vsel_reg, val); + delta_sel = sel - old_sel; + } + + sel <<= ffs(mask) - 1; + val = tmp | sel; + ret = regmap_write(rdev->regmap, rdev->desc->vsel_reg, val); + + /* + * When we change the voltage register directly, the ramp rate is about + * 100000uv/us, wait 1us to make sure the target voltage to be stable, + * so we needn't wait extra time after that. + */ + udelay(1); + + return ret; +} + +static int rk808_buck1_2_set_voltage_sel(struct regulator_dev *rdev, + unsigned sel) +{ + struct rk808_regulator_data *pdata = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + struct gpio_desc *gpio = pdata->dvs_gpio[id]; + unsigned int reg = rdev->desc->vsel_reg; + unsigned old_sel; + int ret, gpio_level; + + if (!gpio) + return rk808_buck1_2_i2c_set_voltage_sel(rdev, sel); + + gpio_level = gpiod_get_value(gpio); + if (gpio_level == 0) { + reg += RK808_DVS_REG_OFFSET; + ret = regmap_read(rdev->regmap, rdev->desc->vsel_reg, &old_sel); + } else { + ret = regmap_read(rdev->regmap, + reg + RK808_DVS_REG_OFFSET, + &old_sel); + } + + if (ret != 0) + return ret; + + sel <<= ffs(rdev->desc->vsel_mask) - 1; + sel |= old_sel & ~rdev->desc->vsel_mask; + + ret = regmap_write(rdev->regmap, reg, sel); + if (ret) + return ret; + + gpiod_set_value(gpio, !gpio_level); + + return ret; +} + +static int rk808_buck1_2_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + struct rk808_regulator_data *pdata = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + struct gpio_desc *gpio = pdata->dvs_gpio[id]; + + /* if there is no dvs1/2 pin, we don't need wait extra time here. */ + if (!gpio) + return 0; + + return regulator_set_voltage_time_sel(rdev, old_selector, new_selector); +} + +static int rk808_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay) +{ + unsigned int ramp_value = RK808_RAMP_RATE_10MV_PER_US; + unsigned int reg = rk808_buck_config_regs[rdev_get_id(rdev)]; + + switch (ramp_delay) { + case 1 ... 2000: + ramp_value = RK808_RAMP_RATE_2MV_PER_US; + break; + case 2001 ... 4000: + ramp_value = RK808_RAMP_RATE_4MV_PER_US; + break; + case 4001 ... 6000: + ramp_value = RK808_RAMP_RATE_6MV_PER_US; + break; + case 6001 ... 10000: + break; + default: + pr_warn("%s ramp_delay: %d not supported, setting 10000\n", + rdev->desc->name, ramp_delay); + } + + return regmap_update_bits(rdev->regmap, reg, + RK808_RAMP_RATE_MASK, ramp_value); +} + +static int rk808_set_suspend_voltage(struct regulator_dev *rdev, int uv) +{ + unsigned int reg; + int sel = regulator_map_voltage_linear(rdev, uv, uv); + + if (sel < 0) + return -EINVAL; + + reg = rdev->desc->vsel_reg + RK808_SLP_REG_OFFSET; + + return regmap_update_bits(rdev->regmap, reg, + rdev->desc->vsel_mask, + sel); +} + +static int rk808_set_suspend_voltage_range(struct regulator_dev *rdev, int uv) +{ + unsigned int reg; + int sel = regulator_map_voltage_linear_range(rdev, uv, uv); + + if (sel < 0) + return -EINVAL; + + reg = rdev->desc->vsel_reg + RK808_SLP_REG_OFFSET; + + return regmap_update_bits(rdev->regmap, reg, + rdev->desc->vsel_mask, + sel); +} + +static int rk805_set_suspend_enable(struct regulator_dev *rdev) +{ + unsigned int reg; + + reg = rdev->desc->enable_reg + RK808_SLP_SET_OFF_REG_OFFSET; + + return regmap_update_bits(rdev->regmap, reg, + rdev->desc->enable_mask, + rdev->desc->enable_mask); +} + +static int rk805_set_suspend_disable(struct regulator_dev *rdev) +{ + unsigned int reg; + + reg = rdev->desc->enable_reg + RK808_SLP_SET_OFF_REG_OFFSET; + + return regmap_update_bits(rdev->regmap, reg, + rdev->desc->enable_mask, + 0); +} + +static int rk808_set_suspend_enable(struct regulator_dev *rdev) +{ + unsigned int reg; + + reg = rdev->desc->enable_reg + RK808_SLP_SET_OFF_REG_OFFSET; + + return regmap_update_bits(rdev->regmap, reg, + rdev->desc->enable_mask, + 0); +} + +static int rk808_set_suspend_disable(struct regulator_dev *rdev) +{ + unsigned int reg; + + reg = rdev->desc->enable_reg + RK808_SLP_SET_OFF_REG_OFFSET; + + return regmap_update_bits(rdev->regmap, reg, + rdev->desc->enable_mask, + rdev->desc->enable_mask); +} + +static struct regulator_ops rk805_reg_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_suspend_voltage = rk808_set_suspend_voltage, + .set_suspend_enable = rk805_set_suspend_enable, + .set_suspend_disable = rk805_set_suspend_disable, +}; + +static struct regulator_ops rk805_switch_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_suspend_enable = rk805_set_suspend_enable, + .set_suspend_disable = rk805_set_suspend_disable, +}; + +static struct regulator_ops rk808_buck1_2_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = rk808_buck1_2_get_voltage_sel_regmap, + .set_voltage_sel = rk808_buck1_2_set_voltage_sel, + .set_voltage_time_sel = rk808_buck1_2_set_voltage_time_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_ramp_delay = rk808_set_ramp_delay, + .set_suspend_voltage = rk808_set_suspend_voltage, + .set_suspend_enable = rk808_set_suspend_enable, + .set_suspend_disable = rk808_set_suspend_disable, +}; + +static struct regulator_ops rk808_reg_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_suspend_voltage = rk808_set_suspend_voltage, + .set_suspend_enable = rk808_set_suspend_enable, + .set_suspend_disable = rk808_set_suspend_disable, +}; + +static struct regulator_ops rk808_reg_ops_ranges = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_suspend_voltage = rk808_set_suspend_voltage_range, + .set_suspend_enable = rk808_set_suspend_enable, + .set_suspend_disable = rk808_set_suspend_disable, +}; + +static struct regulator_ops rk808_switch_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_suspend_enable = rk808_set_suspend_enable, + .set_suspend_disable = rk808_set_suspend_disable, +}; + +static const struct regulator_desc rk805_reg[] = { + { + .name = "DCDC_REG1", + .supply_name = "vcc1", + .of_match = of_match_ptr("DCDC_REG1"), + .regulators_node = of_match_ptr("regulators"), + .id = RK805_ID_DCDC1, + .ops = &rk805_reg_ops, + .type = REGULATOR_VOLTAGE, + .min_uV = 712500, + .uV_step = 12500, + .n_voltages = 64, + .vsel_reg = RK805_BUCK1_ON_VSEL_REG, + .vsel_mask = RK818_BUCK_VSEL_MASK, + .enable_reg = RK805_DCDC_EN_REG, + .enable_mask = BIT(0), + .owner = THIS_MODULE, + }, { + .name = "DCDC_REG2", + .supply_name = "vcc2", + .of_match = of_match_ptr("DCDC_REG2"), + .regulators_node = of_match_ptr("regulators"), + .id = RK805_ID_DCDC2, + .ops = &rk805_reg_ops, + .type = REGULATOR_VOLTAGE, + .min_uV = 712500, + .uV_step = 12500, + .n_voltages = 64, + .vsel_reg = RK805_BUCK2_ON_VSEL_REG, + .vsel_mask = RK818_BUCK_VSEL_MASK, + .enable_reg = RK805_DCDC_EN_REG, + .enable_mask = BIT(1), + .owner = THIS_MODULE, + }, { + .name = "DCDC_REG3", + .supply_name = "vcc3", + .of_match = of_match_ptr("DCDC_REG3"), + .regulators_node = of_match_ptr("regulators"), + .id = RK805_ID_DCDC3, + .ops = &rk805_switch_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = 1, + .enable_reg = RK805_DCDC_EN_REG, + .enable_mask = BIT(2), + .owner = THIS_MODULE, + }, + + RK805_DESC(RK805_ID_DCDC4, "DCDC_REG4", "vcc4", 800, 3400, 100, + RK805_BUCK4_ON_VSEL_REG, RK818_BUCK4_VSEL_MASK, + RK805_DCDC_EN_REG, BIT(3), 0), + + RK805_DESC(RK805_ID_LDO1, "LDO_REG1", "vcc5", 800, 3400, 100, + RK805_LDO1_ON_VSEL_REG, RK818_LDO_VSEL_MASK, RK805_LDO_EN_REG, + BIT(0), 400), + RK805_DESC(RK805_ID_LDO2, "LDO_REG2", "vcc5", 800, 3400, 100, + RK805_LDO2_ON_VSEL_REG, RK818_LDO_VSEL_MASK, RK805_LDO_EN_REG, + BIT(1), 400), + RK805_DESC(RK805_ID_LDO3, "LDO_REG3", "vcc6", 800, 3400, 100, + RK805_LDO3_ON_VSEL_REG, RK818_LDO_VSEL_MASK, RK805_LDO_EN_REG, + BIT(2), 400), +}; + +static const struct regulator_desc rk808_reg[] = { + { + .name = "DCDC_REG1", + .supply_name = "vcc1", + .of_match = of_match_ptr("DCDC_REG1"), + .regulators_node = of_match_ptr("regulators"), + .id = RK808_ID_DCDC1, + .ops = &rk808_buck1_2_ops, + .type = REGULATOR_VOLTAGE, + .min_uV = 712500, + .uV_step = 12500, + .n_voltages = 64, + .vsel_reg = RK808_BUCK1_ON_VSEL_REG, + .vsel_mask = RK808_BUCK_VSEL_MASK, + .enable_reg = RK808_DCDC_EN_REG, + .enable_mask = BIT(0), + .owner = THIS_MODULE, + }, { + .name = "DCDC_REG2", + .supply_name = "vcc2", + .of_match = of_match_ptr("DCDC_REG2"), + .regulators_node = of_match_ptr("regulators"), + .id = RK808_ID_DCDC2, + .ops = &rk808_buck1_2_ops, + .type = REGULATOR_VOLTAGE, + .min_uV = 712500, + .uV_step = 12500, + .n_voltages = 64, + .vsel_reg = RK808_BUCK2_ON_VSEL_REG, + .vsel_mask = RK808_BUCK_VSEL_MASK, + .enable_reg = RK808_DCDC_EN_REG, + .enable_mask = BIT(1), + .owner = THIS_MODULE, + }, { + .name = "DCDC_REG3", + .supply_name = "vcc3", + .of_match = of_match_ptr("DCDC_REG3"), + .regulators_node = of_match_ptr("regulators"), + .id = RK808_ID_DCDC3, + .ops = &rk808_switch_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = 1, + .enable_reg = RK808_DCDC_EN_REG, + .enable_mask = BIT(2), + .owner = THIS_MODULE, + }, + RK8XX_DESC(RK808_ID_DCDC4, "DCDC_REG4", "vcc4", 1800, 3300, 100, + RK808_BUCK4_ON_VSEL_REG, RK808_BUCK4_VSEL_MASK, + RK808_DCDC_EN_REG, BIT(3), 0), + RK8XX_DESC(RK808_ID_LDO1, "LDO_REG1", "vcc6", 1800, 3400, 100, + RK808_LDO1_ON_VSEL_REG, RK808_LDO_VSEL_MASK, RK808_LDO_EN_REG, + BIT(0), 400), + RK8XX_DESC(RK808_ID_LDO2, "LDO_REG2", "vcc6", 1800, 3400, 100, + RK808_LDO2_ON_VSEL_REG, RK808_LDO_VSEL_MASK, RK808_LDO_EN_REG, + BIT(1), 400), + { + .name = "LDO_REG3", + .supply_name = "vcc7", + .of_match = of_match_ptr("LDO_REG3"), + .regulators_node = of_match_ptr("regulators"), + .id = RK808_ID_LDO3, + .ops = &rk808_reg_ops_ranges, + .type = REGULATOR_VOLTAGE, + .n_voltages = 16, + .linear_ranges = rk808_ldo3_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(rk808_ldo3_voltage_ranges), + .vsel_reg = RK808_LDO3_ON_VSEL_REG, + .vsel_mask = RK808_BUCK4_VSEL_MASK, + .enable_reg = RK808_LDO_EN_REG, + .enable_mask = BIT(2), + .enable_time = 400, + .owner = THIS_MODULE, + }, + RK8XX_DESC(RK808_ID_LDO4, "LDO_REG4", "vcc9", 1800, 3400, 100, + RK808_LDO4_ON_VSEL_REG, RK808_LDO_VSEL_MASK, RK808_LDO_EN_REG, + BIT(3), 400), + RK8XX_DESC(RK808_ID_LDO5, "LDO_REG5", "vcc9", 1800, 3400, 100, + RK808_LDO5_ON_VSEL_REG, RK808_LDO_VSEL_MASK, RK808_LDO_EN_REG, + BIT(4), 400), + RK8XX_DESC(RK808_ID_LDO6, "LDO_REG6", "vcc10", 800, 2500, 100, + RK808_LDO6_ON_VSEL_REG, RK808_LDO_VSEL_MASK, RK808_LDO_EN_REG, + BIT(5), 400), + RK8XX_DESC(RK808_ID_LDO7, "LDO_REG7", "vcc7", 800, 2500, 100, + RK808_LDO7_ON_VSEL_REG, RK808_LDO_VSEL_MASK, RK808_LDO_EN_REG, + BIT(6), 400), + RK8XX_DESC(RK808_ID_LDO8, "LDO_REG8", "vcc11", 1800, 3400, 100, + RK808_LDO8_ON_VSEL_REG, RK808_LDO_VSEL_MASK, RK808_LDO_EN_REG, + BIT(7), 400), + RK8XX_DESC_SWITCH(RK808_ID_SWITCH1, "SWITCH_REG1", "vcc8", + RK808_DCDC_EN_REG, BIT(5)), + RK8XX_DESC_SWITCH(RK808_ID_SWITCH2, "SWITCH_REG2", "vcc12", + RK808_DCDC_EN_REG, BIT(6)), +}; + +static const struct regulator_desc rk818_reg[] = { + { + .name = "DCDC_REG1", + .supply_name = "vcc1", + .of_match = of_match_ptr("DCDC_REG1"), + .regulators_node = of_match_ptr("regulators"), + .id = RK818_ID_DCDC1, + .ops = &rk808_reg_ops, + .type = REGULATOR_VOLTAGE, + .min_uV = 712500, + .uV_step = 12500, + .n_voltages = 64, + .vsel_reg = RK818_BUCK1_ON_VSEL_REG, + .vsel_mask = RK818_BUCK_VSEL_MASK, + .enable_reg = RK818_DCDC_EN_REG, + .enable_mask = BIT(0), + .owner = THIS_MODULE, + }, { + .name = "DCDC_REG2", + .supply_name = "vcc2", + .of_match = of_match_ptr("DCDC_REG2"), + .regulators_node = of_match_ptr("regulators"), + .id = RK818_ID_DCDC2, + .ops = &rk808_reg_ops, + .type = REGULATOR_VOLTAGE, + .min_uV = 712500, + .uV_step = 12500, + .n_voltages = 64, + .vsel_reg = RK818_BUCK2_ON_VSEL_REG, + .vsel_mask = RK818_BUCK_VSEL_MASK, + .enable_reg = RK818_DCDC_EN_REG, + .enable_mask = BIT(1), + .owner = THIS_MODULE, + }, { + .name = "DCDC_REG3", + .supply_name = "vcc3", + .of_match = of_match_ptr("DCDC_REG3"), + .regulators_node = of_match_ptr("regulators"), + .id = RK818_ID_DCDC3, + .ops = &rk808_switch_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = 1, + .enable_reg = RK818_DCDC_EN_REG, + .enable_mask = BIT(2), + .owner = THIS_MODULE, + }, + RK8XX_DESC(RK818_ID_DCDC4, "DCDC_REG4", "vcc4", 1800, 3600, 100, + RK818_BUCK4_ON_VSEL_REG, RK818_BUCK4_VSEL_MASK, + RK818_DCDC_EN_REG, BIT(3), 0), + RK8XX_DESC(RK818_ID_BOOST, "DCDC_BOOST", "boost", 4700, 5400, 100, + RK818_BOOST_LDO9_ON_VSEL_REG, RK818_BOOST_ON_VSEL_MASK, + RK818_DCDC_EN_REG, BIT(4), 0), + RK8XX_DESC(RK818_ID_LDO1, "LDO_REG1", "vcc6", 1800, 3400, 100, + RK818_LDO1_ON_VSEL_REG, RK818_LDO_VSEL_MASK, RK818_LDO_EN_REG, + BIT(0), 400), + RK8XX_DESC(RK818_ID_LDO2, "LDO_REG2", "vcc6", 1800, 3400, 100, + RK818_LDO2_ON_VSEL_REG, RK818_LDO_VSEL_MASK, RK818_LDO_EN_REG, + BIT(1), 400), + { + .name = "LDO_REG3", + .supply_name = "vcc7", + .of_match = of_match_ptr("LDO_REG3"), + .regulators_node = of_match_ptr("regulators"), + .id = RK818_ID_LDO3, + .ops = &rk808_reg_ops_ranges, + .type = REGULATOR_VOLTAGE, + .n_voltages = 16, + .linear_ranges = rk808_ldo3_voltage_ranges, + .n_linear_ranges = ARRAY_SIZE(rk808_ldo3_voltage_ranges), + .vsel_reg = RK818_LDO3_ON_VSEL_REG, + .vsel_mask = RK818_LDO3_ON_VSEL_MASK, + .enable_reg = RK818_LDO_EN_REG, + .enable_mask = BIT(2), + .enable_time = 400, + .owner = THIS_MODULE, + }, + RK8XX_DESC(RK818_ID_LDO4, "LDO_REG4", "vcc8", 1800, 3400, 100, + RK818_LDO4_ON_VSEL_REG, RK818_LDO_VSEL_MASK, RK818_LDO_EN_REG, + BIT(3), 400), + RK8XX_DESC(RK818_ID_LDO5, "LDO_REG5", "vcc7", 1800, 3400, 100, + RK818_LDO5_ON_VSEL_REG, RK818_LDO_VSEL_MASK, RK818_LDO_EN_REG, + BIT(4), 400), + RK8XX_DESC(RK818_ID_LDO6, "LDO_REG6", "vcc8", 800, 2500, 100, + RK818_LDO6_ON_VSEL_REG, RK818_LDO_VSEL_MASK, RK818_LDO_EN_REG, + BIT(5), 400), + RK8XX_DESC(RK818_ID_LDO7, "LDO_REG7", "vcc7", 800, 2500, 100, + RK818_LDO7_ON_VSEL_REG, RK818_LDO_VSEL_MASK, RK818_LDO_EN_REG, + BIT(6), 400), + RK8XX_DESC(RK818_ID_LDO8, "LDO_REG8", "vcc8", 1800, 3400, 100, + RK818_LDO8_ON_VSEL_REG, RK818_LDO_VSEL_MASK, RK818_LDO_EN_REG, + BIT(7), 400), + RK8XX_DESC(RK818_ID_LDO9, "LDO_REG9", "vcc9", 1800, 3400, 100, + RK818_BOOST_LDO9_ON_VSEL_REG, RK818_LDO_VSEL_MASK, + RK818_DCDC_EN_REG, BIT(5), 400), + RK8XX_DESC_SWITCH(RK818_ID_SWITCH, "SWITCH_REG", "vcc9", + RK818_DCDC_EN_REG, BIT(6)), + RK8XX_DESC_SWITCH(RK818_ID_HDMI_SWITCH, "HDMI_SWITCH", "h_5v", + RK818_H5V_EN_REG, BIT(0)), + RK8XX_DESC_SWITCH(RK818_ID_OTG_SWITCH, "OTG_SWITCH", "usb", + RK818_DCDC_EN_REG, BIT(7)), +}; + +static int rk808_regulator_dt_parse_pdata(struct device *dev, + struct device *client_dev, + struct regmap *map, + struct rk808_regulator_data *pdata) +{ + struct device_node *np; + int tmp, ret = 0, i; + + np = of_get_child_by_name(client_dev->of_node, "regulators"); + if (!np) + return -ENXIO; + + for (i = 0; i < ARRAY_SIZE(pdata->dvs_gpio); i++) { + pdata->dvs_gpio[i] = + devm_gpiod_get_index_optional(client_dev, "dvs", i, + GPIOD_OUT_LOW); + if (IS_ERR(pdata->dvs_gpio[i])) { + ret = PTR_ERR(pdata->dvs_gpio[i]); + dev_err(dev, "failed to get dvs%d gpio (%d)\n", i, ret); + goto dt_parse_end; + } + + if (!pdata->dvs_gpio[i]) { + dev_info(dev, "there is no dvs%d gpio\n", i); + continue; + } + + tmp = i ? RK808_DVS2_POL : RK808_DVS1_POL; + ret = regmap_update_bits(map, RK808_IO_POL_REG, tmp, + gpiod_is_active_low(pdata->dvs_gpio[i]) ? + 0 : tmp); + } + +dt_parse_end: + of_node_put(np); + return ret; +} + +static int rk808_regulator_probe(struct platform_device *pdev) +{ + struct rk808 *rk808 = dev_get_drvdata(pdev->dev.parent); + struct i2c_client *client = rk808->i2c; + struct regulator_config config = {}; + struct regulator_dev *rk808_rdev; + struct rk808_regulator_data *pdata; + const struct regulator_desc *regulators; + int ret, i, nregulators; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = rk808_regulator_dt_parse_pdata(&pdev->dev, &client->dev, + rk808->regmap, pdata); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, pdata); + + switch (rk808->variant) { + case RK805_ID: + regulators = rk805_reg; + nregulators = RK805_NUM_REGULATORS; + break; + case RK808_ID: + regulators = rk808_reg; + nregulators = RK808_NUM_REGULATORS; + break; + case RK818_ID: + regulators = rk818_reg; + nregulators = RK818_NUM_REGULATORS; + break; + default: + dev_err(&client->dev, "unsupported RK8XX ID %lu\n", + rk808->variant); + return -EINVAL; + } + + config.dev = &client->dev; + config.driver_data = pdata; + config.regmap = rk808->regmap; + + /* Instantiate the regulators */ + for (i = 0; i < nregulators; i++) { + rk808_rdev = devm_regulator_register(&pdev->dev, + ®ulators[i], &config); + if (IS_ERR(rk808_rdev)) { + dev_err(&client->dev, + "failed to register %d regulator\n", i); + return PTR_ERR(rk808_rdev); + } + } + + return 0; +} + +static struct platform_driver rk808_regulator_driver = { + .probe = rk808_regulator_probe, + .driver = { + .name = "rk808-regulator" + }, +}; + +module_platform_driver(rk808_regulator_driver); + +MODULE_DESCRIPTION("regulator driver for the RK808/RK818 series PMICs"); +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); +MODULE_AUTHOR("Zhang Qing <zhangqing@rock-chips.com>"); +MODULE_AUTHOR("Wadim Egorov <w.egorov@phytec.de>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rk808-regulator"); diff --git a/drivers/regulator/rn5t618-regulator.c b/drivers/regulator/rn5t618-regulator.c new file mode 100644 index 000000000..40b74648b --- /dev/null +++ b/drivers/regulator/rn5t618-regulator.c @@ -0,0 +1,160 @@ +/* + * Regulator driver for Ricoh RN5T618 PMIC + * + * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/mfd/rn5t618.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +static const struct regulator_ops rn5t618_reg_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +#define REG(rid, ereg, emask, vreg, vmask, min, max, step) \ + { \ + .name = #rid, \ + .of_match = of_match_ptr(#rid), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = RN5T618_##rid, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .ops = &rn5t618_reg_ops, \ + .n_voltages = ((max) - (min)) / (step) + 1, \ + .min_uV = (min), \ + .uV_step = (step), \ + .enable_reg = RN5T618_##ereg, \ + .enable_mask = (emask), \ + .vsel_reg = RN5T618_##vreg, \ + .vsel_mask = (vmask), \ + } + +static struct regulator_desc rn5t567_regulators[] = { + /* DCDC */ + REG(DCDC1, DC1CTL, BIT(0), DC1DAC, 0xff, 600000, 3500000, 12500), + REG(DCDC2, DC2CTL, BIT(0), DC2DAC, 0xff, 600000, 3500000, 12500), + REG(DCDC3, DC3CTL, BIT(0), DC3DAC, 0xff, 600000, 3500000, 12500), + REG(DCDC4, DC4CTL, BIT(0), DC4DAC, 0xff, 600000, 3500000, 12500), + /* LDO */ + REG(LDO1, LDOEN1, BIT(0), LDO1DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO2, LDOEN1, BIT(1), LDO2DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO3, LDOEN1, BIT(2), LDO3DAC, 0x7f, 600000, 3500000, 25000), + REG(LDO4, LDOEN1, BIT(3), LDO4DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO5, LDOEN1, BIT(4), LDO5DAC, 0x7f, 900000, 3500000, 25000), + /* LDO RTC */ + REG(LDORTC1, LDOEN2, BIT(4), LDORTCDAC, 0x7f, 1200000, 3500000, 25000), + REG(LDORTC2, LDOEN2, BIT(5), LDORTC2DAC, 0x7f, 900000, 3500000, 25000), +}; + +static struct regulator_desc rn5t618_regulators[] = { + /* DCDC */ + REG(DCDC1, DC1CTL, BIT(0), DC1DAC, 0xff, 600000, 3500000, 12500), + REG(DCDC2, DC2CTL, BIT(0), DC2DAC, 0xff, 600000, 3500000, 12500), + REG(DCDC3, DC3CTL, BIT(0), DC3DAC, 0xff, 600000, 3500000, 12500), + /* LDO */ + REG(LDO1, LDOEN1, BIT(0), LDO1DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO2, LDOEN1, BIT(1), LDO2DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO3, LDOEN1, BIT(2), LDO3DAC, 0x7f, 600000, 3500000, 25000), + REG(LDO4, LDOEN1, BIT(3), LDO4DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO5, LDOEN1, BIT(4), LDO5DAC, 0x7f, 900000, 3500000, 25000), + /* LDO RTC */ + REG(LDORTC1, LDOEN2, BIT(4), LDORTCDAC, 0x7f, 1700000, 3500000, 25000), + REG(LDORTC2, LDOEN2, BIT(5), LDORTC2DAC, 0x7f, 900000, 3500000, 25000), +}; + +static struct regulator_desc rc5t619_regulators[] = { + /* DCDC */ + REG(DCDC1, DC1CTL, BIT(0), DC1DAC, 0xff, 600000, 3500000, 12500), + REG(DCDC2, DC2CTL, BIT(0), DC2DAC, 0xff, 600000, 3500000, 12500), + REG(DCDC3, DC3CTL, BIT(0), DC3DAC, 0xff, 600000, 3500000, 12500), + REG(DCDC4, DC4CTL, BIT(0), DC4DAC, 0xff, 600000, 3500000, 12500), + REG(DCDC5, DC5CTL, BIT(0), DC5DAC, 0xff, 600000, 3500000, 12500), + /* LDO */ + REG(LDO1, LDOEN1, BIT(0), LDO1DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO2, LDOEN1, BIT(1), LDO2DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO3, LDOEN1, BIT(2), LDO3DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO4, LDOEN1, BIT(3), LDO4DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO5, LDOEN1, BIT(4), LDO5DAC, 0x7f, 600000, 3500000, 25000), + REG(LDO6, LDOEN1, BIT(5), LDO6DAC, 0x7f, 600000, 3500000, 25000), + REG(LDO7, LDOEN1, BIT(6), LDO7DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO8, LDOEN1, BIT(7), LDO8DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO9, LDOEN2, BIT(0), LDO9DAC, 0x7f, 900000, 3500000, 25000), + REG(LDO10, LDOEN2, BIT(0), LDO10DAC, 0x7f, 900000, 3500000, 25000), + /* LDO RTC */ + REG(LDORTC1, LDOEN2, BIT(4), LDORTCDAC, 0x7f, 1700000, 3500000, 25000), + REG(LDORTC2, LDOEN2, BIT(5), LDORTC2DAC, 0x7f, 900000, 3500000, 25000), +}; + +static int rn5t618_regulator_probe(struct platform_device *pdev) +{ + struct rn5t618 *rn5t618 = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct regulator_desc *regulators; + int i; + int num_regulators = 0; + + switch (rn5t618->variant) { + case RN5T567: + regulators = rn5t567_regulators; + num_regulators = ARRAY_SIZE(rn5t567_regulators); + break; + case RN5T618: + regulators = rn5t618_regulators; + num_regulators = ARRAY_SIZE(rn5t618_regulators); + break; + case RC5T619: + regulators = rc5t619_regulators; + num_regulators = ARRAY_SIZE(rc5t619_regulators); + break; + default: + return -EINVAL; + } + + config.dev = pdev->dev.parent; + config.regmap = rn5t618->regmap; + + for (i = 0; i < num_regulators; i++) { + rdev = devm_regulator_register(&pdev->dev, + ®ulators[i], + &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s regulator\n", + regulators[i].name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static struct platform_driver rn5t618_regulator_driver = { + .probe = rn5t618_regulator_probe, + .driver = { + .name = "rn5t618-regulator", + }, +}; + +module_platform_driver(rn5t618_regulator_driver); + +MODULE_ALIAS("platform:rn5t618-regulator"); +MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>"); +MODULE_DESCRIPTION("RN5T618 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/rt5033-regulator.c b/drivers/regulator/rt5033-regulator.c new file mode 100644 index 000000000..96d2c18e0 --- /dev/null +++ b/drivers/regulator/rt5033-regulator.c @@ -0,0 +1,129 @@ +/* + * Regulator driver for the Richtek RT5033 + * + * Copyright (C) 2014 Samsung Electronics, Co., Ltd. + * Author: Beomho Seo <beomho.seo@samsung.com> + * + * 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 bythe Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/rt5033.h> +#include <linux/mfd/rt5033-private.h> +#include <linux/regulator/of_regulator.h> + +static struct regulator_ops rt5033_safe_ldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, +}; + +static struct regulator_ops rt5033_buck_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_desc rt5033_supported_regulators[] = { + [RT5033_BUCK] = { + .name = "BUCK", + .of_match = of_match_ptr("BUCK"), + .regulators_node = of_match_ptr("regulators"), + .id = RT5033_BUCK, + .ops = &rt5033_buck_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = RT5033_REGULATOR_BUCK_VOLTAGE_STEP_NUM, + .min_uV = RT5033_REGULATOR_BUCK_VOLTAGE_MIN, + .uV_step = RT5033_REGULATOR_BUCK_VOLTAGE_STEP, + .enable_reg = RT5033_REG_CTRL, + .enable_mask = RT5033_CTRL_EN_BUCK_MASK, + .vsel_reg = RT5033_REG_BUCK_CTRL, + .vsel_mask = RT5033_BUCK_CTRL_MASK, + }, + [RT5033_LDO] = { + .name = "LDO", + .of_match = of_match_ptr("LDO"), + .regulators_node = of_match_ptr("regulators"), + .id = RT5033_LDO, + .ops = &rt5033_buck_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = RT5033_REGULATOR_LDO_VOLTAGE_STEP_NUM, + .min_uV = RT5033_REGULATOR_LDO_VOLTAGE_MIN, + .uV_step = RT5033_REGULATOR_LDO_VOLTAGE_STEP, + .enable_reg = RT5033_REG_CTRL, + .enable_mask = RT5033_CTRL_EN_LDO_MASK, + .vsel_reg = RT5033_REG_LDO_CTRL, + .vsel_mask = RT5033_LDO_CTRL_MASK, + }, + [RT5033_SAFE_LDO] = { + .name = "SAFE_LDO", + .of_match = of_match_ptr("SAFE_LDO"), + .regulators_node = of_match_ptr("regulators"), + .id = RT5033_SAFE_LDO, + .ops = &rt5033_safe_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = 1, + .min_uV = RT5033_REGULATOR_SAFE_LDO_VOLTAGE, + .enable_reg = RT5033_REG_CTRL, + .enable_mask = RT5033_CTRL_EN_SAFE_LDO_MASK, + }, +}; + +static int rt5033_regulator_probe(struct platform_device *pdev) +{ + struct rt5033_dev *rt5033 = dev_get_drvdata(pdev->dev.parent); + int ret, i; + struct regulator_config config = {}; + + config.dev = rt5033->dev; + config.driver_data = rt5033; + + for (i = 0; i < ARRAY_SIZE(rt5033_supported_regulators); i++) { + struct regulator_dev *regulator; + + config.regmap = rt5033->regmap; + + regulator = devm_regulator_register(&pdev->dev, + &rt5033_supported_regulators[i], &config); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + dev_err(&pdev->dev, + "Regulator init failed %d: with error: %d\n", + i, ret); + return ret; + } + } + + return 0; +} + +static const struct platform_device_id rt5033_regulator_id[] = { + { "rt5033-regulator", }, + { } +}; +MODULE_DEVICE_TABLE(platform, rt5033_regulator_id); + +static struct platform_driver rt5033_regulator_driver = { + .driver = { + .name = "rt5033-regulator", + }, + .probe = rt5033_regulator_probe, + .id_table = rt5033_regulator_id, +}; +module_platform_driver(rt5033_regulator_driver); + +MODULE_DESCRIPTION("Richtek RT5033 Regulator driver"); +MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/s2mpa01.c b/drivers/regulator/s2mpa01.c new file mode 100644 index 000000000..58a1fe583 --- /dev/null +++ b/drivers/regulator/s2mpa01.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (c) 2013 Samsung Electronics Co., Ltd +// http://www.samsung.com + +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/samsung/core.h> +#include <linux/mfd/samsung/s2mpa01.h> + +#define S2MPA01_REGULATOR_CNT ARRAY_SIZE(regulators) + +struct s2mpa01_info { + struct of_regulator_match rdata[S2MPA01_REGULATOR_MAX]; + int ramp_delay24; + int ramp_delay3; + int ramp_delay5; + int ramp_delay16; + int ramp_delay7; + int ramp_delay8910; +}; + +static int get_ramp_delay(int ramp_delay) +{ + unsigned char cnt = 0; + + ramp_delay /= 6250; + + while (true) { + ramp_delay = ramp_delay >> 1; + if (ramp_delay == 0) + break; + cnt++; + } + + if (cnt > 3) + cnt = 3; + + return cnt; +} + +static int s2mpa01_regulator_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + struct s2mpa01_info *s2mpa01 = rdev_get_drvdata(rdev); + unsigned int ramp_delay = 0; + int old_volt, new_volt; + + switch (rdev_get_id(rdev)) { + case S2MPA01_BUCK2: + case S2MPA01_BUCK4: + ramp_delay = s2mpa01->ramp_delay24; + break; + case S2MPA01_BUCK3: + ramp_delay = s2mpa01->ramp_delay3; + break; + case S2MPA01_BUCK5: + ramp_delay = s2mpa01->ramp_delay5; + break; + case S2MPA01_BUCK1: + case S2MPA01_BUCK6: + ramp_delay = s2mpa01->ramp_delay16; + break; + case S2MPA01_BUCK7: + ramp_delay = s2mpa01->ramp_delay7; + break; + case S2MPA01_BUCK8: + case S2MPA01_BUCK9: + case S2MPA01_BUCK10: + ramp_delay = s2mpa01->ramp_delay8910; + break; + } + + if (ramp_delay == 0) + ramp_delay = rdev->desc->ramp_delay; + + old_volt = rdev->desc->min_uV + (rdev->desc->uV_step * old_selector); + new_volt = rdev->desc->min_uV + (rdev->desc->uV_step * new_selector); + + return DIV_ROUND_UP(abs(new_volt - old_volt), ramp_delay); +} + +static int s2mpa01_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay) +{ + struct s2mpa01_info *s2mpa01 = rdev_get_drvdata(rdev); + unsigned int ramp_val, ramp_shift, ramp_reg = S2MPA01_REG_RAMP2; + unsigned int ramp_enable = 1, enable_shift = 0; + int ret; + + switch (rdev_get_id(rdev)) { + case S2MPA01_BUCK1: + enable_shift = S2MPA01_BUCK1_RAMP_EN_SHIFT; + if (!ramp_delay) { + ramp_enable = 0; + break; + } + + if (ramp_delay > s2mpa01->ramp_delay16) + s2mpa01->ramp_delay16 = ramp_delay; + else + ramp_delay = s2mpa01->ramp_delay16; + + ramp_shift = S2MPA01_BUCK16_RAMP_SHIFT; + break; + case S2MPA01_BUCK2: + enable_shift = S2MPA01_BUCK2_RAMP_EN_SHIFT; + if (!ramp_delay) { + ramp_enable = 0; + break; + } + + if (ramp_delay > s2mpa01->ramp_delay24) + s2mpa01->ramp_delay24 = ramp_delay; + else + ramp_delay = s2mpa01->ramp_delay24; + + ramp_shift = S2MPA01_BUCK24_RAMP_SHIFT; + ramp_reg = S2MPA01_REG_RAMP1; + break; + case S2MPA01_BUCK3: + enable_shift = S2MPA01_BUCK3_RAMP_EN_SHIFT; + if (!ramp_delay) { + ramp_enable = 0; + break; + } + + s2mpa01->ramp_delay3 = ramp_delay; + ramp_shift = S2MPA01_BUCK3_RAMP_SHIFT; + ramp_reg = S2MPA01_REG_RAMP1; + break; + case S2MPA01_BUCK4: + enable_shift = S2MPA01_BUCK4_RAMP_EN_SHIFT; + if (!ramp_delay) { + ramp_enable = 0; + break; + } + + if (ramp_delay > s2mpa01->ramp_delay24) + s2mpa01->ramp_delay24 = ramp_delay; + else + ramp_delay = s2mpa01->ramp_delay24; + + ramp_shift = S2MPA01_BUCK24_RAMP_SHIFT; + ramp_reg = S2MPA01_REG_RAMP1; + break; + case S2MPA01_BUCK5: + s2mpa01->ramp_delay5 = ramp_delay; + ramp_shift = S2MPA01_BUCK5_RAMP_SHIFT; + break; + case S2MPA01_BUCK6: + if (ramp_delay > s2mpa01->ramp_delay16) + s2mpa01->ramp_delay16 = ramp_delay; + else + ramp_delay = s2mpa01->ramp_delay16; + + ramp_shift = S2MPA01_BUCK16_RAMP_SHIFT; + break; + case S2MPA01_BUCK7: + s2mpa01->ramp_delay7 = ramp_delay; + ramp_shift = S2MPA01_BUCK7_RAMP_SHIFT; + break; + case S2MPA01_BUCK8: + case S2MPA01_BUCK9: + case S2MPA01_BUCK10: + if (ramp_delay > s2mpa01->ramp_delay8910) + s2mpa01->ramp_delay8910 = ramp_delay; + else + ramp_delay = s2mpa01->ramp_delay8910; + + ramp_shift = S2MPA01_BUCK8910_RAMP_SHIFT; + break; + default: + return 0; + } + + if (!ramp_enable) + goto ramp_disable; + + /* Ramp delay can be enabled/disabled only for buck[1234] */ + if (rdev_get_id(rdev) >= S2MPA01_BUCK1 && + rdev_get_id(rdev) <= S2MPA01_BUCK4) { + ret = regmap_update_bits(rdev->regmap, S2MPA01_REG_RAMP1, + 1 << enable_shift, 1 << enable_shift); + if (ret) { + dev_err(&rdev->dev, "failed to enable ramp rate\n"); + return ret; + } + } + + ramp_val = get_ramp_delay(ramp_delay); + + return regmap_update_bits(rdev->regmap, ramp_reg, 0x3 << ramp_shift, + ramp_val << ramp_shift); + +ramp_disable: + return regmap_update_bits(rdev->regmap, S2MPA01_REG_RAMP1, + 1 << enable_shift, 0); +} + +static const struct regulator_ops s2mpa01_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, +}; + +static const struct regulator_ops s2mpa01_buck_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = s2mpa01_regulator_set_voltage_time_sel, + .set_ramp_delay = s2mpa01_set_ramp_delay, +}; + +#define regulator_desc_ldo(num, step) { \ + .name = "LDO"#num, \ + .id = S2MPA01_LDO##num, \ + .ops = &s2mpa01_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MIN_800_MV, \ + .uV_step = step, \ + .n_voltages = S2MPA01_LDO_N_VOLTAGES, \ + .vsel_reg = S2MPA01_REG_L1CTRL + num - 1, \ + .vsel_mask = S2MPA01_LDO_VSEL_MASK, \ + .enable_reg = S2MPA01_REG_L1CTRL + num - 1, \ + .enable_mask = S2MPA01_ENABLE_MASK \ +} + +#define regulator_desc_buck1_4(num) { \ + .name = "BUCK"#num, \ + .id = S2MPA01_BUCK##num, \ + .ops = &s2mpa01_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MIN_600_MV, \ + .uV_step = STEP_6_25_MV, \ + .n_voltages = S2MPA01_BUCK_N_VOLTAGES, \ + .ramp_delay = S2MPA01_RAMP_DELAY, \ + .vsel_reg = S2MPA01_REG_B1CTRL2 + (num - 1) * 2, \ + .vsel_mask = S2MPA01_BUCK_VSEL_MASK, \ + .enable_reg = S2MPA01_REG_B1CTRL1 + (num - 1) * 2, \ + .enable_mask = S2MPA01_ENABLE_MASK \ +} + +#define regulator_desc_buck5 { \ + .name = "BUCK5", \ + .id = S2MPA01_BUCK5, \ + .ops = &s2mpa01_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MIN_800_MV, \ + .uV_step = STEP_6_25_MV, \ + .n_voltages = S2MPA01_BUCK_N_VOLTAGES, \ + .ramp_delay = S2MPA01_RAMP_DELAY, \ + .vsel_reg = S2MPA01_REG_B5CTRL2, \ + .vsel_mask = S2MPA01_BUCK_VSEL_MASK, \ + .enable_reg = S2MPA01_REG_B5CTRL1, \ + .enable_mask = S2MPA01_ENABLE_MASK \ +} + +#define regulator_desc_buck6_10(num, min, step) { \ + .name = "BUCK"#num, \ + .id = S2MPA01_BUCK##num, \ + .ops = &s2mpa01_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = S2MPA01_BUCK_N_VOLTAGES, \ + .ramp_delay = S2MPA01_RAMP_DELAY, \ + .vsel_reg = S2MPA01_REG_B6CTRL2 + (num - 6) * 2, \ + .vsel_mask = S2MPA01_BUCK_VSEL_MASK, \ + .enable_reg = S2MPA01_REG_B6CTRL1 + (num - 6) * 2, \ + .enable_mask = S2MPA01_ENABLE_MASK \ +} + +static const struct regulator_desc regulators[] = { + regulator_desc_ldo(1, STEP_25_MV), + regulator_desc_ldo(2, STEP_50_MV), + regulator_desc_ldo(3, STEP_50_MV), + regulator_desc_ldo(4, STEP_50_MV), + regulator_desc_ldo(5, STEP_25_MV), + regulator_desc_ldo(6, STEP_25_MV), + regulator_desc_ldo(7, STEP_50_MV), + regulator_desc_ldo(8, STEP_50_MV), + regulator_desc_ldo(9, STEP_50_MV), + regulator_desc_ldo(10, STEP_50_MV), + regulator_desc_ldo(11, STEP_50_MV), + regulator_desc_ldo(12, STEP_50_MV), + regulator_desc_ldo(13, STEP_50_MV), + regulator_desc_ldo(14, STEP_50_MV), + regulator_desc_ldo(15, STEP_50_MV), + regulator_desc_ldo(16, STEP_50_MV), + regulator_desc_ldo(17, STEP_50_MV), + regulator_desc_ldo(18, STEP_50_MV), + regulator_desc_ldo(19, STEP_50_MV), + regulator_desc_ldo(20, STEP_50_MV), + regulator_desc_ldo(21, STEP_50_MV), + regulator_desc_ldo(22, STEP_50_MV), + regulator_desc_ldo(23, STEP_50_MV), + regulator_desc_ldo(24, STEP_50_MV), + regulator_desc_ldo(25, STEP_50_MV), + regulator_desc_ldo(26, STEP_25_MV), + regulator_desc_buck1_4(1), + regulator_desc_buck1_4(2), + regulator_desc_buck1_4(3), + regulator_desc_buck1_4(4), + regulator_desc_buck5, + regulator_desc_buck6_10(6, MIN_600_MV, STEP_6_25_MV), + regulator_desc_buck6_10(7, MIN_600_MV, STEP_6_25_MV), + regulator_desc_buck6_10(8, MIN_800_MV, STEP_12_5_MV), + regulator_desc_buck6_10(9, MIN_1500_MV, STEP_12_5_MV), + regulator_desc_buck6_10(10, MIN_1000_MV, STEP_12_5_MV), +}; + +static int s2mpa01_pmic_probe(struct platform_device *pdev) +{ + struct sec_pmic_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct sec_platform_data *pdata = dev_get_platdata(iodev->dev); + struct device_node *reg_np = NULL; + struct regulator_config config = { }; + struct of_regulator_match *rdata; + struct s2mpa01_info *s2mpa01; + int i; + + s2mpa01 = devm_kzalloc(&pdev->dev, sizeof(*s2mpa01), GFP_KERNEL); + if (!s2mpa01) + return -ENOMEM; + + rdata = s2mpa01->rdata; + for (i = 0; i < S2MPA01_REGULATOR_CNT; i++) + rdata[i].name = regulators[i].name; + + if (iodev->dev->of_node) { + reg_np = of_get_child_by_name(iodev->dev->of_node, + "regulators"); + if (!reg_np) { + dev_err(&pdev->dev, + "could not find regulators sub-node\n"); + return -EINVAL; + } + + of_regulator_match(&pdev->dev, reg_np, rdata, + S2MPA01_REGULATOR_MAX); + of_node_put(reg_np); + } + + platform_set_drvdata(pdev, s2mpa01); + + config.dev = &pdev->dev; + config.regmap = iodev->regmap_pmic; + config.driver_data = s2mpa01; + + for (i = 0; i < S2MPA01_REGULATOR_MAX; i++) { + struct regulator_dev *rdev; + if (pdata) + config.init_data = pdata->regulators[i].initdata; + else + config.init_data = rdata[i].init_data; + + if (reg_np) + config.of_node = rdata[i].of_node; + + rdev = devm_regulator_register(&pdev->dev, + ®ulators[i], &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "regulator init failed for %d\n", + i); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct platform_device_id s2mpa01_pmic_id[] = { + { "s2mpa01-pmic", 0}, + { }, +}; +MODULE_DEVICE_TABLE(platform, s2mpa01_pmic_id); + +static struct platform_driver s2mpa01_pmic_driver = { + .driver = { + .name = "s2mpa01-pmic", + }, + .probe = s2mpa01_pmic_probe, + .id_table = s2mpa01_pmic_id, +}; + +module_platform_driver(s2mpa01_pmic_driver); + +/* Module information */ +MODULE_AUTHOR("Sangbeom Kim <sbkim73@samsung.com>"); +MODULE_AUTHOR("Sachin Kamat <sachin.kamat@samsung.com>"); +MODULE_DESCRIPTION("SAMSUNG S2MPA01 Regulator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c new file mode 100644 index 000000000..7c598c156 --- /dev/null +++ b/drivers/regulator/s2mps11.c @@ -0,0 +1,1233 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (c) 2012-2014 Samsung Electronics Co., Ltd +// http://www.samsung.com + +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/of_gpio.h> +#include <linux/mfd/samsung/core.h> +#include <linux/mfd/samsung/s2mps11.h> +#include <linux/mfd/samsung/s2mps13.h> +#include <linux/mfd/samsung/s2mps14.h> +#include <linux/mfd/samsung/s2mps15.h> +#include <linux/mfd/samsung/s2mpu02.h> + +/* The highest number of possible regulators for supported devices. */ +#define S2MPS_REGULATOR_MAX S2MPS13_REGULATOR_MAX +struct s2mps11_info { + int ramp_delay2; + int ramp_delay34; + int ramp_delay5; + int ramp_delay16; + int ramp_delay7810; + int ramp_delay9; + + enum sec_device_type dev_type; + + /* + * One bit for each S2MPS13/S2MPS14/S2MPU02 regulator whether + * the suspend mode was enabled. + */ + DECLARE_BITMAP(suspend_state, S2MPS_REGULATOR_MAX); + + /* + * Array (size: number of regulators) with GPIO-s for external + * sleep control. + */ + int *ext_control_gpio; +}; + +static int get_ramp_delay(int ramp_delay) +{ + unsigned char cnt = 0; + + ramp_delay /= 6250; + + while (true) { + ramp_delay = ramp_delay >> 1; + if (ramp_delay == 0) + break; + cnt++; + } + + if (cnt > 3) + cnt = 3; + + return cnt; +} + +static int s2mps11_regulator_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + struct s2mps11_info *s2mps11 = rdev_get_drvdata(rdev); + unsigned int ramp_delay = 0; + int old_volt, new_volt; + + switch (rdev_get_id(rdev)) { + case S2MPS11_BUCK2: + ramp_delay = s2mps11->ramp_delay2; + break; + case S2MPS11_BUCK3: + case S2MPS11_BUCK4: + ramp_delay = s2mps11->ramp_delay34; + break; + case S2MPS11_BUCK5: + ramp_delay = s2mps11->ramp_delay5; + break; + case S2MPS11_BUCK6: + case S2MPS11_BUCK1: + ramp_delay = s2mps11->ramp_delay16; + break; + case S2MPS11_BUCK7: + case S2MPS11_BUCK8: + case S2MPS11_BUCK10: + ramp_delay = s2mps11->ramp_delay7810; + break; + case S2MPS11_BUCK9: + ramp_delay = s2mps11->ramp_delay9; + } + + if (ramp_delay == 0) + ramp_delay = rdev->desc->ramp_delay; + + old_volt = rdev->desc->min_uV + (rdev->desc->uV_step * old_selector); + new_volt = rdev->desc->min_uV + (rdev->desc->uV_step * new_selector); + + return DIV_ROUND_UP(abs(new_volt - old_volt), ramp_delay); +} + +static int s2mps11_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay) +{ + struct s2mps11_info *s2mps11 = rdev_get_drvdata(rdev); + unsigned int ramp_val, ramp_shift, ramp_reg = S2MPS11_REG_RAMP_BUCK; + unsigned int ramp_enable = 1, enable_shift = 0; + int ret; + + switch (rdev_get_id(rdev)) { + case S2MPS11_BUCK1: + if (ramp_delay > s2mps11->ramp_delay16) + s2mps11->ramp_delay16 = ramp_delay; + else + ramp_delay = s2mps11->ramp_delay16; + + ramp_shift = S2MPS11_BUCK16_RAMP_SHIFT; + break; + case S2MPS11_BUCK2: + enable_shift = S2MPS11_BUCK2_RAMP_EN_SHIFT; + if (!ramp_delay) { + ramp_enable = 0; + break; + } + + s2mps11->ramp_delay2 = ramp_delay; + ramp_shift = S2MPS11_BUCK2_RAMP_SHIFT; + ramp_reg = S2MPS11_REG_RAMP; + break; + case S2MPS11_BUCK3: + enable_shift = S2MPS11_BUCK3_RAMP_EN_SHIFT; + if (!ramp_delay) { + ramp_enable = 0; + break; + } + + if (ramp_delay > s2mps11->ramp_delay34) + s2mps11->ramp_delay34 = ramp_delay; + else + ramp_delay = s2mps11->ramp_delay34; + + ramp_shift = S2MPS11_BUCK34_RAMP_SHIFT; + ramp_reg = S2MPS11_REG_RAMP; + break; + case S2MPS11_BUCK4: + enable_shift = S2MPS11_BUCK4_RAMP_EN_SHIFT; + if (!ramp_delay) { + ramp_enable = 0; + break; + } + + if (ramp_delay > s2mps11->ramp_delay34) + s2mps11->ramp_delay34 = ramp_delay; + else + ramp_delay = s2mps11->ramp_delay34; + + ramp_shift = S2MPS11_BUCK34_RAMP_SHIFT; + ramp_reg = S2MPS11_REG_RAMP; + break; + case S2MPS11_BUCK5: + s2mps11->ramp_delay5 = ramp_delay; + ramp_shift = S2MPS11_BUCK5_RAMP_SHIFT; + break; + case S2MPS11_BUCK6: + enable_shift = S2MPS11_BUCK6_RAMP_EN_SHIFT; + if (!ramp_delay) { + ramp_enable = 0; + break; + } + + if (ramp_delay > s2mps11->ramp_delay16) + s2mps11->ramp_delay16 = ramp_delay; + else + ramp_delay = s2mps11->ramp_delay16; + + ramp_shift = S2MPS11_BUCK16_RAMP_SHIFT; + break; + case S2MPS11_BUCK7: + case S2MPS11_BUCK8: + case S2MPS11_BUCK10: + if (ramp_delay > s2mps11->ramp_delay7810) + s2mps11->ramp_delay7810 = ramp_delay; + else + ramp_delay = s2mps11->ramp_delay7810; + + ramp_shift = S2MPS11_BUCK7810_RAMP_SHIFT; + break; + case S2MPS11_BUCK9: + s2mps11->ramp_delay9 = ramp_delay; + ramp_shift = S2MPS11_BUCK9_RAMP_SHIFT; + break; + default: + return 0; + } + + if (!ramp_enable) + goto ramp_disable; + + /* Ramp delay can be enabled/disabled only for buck[2346] */ + if ((rdev_get_id(rdev) >= S2MPS11_BUCK2 && + rdev_get_id(rdev) <= S2MPS11_BUCK4) || + rdev_get_id(rdev) == S2MPS11_BUCK6) { + ret = regmap_update_bits(rdev->regmap, S2MPS11_REG_RAMP, + 1 << enable_shift, 1 << enable_shift); + if (ret) { + dev_err(&rdev->dev, "failed to enable ramp rate\n"); + return ret; + } + } + + ramp_val = get_ramp_delay(ramp_delay); + + return regmap_update_bits(rdev->regmap, ramp_reg, 0x3 << ramp_shift, + ramp_val << ramp_shift); + +ramp_disable: + return regmap_update_bits(rdev->regmap, S2MPS11_REG_RAMP, + 1 << enable_shift, 0); +} + +static const struct regulator_ops s2mps11_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, +}; + +static const struct regulator_ops s2mps11_buck_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = s2mps11_regulator_set_voltage_time_sel, + .set_ramp_delay = s2mps11_set_ramp_delay, +}; + +#define regulator_desc_s2mps11_ldo(num, step) { \ + .name = "LDO"#num, \ + .id = S2MPS11_LDO##num, \ + .ops = &s2mps11_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .ramp_delay = RAMP_DELAY_12_MVUS, \ + .min_uV = MIN_800_MV, \ + .uV_step = step, \ + .n_voltages = S2MPS11_LDO_N_VOLTAGES, \ + .vsel_reg = S2MPS11_REG_L1CTRL + num - 1, \ + .vsel_mask = S2MPS11_LDO_VSEL_MASK, \ + .enable_reg = S2MPS11_REG_L1CTRL + num - 1, \ + .enable_mask = S2MPS11_ENABLE_MASK \ +} + +#define regulator_desc_s2mps11_buck1_4(num) { \ + .name = "BUCK"#num, \ + .id = S2MPS11_BUCK##num, \ + .ops = &s2mps11_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MIN_600_MV, \ + .uV_step = STEP_6_25_MV, \ + .n_voltages = S2MPS11_BUCK_N_VOLTAGES, \ + .ramp_delay = S2MPS11_RAMP_DELAY, \ + .vsel_reg = S2MPS11_REG_B1CTRL2 + (num - 1) * 2, \ + .vsel_mask = S2MPS11_BUCK_VSEL_MASK, \ + .enable_reg = S2MPS11_REG_B1CTRL1 + (num - 1) * 2, \ + .enable_mask = S2MPS11_ENABLE_MASK \ +} + +#define regulator_desc_s2mps11_buck5 { \ + .name = "BUCK5", \ + .id = S2MPS11_BUCK5, \ + .ops = &s2mps11_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MIN_600_MV, \ + .uV_step = STEP_6_25_MV, \ + .n_voltages = S2MPS11_BUCK_N_VOLTAGES, \ + .ramp_delay = S2MPS11_RAMP_DELAY, \ + .vsel_reg = S2MPS11_REG_B5CTRL2, \ + .vsel_mask = S2MPS11_BUCK_VSEL_MASK, \ + .enable_reg = S2MPS11_REG_B5CTRL1, \ + .enable_mask = S2MPS11_ENABLE_MASK \ +} + +#define regulator_desc_s2mps11_buck67810(num, min, step) { \ + .name = "BUCK"#num, \ + .id = S2MPS11_BUCK##num, \ + .ops = &s2mps11_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = S2MPS11_BUCK_N_VOLTAGES, \ + .ramp_delay = S2MPS11_RAMP_DELAY, \ + .vsel_reg = S2MPS11_REG_B6CTRL2 + (num - 6) * 2, \ + .vsel_mask = S2MPS11_BUCK_VSEL_MASK, \ + .enable_reg = S2MPS11_REG_B6CTRL1 + (num - 6) * 2, \ + .enable_mask = S2MPS11_ENABLE_MASK \ +} + +#define regulator_desc_s2mps11_buck9 { \ + .name = "BUCK9", \ + .id = S2MPS11_BUCK9, \ + .ops = &s2mps11_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = MIN_3000_MV, \ + .uV_step = STEP_25_MV, \ + .n_voltages = S2MPS11_BUCK9_N_VOLTAGES, \ + .ramp_delay = S2MPS11_RAMP_DELAY, \ + .vsel_reg = S2MPS11_REG_B9CTRL2, \ + .vsel_mask = S2MPS11_BUCK9_VSEL_MASK, \ + .enable_reg = S2MPS11_REG_B9CTRL1, \ + .enable_mask = S2MPS11_ENABLE_MASK \ +} + +static const struct regulator_desc s2mps11_regulators[] = { + regulator_desc_s2mps11_ldo(1, STEP_25_MV), + regulator_desc_s2mps11_ldo(2, STEP_50_MV), + regulator_desc_s2mps11_ldo(3, STEP_50_MV), + regulator_desc_s2mps11_ldo(4, STEP_50_MV), + regulator_desc_s2mps11_ldo(5, STEP_50_MV), + regulator_desc_s2mps11_ldo(6, STEP_25_MV), + regulator_desc_s2mps11_ldo(7, STEP_50_MV), + regulator_desc_s2mps11_ldo(8, STEP_50_MV), + regulator_desc_s2mps11_ldo(9, STEP_50_MV), + regulator_desc_s2mps11_ldo(10, STEP_50_MV), + regulator_desc_s2mps11_ldo(11, STEP_25_MV), + regulator_desc_s2mps11_ldo(12, STEP_50_MV), + regulator_desc_s2mps11_ldo(13, STEP_50_MV), + regulator_desc_s2mps11_ldo(14, STEP_50_MV), + regulator_desc_s2mps11_ldo(15, STEP_50_MV), + regulator_desc_s2mps11_ldo(16, STEP_50_MV), + regulator_desc_s2mps11_ldo(17, STEP_50_MV), + regulator_desc_s2mps11_ldo(18, STEP_50_MV), + regulator_desc_s2mps11_ldo(19, STEP_50_MV), + regulator_desc_s2mps11_ldo(20, STEP_50_MV), + regulator_desc_s2mps11_ldo(21, STEP_50_MV), + regulator_desc_s2mps11_ldo(22, STEP_25_MV), + regulator_desc_s2mps11_ldo(23, STEP_25_MV), + regulator_desc_s2mps11_ldo(24, STEP_50_MV), + regulator_desc_s2mps11_ldo(25, STEP_50_MV), + regulator_desc_s2mps11_ldo(26, STEP_50_MV), + regulator_desc_s2mps11_ldo(27, STEP_25_MV), + regulator_desc_s2mps11_ldo(28, STEP_50_MV), + regulator_desc_s2mps11_ldo(29, STEP_50_MV), + regulator_desc_s2mps11_ldo(30, STEP_50_MV), + regulator_desc_s2mps11_ldo(31, STEP_50_MV), + regulator_desc_s2mps11_ldo(32, STEP_50_MV), + regulator_desc_s2mps11_ldo(33, STEP_50_MV), + regulator_desc_s2mps11_ldo(34, STEP_50_MV), + regulator_desc_s2mps11_ldo(35, STEP_25_MV), + regulator_desc_s2mps11_ldo(36, STEP_50_MV), + regulator_desc_s2mps11_ldo(37, STEP_50_MV), + regulator_desc_s2mps11_ldo(38, STEP_50_MV), + regulator_desc_s2mps11_buck1_4(1), + regulator_desc_s2mps11_buck1_4(2), + regulator_desc_s2mps11_buck1_4(3), + regulator_desc_s2mps11_buck1_4(4), + regulator_desc_s2mps11_buck5, + regulator_desc_s2mps11_buck67810(6, MIN_600_MV, STEP_6_25_MV), + regulator_desc_s2mps11_buck67810(7, MIN_750_MV, STEP_12_5_MV), + regulator_desc_s2mps11_buck67810(8, MIN_750_MV, STEP_12_5_MV), + regulator_desc_s2mps11_buck9, + regulator_desc_s2mps11_buck67810(10, MIN_750_MV, STEP_12_5_MV), +}; + +static const struct regulator_ops s2mps14_reg_ops; + +#define regulator_desc_s2mps13_ldo(num, min, step, min_sel) { \ + .name = "LDO"#num, \ + .id = S2MPS13_LDO##num, \ + .ops = &s2mps14_reg_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = min, \ + .uV_step = step, \ + .linear_min_sel = min_sel, \ + .n_voltages = S2MPS14_LDO_N_VOLTAGES, \ + .vsel_reg = S2MPS13_REG_L1CTRL + num - 1, \ + .vsel_mask = S2MPS14_LDO_VSEL_MASK, \ + .enable_reg = S2MPS13_REG_L1CTRL + num - 1, \ + .enable_mask = S2MPS14_ENABLE_MASK \ +} + +#define regulator_desc_s2mps13_buck(num, min, step, min_sel) { \ + .name = "BUCK"#num, \ + .id = S2MPS13_BUCK##num, \ + .ops = &s2mps14_reg_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = min, \ + .uV_step = step, \ + .linear_min_sel = min_sel, \ + .n_voltages = S2MPS14_BUCK_N_VOLTAGES, \ + .ramp_delay = S2MPS13_BUCK_RAMP_DELAY, \ + .vsel_reg = S2MPS13_REG_B1OUT + (num - 1) * 2, \ + .vsel_mask = S2MPS14_BUCK_VSEL_MASK, \ + .enable_reg = S2MPS13_REG_B1CTRL + (num - 1) * 2, \ + .enable_mask = S2MPS14_ENABLE_MASK \ +} + +#define regulator_desc_s2mps13_buck7(num, min, step, min_sel) { \ + .name = "BUCK"#num, \ + .id = S2MPS13_BUCK##num, \ + .ops = &s2mps14_reg_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = min, \ + .uV_step = step, \ + .linear_min_sel = min_sel, \ + .n_voltages = S2MPS14_BUCK_N_VOLTAGES, \ + .ramp_delay = S2MPS13_BUCK_RAMP_DELAY, \ + .vsel_reg = S2MPS13_REG_B1OUT + (num) * 2 - 1, \ + .vsel_mask = S2MPS14_BUCK_VSEL_MASK, \ + .enable_reg = S2MPS13_REG_B1CTRL + (num - 1) * 2, \ + .enable_mask = S2MPS14_ENABLE_MASK \ +} + +#define regulator_desc_s2mps13_buck8_10(num, min, step, min_sel) { \ + .name = "BUCK"#num, \ + .id = S2MPS13_BUCK##num, \ + .ops = &s2mps14_reg_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = min, \ + .uV_step = step, \ + .linear_min_sel = min_sel, \ + .n_voltages = S2MPS14_BUCK_N_VOLTAGES, \ + .ramp_delay = S2MPS13_BUCK_RAMP_DELAY, \ + .vsel_reg = S2MPS13_REG_B1OUT + (num) * 2 - 1, \ + .vsel_mask = S2MPS14_BUCK_VSEL_MASK, \ + .enable_reg = S2MPS13_REG_B1CTRL + (num) * 2 - 1, \ + .enable_mask = S2MPS14_ENABLE_MASK \ +} + +static const struct regulator_desc s2mps13_regulators[] = { + regulator_desc_s2mps13_ldo(1, MIN_800_MV, STEP_12_5_MV, 0x00), + regulator_desc_s2mps13_ldo(2, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(3, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(4, MIN_800_MV, STEP_12_5_MV, 0x00), + regulator_desc_s2mps13_ldo(5, MIN_800_MV, STEP_12_5_MV, 0x00), + regulator_desc_s2mps13_ldo(6, MIN_800_MV, STEP_12_5_MV, 0x00), + regulator_desc_s2mps13_ldo(7, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(8, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(9, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(10, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(11, MIN_800_MV, STEP_25_MV, 0x10), + regulator_desc_s2mps13_ldo(12, MIN_800_MV, STEP_25_MV, 0x10), + regulator_desc_s2mps13_ldo(13, MIN_800_MV, STEP_25_MV, 0x10), + regulator_desc_s2mps13_ldo(14, MIN_800_MV, STEP_12_5_MV, 0x00), + regulator_desc_s2mps13_ldo(15, MIN_800_MV, STEP_12_5_MV, 0x00), + regulator_desc_s2mps13_ldo(16, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(17, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(18, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(19, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(20, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(21, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(22, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(23, MIN_800_MV, STEP_12_5_MV, 0x00), + regulator_desc_s2mps13_ldo(24, MIN_800_MV, STEP_12_5_MV, 0x00), + regulator_desc_s2mps13_ldo(25, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(26, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(27, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(28, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(29, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(30, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(31, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(32, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(33, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(34, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(35, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(36, MIN_800_MV, STEP_12_5_MV, 0x00), + regulator_desc_s2mps13_ldo(37, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(38, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_ldo(39, MIN_1000_MV, STEP_25_MV, 0x08), + regulator_desc_s2mps13_ldo(40, MIN_1400_MV, STEP_50_MV, 0x0C), + regulator_desc_s2mps13_buck(1, MIN_500_MV, STEP_6_25_MV, 0x10), + regulator_desc_s2mps13_buck(2, MIN_500_MV, STEP_6_25_MV, 0x10), + regulator_desc_s2mps13_buck(3, MIN_500_MV, STEP_6_25_MV, 0x10), + regulator_desc_s2mps13_buck(4, MIN_500_MV, STEP_6_25_MV, 0x10), + regulator_desc_s2mps13_buck(5, MIN_500_MV, STEP_6_25_MV, 0x10), + regulator_desc_s2mps13_buck(6, MIN_500_MV, STEP_6_25_MV, 0x10), + regulator_desc_s2mps13_buck7(7, MIN_500_MV, STEP_6_25_MV, 0x10), + regulator_desc_s2mps13_buck8_10(8, MIN_1000_MV, STEP_12_5_MV, 0x20), + regulator_desc_s2mps13_buck8_10(9, MIN_1000_MV, STEP_12_5_MV, 0x20), + regulator_desc_s2mps13_buck8_10(10, MIN_500_MV, STEP_6_25_MV, 0x10), +}; + +static int s2mps14_regulator_enable(struct regulator_dev *rdev) +{ + struct s2mps11_info *s2mps11 = rdev_get_drvdata(rdev); + unsigned int val; + + switch (s2mps11->dev_type) { + case S2MPS13X: + case S2MPS14X: + if (test_bit(rdev_get_id(rdev), s2mps11->suspend_state)) + val = S2MPS14_ENABLE_SUSPEND; + else if (gpio_is_valid(s2mps11->ext_control_gpio[rdev_get_id(rdev)])) + val = S2MPS14_ENABLE_EXT_CONTROL; + else + val = rdev->desc->enable_mask; + break; + case S2MPU02: + if (test_bit(rdev_get_id(rdev), s2mps11->suspend_state)) + val = S2MPU02_ENABLE_SUSPEND; + else + val = rdev->desc->enable_mask; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, val); +} + +static int s2mps14_regulator_set_suspend_disable(struct regulator_dev *rdev) +{ + int ret; + unsigned int val, state; + struct s2mps11_info *s2mps11 = rdev_get_drvdata(rdev); + int rdev_id = rdev_get_id(rdev); + + /* Below LDO should be always on or does not support suspend mode. */ + switch (s2mps11->dev_type) { + case S2MPS13X: + case S2MPS14X: + switch (rdev_id) { + case S2MPS14_LDO3: + return 0; + default: + state = S2MPS14_ENABLE_SUSPEND; + break; + } + break; + case S2MPU02: + switch (rdev_id) { + case S2MPU02_LDO13: + case S2MPU02_LDO14: + case S2MPU02_LDO15: + case S2MPU02_LDO17: + case S2MPU02_BUCK7: + state = S2MPU02_DISABLE_SUSPEND; + break; + default: + state = S2MPU02_ENABLE_SUSPEND; + break; + } + break; + default: + return -EINVAL; + } + + ret = regmap_read(rdev->regmap, rdev->desc->enable_reg, &val); + if (ret < 0) + return ret; + + set_bit(rdev_get_id(rdev), s2mps11->suspend_state); + /* + * Don't enable suspend mode if regulator is already disabled because + * this would effectively for a short time turn on the regulator after + * resuming. + * However we still want to toggle the suspend_state bit for regulator + * in case if it got enabled before suspending the system. + */ + if (!(val & rdev->desc->enable_mask)) + return 0; + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, state); +} + +static const struct regulator_ops s2mps14_reg_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = s2mps14_regulator_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_disable = s2mps14_regulator_set_suspend_disable, +}; + +#define regulator_desc_s2mps14_ldo(num, min, step) { \ + .name = "LDO"#num, \ + .id = S2MPS14_LDO##num, \ + .ops = &s2mps14_reg_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = S2MPS14_LDO_N_VOLTAGES, \ + .vsel_reg = S2MPS14_REG_L1CTRL + num - 1, \ + .vsel_mask = S2MPS14_LDO_VSEL_MASK, \ + .enable_reg = S2MPS14_REG_L1CTRL + num - 1, \ + .enable_mask = S2MPS14_ENABLE_MASK \ +} + +#define regulator_desc_s2mps14_buck(num, min, step, min_sel) { \ + .name = "BUCK"#num, \ + .id = S2MPS14_BUCK##num, \ + .ops = &s2mps14_reg_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = S2MPS14_BUCK_N_VOLTAGES, \ + .linear_min_sel = min_sel, \ + .ramp_delay = S2MPS14_BUCK_RAMP_DELAY, \ + .vsel_reg = S2MPS14_REG_B1CTRL2 + (num - 1) * 2, \ + .vsel_mask = S2MPS14_BUCK_VSEL_MASK, \ + .enable_reg = S2MPS14_REG_B1CTRL1 + (num - 1) * 2, \ + .enable_mask = S2MPS14_ENABLE_MASK \ +} + +static const struct regulator_desc s2mps14_regulators[] = { + regulator_desc_s2mps14_ldo(1, MIN_800_MV, STEP_12_5_MV), + regulator_desc_s2mps14_ldo(2, MIN_800_MV, STEP_12_5_MV), + regulator_desc_s2mps14_ldo(3, MIN_800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(4, MIN_800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(5, MIN_800_MV, STEP_12_5_MV), + regulator_desc_s2mps14_ldo(6, MIN_800_MV, STEP_12_5_MV), + regulator_desc_s2mps14_ldo(7, MIN_800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(8, MIN_1800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(9, MIN_800_MV, STEP_12_5_MV), + regulator_desc_s2mps14_ldo(10, MIN_800_MV, STEP_12_5_MV), + regulator_desc_s2mps14_ldo(11, MIN_800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(12, MIN_1800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(13, MIN_1800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(14, MIN_1800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(15, MIN_1800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(16, MIN_1800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(17, MIN_1800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(18, MIN_1800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(19, MIN_800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(20, MIN_800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(21, MIN_800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(22, MIN_800_MV, STEP_12_5_MV), + regulator_desc_s2mps14_ldo(23, MIN_800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(24, MIN_1800_MV, STEP_25_MV), + regulator_desc_s2mps14_ldo(25, MIN_1800_MV, STEP_25_MV), + regulator_desc_s2mps14_buck(1, MIN_600_MV, STEP_6_25_MV, + S2MPS14_BUCK1235_START_SEL), + regulator_desc_s2mps14_buck(2, MIN_600_MV, STEP_6_25_MV, + S2MPS14_BUCK1235_START_SEL), + regulator_desc_s2mps14_buck(3, MIN_600_MV, STEP_6_25_MV, + S2MPS14_BUCK1235_START_SEL), + regulator_desc_s2mps14_buck(4, MIN_1400_MV, STEP_12_5_MV, + S2MPS14_BUCK4_START_SEL), + regulator_desc_s2mps14_buck(5, MIN_600_MV, STEP_6_25_MV, + S2MPS14_BUCK1235_START_SEL), +}; + +static const struct regulator_ops s2mps15_reg_ldo_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_ops s2mps15_reg_buck_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, +}; + +#define regulator_desc_s2mps15_ldo(num, range) { \ + .name = "LDO"#num, \ + .id = S2MPS15_LDO##num, \ + .ops = &s2mps15_reg_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .linear_ranges = range, \ + .n_linear_ranges = ARRAY_SIZE(range), \ + .n_voltages = S2MPS15_LDO_N_VOLTAGES, \ + .vsel_reg = S2MPS15_REG_L1CTRL + num - 1, \ + .vsel_mask = S2MPS15_LDO_VSEL_MASK, \ + .enable_reg = S2MPS15_REG_L1CTRL + num - 1, \ + .enable_mask = S2MPS15_ENABLE_MASK \ +} + +#define regulator_desc_s2mps15_buck(num, range) { \ + .name = "BUCK"#num, \ + .id = S2MPS15_BUCK##num, \ + .ops = &s2mps15_reg_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .linear_ranges = range, \ + .n_linear_ranges = ARRAY_SIZE(range), \ + .ramp_delay = 12500, \ + .n_voltages = S2MPS15_BUCK_N_VOLTAGES, \ + .vsel_reg = S2MPS15_REG_B1CTRL2 + ((num - 1) * 2), \ + .vsel_mask = S2MPS15_BUCK_VSEL_MASK, \ + .enable_reg = S2MPS15_REG_B1CTRL1 + ((num - 1) * 2), \ + .enable_mask = S2MPS15_ENABLE_MASK \ +} + +/* voltage range for s2mps15 LDO 3, 5, 15, 16, 18, 20, 23 and 27 */ +static const struct regulator_linear_range s2mps15_ldo_voltage_ranges1[] = { + REGULATOR_LINEAR_RANGE(1000000, 0xc, 0x38, 25000), +}; + +/* voltage range for s2mps15 LDO 2, 6, 14, 17, 19, 21, 24 and 25 */ +static const struct regulator_linear_range s2mps15_ldo_voltage_ranges2[] = { + REGULATOR_LINEAR_RANGE(1800000, 0x0, 0x3f, 25000), +}; + +/* voltage range for s2mps15 LDO 4, 11, 12, 13, 22 and 26 */ +static const struct regulator_linear_range s2mps15_ldo_voltage_ranges3[] = { + REGULATOR_LINEAR_RANGE(700000, 0x0, 0x34, 12500), +}; + +/* voltage range for s2mps15 LDO 7, 8, 9 and 10 */ +static const struct regulator_linear_range s2mps15_ldo_voltage_ranges4[] = { + REGULATOR_LINEAR_RANGE(700000, 0x10, 0x20, 25000), +}; + +/* voltage range for s2mps15 LDO 1 */ +static const struct regulator_linear_range s2mps15_ldo_voltage_ranges5[] = { + REGULATOR_LINEAR_RANGE(500000, 0x0, 0x20, 12500), +}; + +/* voltage range for s2mps15 BUCK 1, 2, 3, 4, 5, 6 and 7 */ +static const struct regulator_linear_range s2mps15_buck_voltage_ranges1[] = { + REGULATOR_LINEAR_RANGE(500000, 0x20, 0xc0, 6250), +}; + +/* voltage range for s2mps15 BUCK 8, 9 and 10 */ +static const struct regulator_linear_range s2mps15_buck_voltage_ranges2[] = { + REGULATOR_LINEAR_RANGE(1000000, 0x20, 0x78, 12500), +}; + +static const struct regulator_desc s2mps15_regulators[] = { + regulator_desc_s2mps15_ldo(1, s2mps15_ldo_voltage_ranges5), + regulator_desc_s2mps15_ldo(2, s2mps15_ldo_voltage_ranges2), + regulator_desc_s2mps15_ldo(3, s2mps15_ldo_voltage_ranges1), + regulator_desc_s2mps15_ldo(4, s2mps15_ldo_voltage_ranges3), + regulator_desc_s2mps15_ldo(5, s2mps15_ldo_voltage_ranges1), + regulator_desc_s2mps15_ldo(6, s2mps15_ldo_voltage_ranges2), + regulator_desc_s2mps15_ldo(7, s2mps15_ldo_voltage_ranges4), + regulator_desc_s2mps15_ldo(8, s2mps15_ldo_voltage_ranges4), + regulator_desc_s2mps15_ldo(9, s2mps15_ldo_voltage_ranges4), + regulator_desc_s2mps15_ldo(10, s2mps15_ldo_voltage_ranges4), + regulator_desc_s2mps15_ldo(11, s2mps15_ldo_voltage_ranges3), + regulator_desc_s2mps15_ldo(12, s2mps15_ldo_voltage_ranges3), + regulator_desc_s2mps15_ldo(13, s2mps15_ldo_voltage_ranges3), + regulator_desc_s2mps15_ldo(14, s2mps15_ldo_voltage_ranges2), + regulator_desc_s2mps15_ldo(15, s2mps15_ldo_voltage_ranges1), + regulator_desc_s2mps15_ldo(16, s2mps15_ldo_voltage_ranges1), + regulator_desc_s2mps15_ldo(17, s2mps15_ldo_voltage_ranges2), + regulator_desc_s2mps15_ldo(18, s2mps15_ldo_voltage_ranges1), + regulator_desc_s2mps15_ldo(19, s2mps15_ldo_voltage_ranges2), + regulator_desc_s2mps15_ldo(20, s2mps15_ldo_voltage_ranges1), + regulator_desc_s2mps15_ldo(21, s2mps15_ldo_voltage_ranges2), + regulator_desc_s2mps15_ldo(22, s2mps15_ldo_voltage_ranges3), + regulator_desc_s2mps15_ldo(23, s2mps15_ldo_voltage_ranges1), + regulator_desc_s2mps15_ldo(24, s2mps15_ldo_voltage_ranges2), + regulator_desc_s2mps15_ldo(25, s2mps15_ldo_voltage_ranges2), + regulator_desc_s2mps15_ldo(26, s2mps15_ldo_voltage_ranges3), + regulator_desc_s2mps15_ldo(27, s2mps15_ldo_voltage_ranges1), + regulator_desc_s2mps15_buck(1, s2mps15_buck_voltage_ranges1), + regulator_desc_s2mps15_buck(2, s2mps15_buck_voltage_ranges1), + regulator_desc_s2mps15_buck(3, s2mps15_buck_voltage_ranges1), + regulator_desc_s2mps15_buck(4, s2mps15_buck_voltage_ranges1), + regulator_desc_s2mps15_buck(5, s2mps15_buck_voltage_ranges1), + regulator_desc_s2mps15_buck(6, s2mps15_buck_voltage_ranges1), + regulator_desc_s2mps15_buck(7, s2mps15_buck_voltage_ranges1), + regulator_desc_s2mps15_buck(8, s2mps15_buck_voltage_ranges2), + regulator_desc_s2mps15_buck(9, s2mps15_buck_voltage_ranges2), + regulator_desc_s2mps15_buck(10, s2mps15_buck_voltage_ranges2), +}; + +static int s2mps14_pmic_enable_ext_control(struct s2mps11_info *s2mps11, + struct regulator_dev *rdev) +{ + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, S2MPS14_ENABLE_EXT_CONTROL); +} + +static void s2mps14_pmic_dt_parse_ext_control_gpio(struct platform_device *pdev, + struct of_regulator_match *rdata, struct s2mps11_info *s2mps11) +{ + int *gpio = s2mps11->ext_control_gpio; + unsigned int i; + unsigned int valid_regulators[3] = { S2MPS14_LDO10, S2MPS14_LDO11, + S2MPS14_LDO12 }; + + for (i = 0; i < ARRAY_SIZE(valid_regulators); i++) { + unsigned int reg = valid_regulators[i]; + + if (!rdata[reg].init_data || !rdata[reg].of_node) + continue; + + gpio[reg] = of_get_named_gpio(rdata[reg].of_node, + "samsung,ext-control-gpios", 0); + if (gpio_is_valid(gpio[reg])) + dev_dbg(&pdev->dev, "Using GPIO %d for ext-control over %d/%s\n", + gpio[reg], reg, rdata[reg].name); + } +} + +static int s2mps11_pmic_dt_parse(struct platform_device *pdev, + struct of_regulator_match *rdata, struct s2mps11_info *s2mps11, + unsigned int rdev_num) +{ + struct device_node *reg_np; + + reg_np = of_get_child_by_name(pdev->dev.parent->of_node, "regulators"); + if (!reg_np) { + dev_err(&pdev->dev, "could not find regulators sub-node\n"); + return -EINVAL; + } + + of_regulator_match(&pdev->dev, reg_np, rdata, rdev_num); + if (s2mps11->dev_type == S2MPS14X) + s2mps14_pmic_dt_parse_ext_control_gpio(pdev, rdata, s2mps11); + + of_node_put(reg_np); + + return 0; +} + +static int s2mpu02_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay) +{ + unsigned int ramp_val, ramp_shift, ramp_reg; + + switch (rdev_get_id(rdev)) { + case S2MPU02_BUCK1: + ramp_shift = S2MPU02_BUCK1_RAMP_SHIFT; + break; + case S2MPU02_BUCK2: + ramp_shift = S2MPU02_BUCK2_RAMP_SHIFT; + break; + case S2MPU02_BUCK3: + ramp_shift = S2MPU02_BUCK3_RAMP_SHIFT; + break; + case S2MPU02_BUCK4: + ramp_shift = S2MPU02_BUCK4_RAMP_SHIFT; + break; + default: + return 0; + } + ramp_reg = S2MPU02_REG_RAMP1; + ramp_val = get_ramp_delay(ramp_delay); + + return regmap_update_bits(rdev->regmap, ramp_reg, + S2MPU02_BUCK1234_RAMP_MASK << ramp_shift, + ramp_val << ramp_shift); +} + +static const struct regulator_ops s2mpu02_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = s2mps14_regulator_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_disable = s2mps14_regulator_set_suspend_disable, +}; + +static const struct regulator_ops s2mpu02_buck_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = s2mps14_regulator_enable, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_disable = s2mps14_regulator_set_suspend_disable, + .set_ramp_delay = s2mpu02_set_ramp_delay, +}; + +#define regulator_desc_s2mpu02_ldo1(num) { \ + .name = "LDO"#num, \ + .id = S2MPU02_LDO##num, \ + .ops = &s2mpu02_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = S2MPU02_LDO_MIN_900MV, \ + .uV_step = S2MPU02_LDO_STEP_12_5MV, \ + .linear_min_sel = S2MPU02_LDO_GROUP1_START_SEL, \ + .n_voltages = S2MPU02_LDO_N_VOLTAGES, \ + .vsel_reg = S2MPU02_REG_L1CTRL, \ + .vsel_mask = S2MPU02_LDO_VSEL_MASK, \ + .enable_reg = S2MPU02_REG_L1CTRL, \ + .enable_mask = S2MPU02_ENABLE_MASK \ +} +#define regulator_desc_s2mpu02_ldo2(num) { \ + .name = "LDO"#num, \ + .id = S2MPU02_LDO##num, \ + .ops = &s2mpu02_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = S2MPU02_LDO_MIN_1050MV, \ + .uV_step = S2MPU02_LDO_STEP_25MV, \ + .linear_min_sel = S2MPU02_LDO_GROUP2_START_SEL, \ + .n_voltages = S2MPU02_LDO_N_VOLTAGES, \ + .vsel_reg = S2MPU02_REG_L2CTRL1, \ + .vsel_mask = S2MPU02_LDO_VSEL_MASK, \ + .enable_reg = S2MPU02_REG_L2CTRL1, \ + .enable_mask = S2MPU02_ENABLE_MASK \ +} +#define regulator_desc_s2mpu02_ldo3(num) { \ + .name = "LDO"#num, \ + .id = S2MPU02_LDO##num, \ + .ops = &s2mpu02_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = S2MPU02_LDO_MIN_900MV, \ + .uV_step = S2MPU02_LDO_STEP_12_5MV, \ + .linear_min_sel = S2MPU02_LDO_GROUP1_START_SEL, \ + .n_voltages = S2MPU02_LDO_N_VOLTAGES, \ + .vsel_reg = S2MPU02_REG_L3CTRL + num - 3, \ + .vsel_mask = S2MPU02_LDO_VSEL_MASK, \ + .enable_reg = S2MPU02_REG_L3CTRL + num - 3, \ + .enable_mask = S2MPU02_ENABLE_MASK \ +} +#define regulator_desc_s2mpu02_ldo4(num) { \ + .name = "LDO"#num, \ + .id = S2MPU02_LDO##num, \ + .ops = &s2mpu02_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = S2MPU02_LDO_MIN_1050MV, \ + .uV_step = S2MPU02_LDO_STEP_25MV, \ + .linear_min_sel = S2MPU02_LDO_GROUP2_START_SEL, \ + .n_voltages = S2MPU02_LDO_N_VOLTAGES, \ + .vsel_reg = S2MPU02_REG_L3CTRL + num - 3, \ + .vsel_mask = S2MPU02_LDO_VSEL_MASK, \ + .enable_reg = S2MPU02_REG_L3CTRL + num - 3, \ + .enable_mask = S2MPU02_ENABLE_MASK \ +} +#define regulator_desc_s2mpu02_ldo5(num) { \ + .name = "LDO"#num, \ + .id = S2MPU02_LDO##num, \ + .ops = &s2mpu02_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = S2MPU02_LDO_MIN_1600MV, \ + .uV_step = S2MPU02_LDO_STEP_50MV, \ + .linear_min_sel = S2MPU02_LDO_GROUP3_START_SEL, \ + .n_voltages = S2MPU02_LDO_N_VOLTAGES, \ + .vsel_reg = S2MPU02_REG_L3CTRL + num - 3, \ + .vsel_mask = S2MPU02_LDO_VSEL_MASK, \ + .enable_reg = S2MPU02_REG_L3CTRL + num - 3, \ + .enable_mask = S2MPU02_ENABLE_MASK \ +} + +#define regulator_desc_s2mpu02_buck1234(num) { \ + .name = "BUCK"#num, \ + .id = S2MPU02_BUCK##num, \ + .ops = &s2mpu02_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = S2MPU02_BUCK1234_MIN_600MV, \ + .uV_step = S2MPU02_BUCK1234_STEP_6_25MV, \ + .n_voltages = S2MPU02_BUCK_N_VOLTAGES, \ + .linear_min_sel = S2MPU02_BUCK1234_START_SEL, \ + .ramp_delay = S2MPU02_BUCK_RAMP_DELAY, \ + .vsel_reg = S2MPU02_REG_B1CTRL2 + (num - 1) * 2, \ + .vsel_mask = S2MPU02_BUCK_VSEL_MASK, \ + .enable_reg = S2MPU02_REG_B1CTRL1 + (num - 1) * 2, \ + .enable_mask = S2MPU02_ENABLE_MASK \ +} +#define regulator_desc_s2mpu02_buck5(num) { \ + .name = "BUCK"#num, \ + .id = S2MPU02_BUCK##num, \ + .ops = &s2mpu02_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = S2MPU02_BUCK5_MIN_1081_25MV, \ + .uV_step = S2MPU02_BUCK5_STEP_6_25MV, \ + .n_voltages = S2MPU02_BUCK_N_VOLTAGES, \ + .linear_min_sel = S2MPU02_BUCK5_START_SEL, \ + .ramp_delay = S2MPU02_BUCK_RAMP_DELAY, \ + .vsel_reg = S2MPU02_REG_B5CTRL2, \ + .vsel_mask = S2MPU02_BUCK_VSEL_MASK, \ + .enable_reg = S2MPU02_REG_B5CTRL1, \ + .enable_mask = S2MPU02_ENABLE_MASK \ +} +#define regulator_desc_s2mpu02_buck6(num) { \ + .name = "BUCK"#num, \ + .id = S2MPU02_BUCK##num, \ + .ops = &s2mpu02_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = S2MPU02_BUCK6_MIN_1700MV, \ + .uV_step = S2MPU02_BUCK6_STEP_2_50MV, \ + .n_voltages = S2MPU02_BUCK_N_VOLTAGES, \ + .linear_min_sel = S2MPU02_BUCK6_START_SEL, \ + .ramp_delay = S2MPU02_BUCK_RAMP_DELAY, \ + .vsel_reg = S2MPU02_REG_B6CTRL2, \ + .vsel_mask = S2MPU02_BUCK_VSEL_MASK, \ + .enable_reg = S2MPU02_REG_B6CTRL1, \ + .enable_mask = S2MPU02_ENABLE_MASK \ +} +#define regulator_desc_s2mpu02_buck7(num) { \ + .name = "BUCK"#num, \ + .id = S2MPU02_BUCK##num, \ + .ops = &s2mpu02_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = S2MPU02_BUCK7_MIN_900MV, \ + .uV_step = S2MPU02_BUCK7_STEP_6_25MV, \ + .n_voltages = S2MPU02_BUCK_N_VOLTAGES, \ + .linear_min_sel = S2MPU02_BUCK7_START_SEL, \ + .ramp_delay = S2MPU02_BUCK_RAMP_DELAY, \ + .vsel_reg = S2MPU02_REG_B7CTRL2, \ + .vsel_mask = S2MPU02_BUCK_VSEL_MASK, \ + .enable_reg = S2MPU02_REG_B7CTRL1, \ + .enable_mask = S2MPU02_ENABLE_MASK \ +} + +static const struct regulator_desc s2mpu02_regulators[] = { + regulator_desc_s2mpu02_ldo1(1), + regulator_desc_s2mpu02_ldo2(2), + regulator_desc_s2mpu02_ldo4(3), + regulator_desc_s2mpu02_ldo5(4), + regulator_desc_s2mpu02_ldo4(5), + regulator_desc_s2mpu02_ldo3(6), + regulator_desc_s2mpu02_ldo3(7), + regulator_desc_s2mpu02_ldo4(8), + regulator_desc_s2mpu02_ldo5(9), + regulator_desc_s2mpu02_ldo3(10), + regulator_desc_s2mpu02_ldo4(11), + regulator_desc_s2mpu02_ldo5(12), + regulator_desc_s2mpu02_ldo5(13), + regulator_desc_s2mpu02_ldo5(14), + regulator_desc_s2mpu02_ldo5(15), + regulator_desc_s2mpu02_ldo5(16), + regulator_desc_s2mpu02_ldo4(17), + regulator_desc_s2mpu02_ldo5(18), + regulator_desc_s2mpu02_ldo3(19), + regulator_desc_s2mpu02_ldo4(20), + regulator_desc_s2mpu02_ldo5(21), + regulator_desc_s2mpu02_ldo5(22), + regulator_desc_s2mpu02_ldo5(23), + regulator_desc_s2mpu02_ldo4(24), + regulator_desc_s2mpu02_ldo5(25), + regulator_desc_s2mpu02_ldo4(26), + regulator_desc_s2mpu02_ldo5(27), + regulator_desc_s2mpu02_ldo5(28), + regulator_desc_s2mpu02_buck1234(1), + regulator_desc_s2mpu02_buck1234(2), + regulator_desc_s2mpu02_buck1234(3), + regulator_desc_s2mpu02_buck1234(4), + regulator_desc_s2mpu02_buck5(5), + regulator_desc_s2mpu02_buck6(6), + regulator_desc_s2mpu02_buck7(7), +}; + +static int s2mps11_pmic_probe(struct platform_device *pdev) +{ + struct sec_pmic_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct sec_platform_data *pdata = NULL; + struct of_regulator_match *rdata = NULL; + struct regulator_config config = { }; + struct s2mps11_info *s2mps11; + unsigned int rdev_num = 0; + int i, ret = 0; + const struct regulator_desc *regulators; + + s2mps11 = devm_kzalloc(&pdev->dev, sizeof(struct s2mps11_info), + GFP_KERNEL); + if (!s2mps11) + return -ENOMEM; + + s2mps11->dev_type = platform_get_device_id(pdev)->driver_data; + switch (s2mps11->dev_type) { + case S2MPS11X: + rdev_num = ARRAY_SIZE(s2mps11_regulators); + regulators = s2mps11_regulators; + BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mps11_regulators)); + break; + case S2MPS13X: + rdev_num = ARRAY_SIZE(s2mps13_regulators); + regulators = s2mps13_regulators; + BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mps13_regulators)); + break; + case S2MPS14X: + rdev_num = ARRAY_SIZE(s2mps14_regulators); + regulators = s2mps14_regulators; + BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mps14_regulators)); + break; + case S2MPS15X: + rdev_num = ARRAY_SIZE(s2mps15_regulators); + regulators = s2mps15_regulators; + BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mps15_regulators)); + break; + case S2MPU02: + rdev_num = ARRAY_SIZE(s2mpu02_regulators); + regulators = s2mpu02_regulators; + BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mpu02_regulators)); + break; + default: + dev_err(&pdev->dev, "Invalid device type: %u\n", + s2mps11->dev_type); + return -EINVAL; + } + + s2mps11->ext_control_gpio = devm_kmalloc_array(&pdev->dev, + rdev_num, sizeof(*s2mps11->ext_control_gpio), + GFP_KERNEL); + if (!s2mps11->ext_control_gpio) + return -ENOMEM; + /* + * 0 is a valid GPIO so initialize all GPIO-s to negative value + * to indicate that external control won't be used for this regulator. + */ + for (i = 0; i < rdev_num; i++) + s2mps11->ext_control_gpio[i] = -EINVAL; + + if (!iodev->dev->of_node) { + if (iodev->pdata) { + pdata = iodev->pdata; + goto common_reg; + } else { + dev_err(pdev->dev.parent, + "Platform data or DT node not supplied\n"); + return -ENODEV; + } + } + + rdata = kcalloc(rdev_num, sizeof(*rdata), GFP_KERNEL); + if (!rdata) + return -ENOMEM; + + for (i = 0; i < rdev_num; i++) + rdata[i].name = regulators[i].name; + + ret = s2mps11_pmic_dt_parse(pdev, rdata, s2mps11, rdev_num); + if (ret) + goto out; + +common_reg: + platform_set_drvdata(pdev, s2mps11); + + config.dev = &pdev->dev; + config.regmap = iodev->regmap_pmic; + config.driver_data = s2mps11; + config.ena_gpio_flags = GPIOF_OUT_INIT_HIGH; + config.ena_gpio_initialized = true; + for (i = 0; i < rdev_num; i++) { + struct regulator_dev *regulator; + + if (pdata) { + config.init_data = pdata->regulators[i].initdata; + config.of_node = pdata->regulators[i].reg_node; + } else { + config.init_data = rdata[i].init_data; + config.of_node = rdata[i].of_node; + } + config.ena_gpio = s2mps11->ext_control_gpio[i]; + + regulator = devm_regulator_register(&pdev->dev, + ®ulators[i], &config); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + dev_err(&pdev->dev, "regulator init failed for %d\n", + i); + goto out; + } + + if (gpio_is_valid(s2mps11->ext_control_gpio[i])) { + ret = s2mps14_pmic_enable_ext_control(s2mps11, + regulator); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to enable GPIO control over %s: %d\n", + regulator->desc->name, ret); + goto out; + } + } + } + +out: + kfree(rdata); + + return ret; +} + +static const struct platform_device_id s2mps11_pmic_id[] = { + { "s2mps11-regulator", S2MPS11X}, + { "s2mps13-regulator", S2MPS13X}, + { "s2mps14-regulator", S2MPS14X}, + { "s2mps15-regulator", S2MPS15X}, + { "s2mpu02-regulator", S2MPU02}, + { }, +}; +MODULE_DEVICE_TABLE(platform, s2mps11_pmic_id); + +static struct platform_driver s2mps11_pmic_driver = { + .driver = { + .name = "s2mps11-pmic", + }, + .probe = s2mps11_pmic_probe, + .id_table = s2mps11_pmic_id, +}; + +module_platform_driver(s2mps11_pmic_driver); + +/* Module information */ +MODULE_AUTHOR("Sangbeom Kim <sbkim73@samsung.com>"); +MODULE_DESCRIPTION("SAMSUNG S2MPS11/S2MPS14/S2MPS15/S2MPU02 Regulator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/s5m8767.c b/drivers/regulator/s5m8767.c new file mode 100644 index 000000000..4818df3f8 --- /dev/null +++ b/drivers/regulator/s5m8767.c @@ -0,0 +1,1015 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (c) 2011 Samsung Electronics Co., Ltd +// http://www.samsung.com + +#include <linux/err.h> +#include <linux/of_gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/samsung/core.h> +#include <linux/mfd/samsung/s5m8767.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regmap.h> + +#define S5M8767_OPMODE_NORMAL_MODE 0x1 + +struct s5m8767_info { + struct device *dev; + struct sec_pmic_dev *iodev; + int num_regulators; + struct sec_opmode_data *opmode; + + int ramp_delay; + bool buck2_ramp; + bool buck3_ramp; + bool buck4_ramp; + + bool buck2_gpiodvs; + bool buck3_gpiodvs; + bool buck4_gpiodvs; + u8 buck2_vol[8]; + u8 buck3_vol[8]; + u8 buck4_vol[8]; + int buck_gpios[3]; + int buck_ds[3]; + int buck_gpioindex; +}; + +struct sec_voltage_desc { + int max; + int min; + int step; +}; + +static const struct sec_voltage_desc buck_voltage_val1 = { + .max = 2225000, + .min = 650000, + .step = 6250, +}; + +static const struct sec_voltage_desc buck_voltage_val2 = { + .max = 1600000, + .min = 600000, + .step = 6250, +}; + +static const struct sec_voltage_desc buck_voltage_val3 = { + .max = 3000000, + .min = 750000, + .step = 12500, +}; + +static const struct sec_voltage_desc ldo_voltage_val1 = { + .max = 3950000, + .min = 800000, + .step = 50000, +}; + +static const struct sec_voltage_desc ldo_voltage_val2 = { + .max = 2375000, + .min = 800000, + .step = 25000, +}; + +static const struct sec_voltage_desc *reg_voltage_map[] = { + [S5M8767_LDO1] = &ldo_voltage_val2, + [S5M8767_LDO2] = &ldo_voltage_val2, + [S5M8767_LDO3] = &ldo_voltage_val1, + [S5M8767_LDO4] = &ldo_voltage_val1, + [S5M8767_LDO5] = &ldo_voltage_val1, + [S5M8767_LDO6] = &ldo_voltage_val2, + [S5M8767_LDO7] = &ldo_voltage_val2, + [S5M8767_LDO8] = &ldo_voltage_val2, + [S5M8767_LDO9] = &ldo_voltage_val1, + [S5M8767_LDO10] = &ldo_voltage_val1, + [S5M8767_LDO11] = &ldo_voltage_val1, + [S5M8767_LDO12] = &ldo_voltage_val1, + [S5M8767_LDO13] = &ldo_voltage_val1, + [S5M8767_LDO14] = &ldo_voltage_val1, + [S5M8767_LDO15] = &ldo_voltage_val2, + [S5M8767_LDO16] = &ldo_voltage_val1, + [S5M8767_LDO17] = &ldo_voltage_val1, + [S5M8767_LDO18] = &ldo_voltage_val1, + [S5M8767_LDO19] = &ldo_voltage_val1, + [S5M8767_LDO20] = &ldo_voltage_val1, + [S5M8767_LDO21] = &ldo_voltage_val1, + [S5M8767_LDO22] = &ldo_voltage_val1, + [S5M8767_LDO23] = &ldo_voltage_val1, + [S5M8767_LDO24] = &ldo_voltage_val1, + [S5M8767_LDO25] = &ldo_voltage_val1, + [S5M8767_LDO26] = &ldo_voltage_val1, + [S5M8767_LDO27] = &ldo_voltage_val1, + [S5M8767_LDO28] = &ldo_voltage_val1, + [S5M8767_BUCK1] = &buck_voltage_val1, + [S5M8767_BUCK2] = &buck_voltage_val2, + [S5M8767_BUCK3] = &buck_voltage_val2, + [S5M8767_BUCK4] = &buck_voltage_val2, + [S5M8767_BUCK5] = &buck_voltage_val1, + [S5M8767_BUCK6] = &buck_voltage_val1, + [S5M8767_BUCK7] = &buck_voltage_val3, + [S5M8767_BUCK8] = &buck_voltage_val3, + [S5M8767_BUCK9] = &buck_voltage_val3, +}; + +static unsigned int s5m8767_opmode_reg[][4] = { + /* {OFF, ON, LOWPOWER, SUSPEND} */ + /* LDO1 ... LDO28 */ + {0x0, 0x3, 0x2, 0x1}, /* LDO1 */ + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x0, 0x0, 0x0}, + {0x0, 0x3, 0x2, 0x1}, /* LDO5 */ + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, /* LDO10 */ + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, /* LDO15 */ + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x0, 0x0, 0x0}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, /* LDO20 */ + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x0, 0x0, 0x0}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, /* LDO25 */ + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, + {0x0, 0x3, 0x2, 0x1}, /* LDO28 */ + + /* BUCK1 ... BUCK9 */ + {0x0, 0x3, 0x1, 0x1}, /* BUCK1 */ + {0x0, 0x3, 0x1, 0x1}, + {0x0, 0x3, 0x1, 0x1}, + {0x0, 0x3, 0x1, 0x1}, + {0x0, 0x3, 0x2, 0x1}, /* BUCK5 */ + {0x0, 0x3, 0x1, 0x1}, + {0x0, 0x3, 0x1, 0x1}, + {0x0, 0x3, 0x1, 0x1}, + {0x0, 0x3, 0x1, 0x1}, /* BUCK9 */ +}; + +static int s5m8767_get_register(struct s5m8767_info *s5m8767, int reg_id, + int *reg, int *enable_ctrl) +{ + int i; + unsigned int mode; + + switch (reg_id) { + case S5M8767_LDO1 ... S5M8767_LDO2: + *reg = S5M8767_REG_LDO1CTRL + (reg_id - S5M8767_LDO1); + break; + case S5M8767_LDO3 ... S5M8767_LDO28: + *reg = S5M8767_REG_LDO3CTRL + (reg_id - S5M8767_LDO3); + break; + case S5M8767_BUCK1: + *reg = S5M8767_REG_BUCK1CTRL1; + break; + case S5M8767_BUCK2 ... S5M8767_BUCK4: + *reg = S5M8767_REG_BUCK2CTRL + (reg_id - S5M8767_BUCK2) * 9; + break; + case S5M8767_BUCK5: + *reg = S5M8767_REG_BUCK5CTRL1; + break; + case S5M8767_BUCK6 ... S5M8767_BUCK9: + *reg = S5M8767_REG_BUCK6CTRL1 + (reg_id - S5M8767_BUCK6) * 2; + break; + default: + return -EINVAL; + } + + for (i = 0; i < s5m8767->num_regulators; i++) { + if (s5m8767->opmode[i].id == reg_id) { + mode = s5m8767->opmode[i].mode; + break; + } + } + + if (i >= s5m8767->num_regulators) + return -EINVAL; + + *enable_ctrl = s5m8767_opmode_reg[reg_id][mode] << S5M8767_ENCTRL_SHIFT; + + return 0; +} + +static int s5m8767_get_vsel_reg(int reg_id, struct s5m8767_info *s5m8767) +{ + int reg; + + switch (reg_id) { + case S5M8767_LDO1 ... S5M8767_LDO2: + reg = S5M8767_REG_LDO1CTRL + (reg_id - S5M8767_LDO1); + break; + case S5M8767_LDO3 ... S5M8767_LDO28: + reg = S5M8767_REG_LDO3CTRL + (reg_id - S5M8767_LDO3); + break; + case S5M8767_BUCK1: + reg = S5M8767_REG_BUCK1CTRL2; + break; + case S5M8767_BUCK2: + reg = S5M8767_REG_BUCK2DVS1; + if (s5m8767->buck2_gpiodvs) + reg += s5m8767->buck_gpioindex; + break; + case S5M8767_BUCK3: + reg = S5M8767_REG_BUCK3DVS1; + if (s5m8767->buck3_gpiodvs) + reg += s5m8767->buck_gpioindex; + break; + case S5M8767_BUCK4: + reg = S5M8767_REG_BUCK4DVS1; + if (s5m8767->buck4_gpiodvs) + reg += s5m8767->buck_gpioindex; + break; + case S5M8767_BUCK5: + reg = S5M8767_REG_BUCK5CTRL2; + break; + case S5M8767_BUCK6 ... S5M8767_BUCK9: + reg = S5M8767_REG_BUCK6CTRL2 + (reg_id - S5M8767_BUCK6) * 2; + break; + default: + return -EINVAL; + } + + return reg; +} + +static int s5m8767_convert_voltage_to_sel(const struct sec_voltage_desc *desc, + int min_vol) +{ + int selector = 0; + + if (desc == NULL) + return -EINVAL; + + if (min_vol > desc->max) + return -EINVAL; + + if (min_vol < desc->min) + min_vol = desc->min; + + selector = DIV_ROUND_UP(min_vol - desc->min, desc->step); + + if (desc->min + desc->step * selector > desc->max) + return -EINVAL; + + return selector; +} + +static inline int s5m8767_set_high(struct s5m8767_info *s5m8767) +{ + int temp_index = s5m8767->buck_gpioindex; + + gpio_set_value(s5m8767->buck_gpios[0], (temp_index >> 2) & 0x1); + gpio_set_value(s5m8767->buck_gpios[1], (temp_index >> 1) & 0x1); + gpio_set_value(s5m8767->buck_gpios[2], temp_index & 0x1); + + return 0; +} + +static inline int s5m8767_set_low(struct s5m8767_info *s5m8767) +{ + int temp_index = s5m8767->buck_gpioindex; + + gpio_set_value(s5m8767->buck_gpios[2], temp_index & 0x1); + gpio_set_value(s5m8767->buck_gpios[1], (temp_index >> 1) & 0x1); + gpio_set_value(s5m8767->buck_gpios[0], (temp_index >> 2) & 0x1); + + return 0; +} + +static int s5m8767_set_voltage_sel(struct regulator_dev *rdev, + unsigned selector) +{ + struct s5m8767_info *s5m8767 = rdev_get_drvdata(rdev); + int reg_id = rdev_get_id(rdev); + int old_index, index = 0; + u8 *buck234_vol = NULL; + + switch (reg_id) { + case S5M8767_LDO1 ... S5M8767_LDO28: + break; + case S5M8767_BUCK1 ... S5M8767_BUCK6: + if (reg_id == S5M8767_BUCK2 && s5m8767->buck2_gpiodvs) + buck234_vol = &s5m8767->buck2_vol[0]; + else if (reg_id == S5M8767_BUCK3 && s5m8767->buck3_gpiodvs) + buck234_vol = &s5m8767->buck3_vol[0]; + else if (reg_id == S5M8767_BUCK4 && s5m8767->buck4_gpiodvs) + buck234_vol = &s5m8767->buck4_vol[0]; + break; + case S5M8767_BUCK7 ... S5M8767_BUCK8: + return -EINVAL; + case S5M8767_BUCK9: + break; + default: + return -EINVAL; + } + + /* buck234_vol != NULL means to control buck234 voltage via DVS GPIO */ + if (buck234_vol) { + while (*buck234_vol != selector) { + buck234_vol++; + index++; + } + old_index = s5m8767->buck_gpioindex; + s5m8767->buck_gpioindex = index; + + if (index > old_index) + return s5m8767_set_high(s5m8767); + else + return s5m8767_set_low(s5m8767); + } else { + return regulator_set_voltage_sel_regmap(rdev, selector); + } +} + +static int s5m8767_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_sel, + unsigned int new_sel) +{ + struct s5m8767_info *s5m8767 = rdev_get_drvdata(rdev); + const struct sec_voltage_desc *desc; + int reg_id = rdev_get_id(rdev); + + desc = reg_voltage_map[reg_id]; + + if ((old_sel < new_sel) && s5m8767->ramp_delay) + return DIV_ROUND_UP(desc->step * (new_sel - old_sel), + s5m8767->ramp_delay * 1000); + return 0; +} + +static const struct regulator_ops s5m8767_ops = { + .list_voltage = regulator_list_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = s5m8767_set_voltage_sel, + .set_voltage_time_sel = s5m8767_set_voltage_time_sel, +}; + +static const struct regulator_ops s5m8767_buck78_ops = { + .list_voltage = regulator_list_voltage_linear, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +#define s5m8767_regulator_desc(_name) { \ + .name = #_name, \ + .id = S5M8767_##_name, \ + .ops = &s5m8767_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ +} + +#define s5m8767_regulator_buck78_desc(_name) { \ + .name = #_name, \ + .id = S5M8767_##_name, \ + .ops = &s5m8767_buck78_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ +} + +static struct regulator_desc regulators[] = { + s5m8767_regulator_desc(LDO1), + s5m8767_regulator_desc(LDO2), + s5m8767_regulator_desc(LDO3), + s5m8767_regulator_desc(LDO4), + s5m8767_regulator_desc(LDO5), + s5m8767_regulator_desc(LDO6), + s5m8767_regulator_desc(LDO7), + s5m8767_regulator_desc(LDO8), + s5m8767_regulator_desc(LDO9), + s5m8767_regulator_desc(LDO10), + s5m8767_regulator_desc(LDO11), + s5m8767_regulator_desc(LDO12), + s5m8767_regulator_desc(LDO13), + s5m8767_regulator_desc(LDO14), + s5m8767_regulator_desc(LDO15), + s5m8767_regulator_desc(LDO16), + s5m8767_regulator_desc(LDO17), + s5m8767_regulator_desc(LDO18), + s5m8767_regulator_desc(LDO19), + s5m8767_regulator_desc(LDO20), + s5m8767_regulator_desc(LDO21), + s5m8767_regulator_desc(LDO22), + s5m8767_regulator_desc(LDO23), + s5m8767_regulator_desc(LDO24), + s5m8767_regulator_desc(LDO25), + s5m8767_regulator_desc(LDO26), + s5m8767_regulator_desc(LDO27), + s5m8767_regulator_desc(LDO28), + s5m8767_regulator_desc(BUCK1), + s5m8767_regulator_desc(BUCK2), + s5m8767_regulator_desc(BUCK3), + s5m8767_regulator_desc(BUCK4), + s5m8767_regulator_desc(BUCK5), + s5m8767_regulator_desc(BUCK6), + s5m8767_regulator_buck78_desc(BUCK7), + s5m8767_regulator_buck78_desc(BUCK8), + s5m8767_regulator_desc(BUCK9), +}; + +/* + * Enable GPIO control over BUCK9 in regulator_config for that regulator. + */ +static void s5m8767_regulator_config_ext_control(struct s5m8767_info *s5m8767, + struct sec_regulator_data *rdata, + struct regulator_config *config) +{ + int i, mode = 0; + + if (rdata->id != S5M8767_BUCK9) + return; + + /* Check if opmode for regulator matches S5M8767_ENCTRL_USE_GPIO */ + for (i = 0; i < s5m8767->num_regulators; i++) { + const struct sec_opmode_data *opmode = &s5m8767->opmode[i]; + if (opmode->id == rdata->id) { + mode = s5m8767_opmode_reg[rdata->id][opmode->mode]; + break; + } + } + if (mode != S5M8767_ENCTRL_USE_GPIO) { + dev_warn(s5m8767->dev, + "ext-control for %s: mismatched op_mode (%x), ignoring\n", + rdata->reg_node->name, mode); + return; + } + + if (!rdata->ext_control_gpiod) { + dev_warn(s5m8767->dev, + "ext-control for %s: GPIO not valid, ignoring\n", + rdata->reg_node->name); + return; + } + + config->ena_gpiod = rdata->ext_control_gpiod; +} + +/* + * Turn on GPIO control over BUCK9. + */ +static int s5m8767_enable_ext_control(struct s5m8767_info *s5m8767, + struct regulator_dev *rdev) +{ + int id = rdev_get_id(rdev); + int ret, reg, enable_ctrl; + + if (id != S5M8767_BUCK9) + return -EINVAL; + + ret = s5m8767_get_register(s5m8767, id, ®, &enable_ctrl); + if (ret) + return ret; + + return regmap_update_bits(s5m8767->iodev->regmap_pmic, + reg, S5M8767_ENCTRL_MASK, + S5M8767_ENCTRL_USE_GPIO << S5M8767_ENCTRL_SHIFT); +} + + +#ifdef CONFIG_OF +static int s5m8767_pmic_dt_parse_dvs_gpio(struct sec_pmic_dev *iodev, + struct sec_platform_data *pdata, + struct device_node *pmic_np) +{ + int i, gpio; + + for (i = 0; i < 3; i++) { + gpio = of_get_named_gpio(pmic_np, + "s5m8767,pmic-buck-dvs-gpios", i); + if (!gpio_is_valid(gpio)) { + dev_err(iodev->dev, "invalid gpio[%d]: %d\n", i, gpio); + return -EINVAL; + } + pdata->buck_gpios[i] = gpio; + } + return 0; +} + +static int s5m8767_pmic_dt_parse_ds_gpio(struct sec_pmic_dev *iodev, + struct sec_platform_data *pdata, + struct device_node *pmic_np) +{ + int i, gpio; + + for (i = 0; i < 3; i++) { + gpio = of_get_named_gpio(pmic_np, + "s5m8767,pmic-buck-ds-gpios", i); + if (!gpio_is_valid(gpio)) { + dev_err(iodev->dev, "invalid gpio[%d]: %d\n", i, gpio); + return -EINVAL; + } + pdata->buck_ds[i] = gpio; + } + return 0; +} + +static int s5m8767_pmic_dt_parse_pdata(struct platform_device *pdev, + struct sec_platform_data *pdata) +{ + struct sec_pmic_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct device_node *pmic_np, *regulators_np, *reg_np; + struct sec_regulator_data *rdata; + struct sec_opmode_data *rmode; + unsigned int i, dvs_voltage_nr = 8, ret; + + pmic_np = iodev->dev->of_node; + if (!pmic_np) { + dev_err(iodev->dev, "could not find pmic sub-node\n"); + return -ENODEV; + } + + regulators_np = of_get_child_by_name(pmic_np, "regulators"); + if (!regulators_np) { + dev_err(iodev->dev, "could not find regulators sub-node\n"); + return -EINVAL; + } + + /* count the number of regulators to be supported in pmic */ + pdata->num_regulators = of_get_child_count(regulators_np); + + rdata = devm_kcalloc(&pdev->dev, + pdata->num_regulators, sizeof(*rdata), + GFP_KERNEL); + if (!rdata) { + of_node_put(regulators_np); + return -ENOMEM; + } + + rmode = devm_kcalloc(&pdev->dev, + pdata->num_regulators, sizeof(*rmode), + GFP_KERNEL); + if (!rmode) { + of_node_put(regulators_np); + return -ENOMEM; + } + + pdata->regulators = rdata; + pdata->opmode = rmode; + for_each_child_of_node(regulators_np, reg_np) { + for (i = 0; i < ARRAY_SIZE(regulators); i++) + if (!of_node_cmp(reg_np->name, regulators[i].name)) + break; + + if (i == ARRAY_SIZE(regulators)) { + dev_warn(iodev->dev, + "don't know how to configure regulator %s\n", + reg_np->name); + continue; + } + + rdata->ext_control_gpiod = devm_gpiod_get_from_of_node(&pdev->dev, + reg_np, + "s5m8767,pmic-ext-control-gpios", + 0, + GPIOD_OUT_HIGH, + "s5m8767"); + if (IS_ERR(rdata->ext_control_gpiod)) + return PTR_ERR(rdata->ext_control_gpiod); + + rdata->id = i; + rdata->initdata = of_get_regulator_init_data( + &pdev->dev, reg_np, + ®ulators[i]); + rdata->reg_node = reg_np; + rdata++; + rmode->id = i; + if (of_property_read_u32(reg_np, "op_mode", + &rmode->mode)) { + dev_warn(iodev->dev, + "no op_mode property property at %pOF\n", + reg_np); + + rmode->mode = S5M8767_OPMODE_NORMAL_MODE; + } + rmode++; + } + + of_node_put(regulators_np); + + if (of_get_property(pmic_np, "s5m8767,pmic-buck2-uses-gpio-dvs", NULL)) { + pdata->buck2_gpiodvs = true; + + if (of_property_read_u32_array(pmic_np, + "s5m8767,pmic-buck2-dvs-voltage", + pdata->buck2_voltage, dvs_voltage_nr)) { + dev_err(iodev->dev, "buck2 voltages not specified\n"); + return -EINVAL; + } + } + + if (of_get_property(pmic_np, "s5m8767,pmic-buck3-uses-gpio-dvs", NULL)) { + pdata->buck3_gpiodvs = true; + + if (of_property_read_u32_array(pmic_np, + "s5m8767,pmic-buck3-dvs-voltage", + pdata->buck3_voltage, dvs_voltage_nr)) { + dev_err(iodev->dev, "buck3 voltages not specified\n"); + return -EINVAL; + } + } + + if (of_get_property(pmic_np, "s5m8767,pmic-buck4-uses-gpio-dvs", NULL)) { + pdata->buck4_gpiodvs = true; + + if (of_property_read_u32_array(pmic_np, + "s5m8767,pmic-buck4-dvs-voltage", + pdata->buck4_voltage, dvs_voltage_nr)) { + dev_err(iodev->dev, "buck4 voltages not specified\n"); + return -EINVAL; + } + } + + if (pdata->buck2_gpiodvs || pdata->buck3_gpiodvs || + pdata->buck4_gpiodvs) { + ret = s5m8767_pmic_dt_parse_dvs_gpio(iodev, pdata, pmic_np); + if (ret) + return -EINVAL; + + if (of_property_read_u32(pmic_np, + "s5m8767,pmic-buck-default-dvs-idx", + &pdata->buck_default_idx)) { + pdata->buck_default_idx = 0; + } else { + if (pdata->buck_default_idx >= 8) { + pdata->buck_default_idx = 0; + dev_info(iodev->dev, + "invalid value for default dvs index, use 0\n"); + } + } + } + + ret = s5m8767_pmic_dt_parse_ds_gpio(iodev, pdata, pmic_np); + if (ret) + return -EINVAL; + + if (of_get_property(pmic_np, "s5m8767,pmic-buck2-ramp-enable", NULL)) + pdata->buck2_ramp_enable = true; + + if (of_get_property(pmic_np, "s5m8767,pmic-buck3-ramp-enable", NULL)) + pdata->buck3_ramp_enable = true; + + if (of_get_property(pmic_np, "s5m8767,pmic-buck4-ramp-enable", NULL)) + pdata->buck4_ramp_enable = true; + + if (pdata->buck2_ramp_enable || pdata->buck3_ramp_enable + || pdata->buck4_ramp_enable) { + if (of_property_read_u32(pmic_np, "s5m8767,pmic-buck-ramp-delay", + &pdata->buck_ramp_delay)) + pdata->buck_ramp_delay = 0; + } + + return 0; +} +#else +static int s5m8767_pmic_dt_parse_pdata(struct platform_device *pdev, + struct sec_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_OF */ + +static int s5m8767_pmic_probe(struct platform_device *pdev) +{ + struct sec_pmic_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct sec_platform_data *pdata = iodev->pdata; + struct regulator_config config = { }; + struct s5m8767_info *s5m8767; + int i, ret, buck_init; + + if (!pdata) { + dev_err(pdev->dev.parent, "Platform data not supplied\n"); + return -ENODEV; + } + + if (iodev->dev->of_node) { + ret = s5m8767_pmic_dt_parse_pdata(pdev, pdata); + if (ret) + return ret; + } + + if (pdata->buck2_gpiodvs) { + if (pdata->buck3_gpiodvs || pdata->buck4_gpiodvs) { + dev_err(&pdev->dev, "S5M8767 GPIO DVS NOT VALID\n"); + return -EINVAL; + } + } + + if (pdata->buck3_gpiodvs) { + if (pdata->buck2_gpiodvs || pdata->buck4_gpiodvs) { + dev_err(&pdev->dev, "S5M8767 GPIO DVS NOT VALID\n"); + return -EINVAL; + } + } + + if (pdata->buck4_gpiodvs) { + if (pdata->buck2_gpiodvs || pdata->buck3_gpiodvs) { + dev_err(&pdev->dev, "S5M8767 GPIO DVS NOT VALID\n"); + return -EINVAL; + } + } + + s5m8767 = devm_kzalloc(&pdev->dev, sizeof(struct s5m8767_info), + GFP_KERNEL); + if (!s5m8767) + return -ENOMEM; + + s5m8767->dev = &pdev->dev; + s5m8767->iodev = iodev; + s5m8767->num_regulators = pdata->num_regulators; + platform_set_drvdata(pdev, s5m8767); + + s5m8767->buck_gpioindex = pdata->buck_default_idx; + s5m8767->buck2_gpiodvs = pdata->buck2_gpiodvs; + s5m8767->buck3_gpiodvs = pdata->buck3_gpiodvs; + s5m8767->buck4_gpiodvs = pdata->buck4_gpiodvs; + s5m8767->buck_gpios[0] = pdata->buck_gpios[0]; + s5m8767->buck_gpios[1] = pdata->buck_gpios[1]; + s5m8767->buck_gpios[2] = pdata->buck_gpios[2]; + 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->ramp_delay = pdata->buck_ramp_delay; + s5m8767->buck2_ramp = pdata->buck2_ramp_enable; + s5m8767->buck3_ramp = pdata->buck3_ramp_enable; + s5m8767->buck4_ramp = pdata->buck4_ramp_enable; + s5m8767->opmode = pdata->opmode; + + buck_init = s5m8767_convert_voltage_to_sel(&buck_voltage_val2, + pdata->buck2_init); + + regmap_write(s5m8767->iodev->regmap_pmic, S5M8767_REG_BUCK2DVS2, + buck_init); + + buck_init = s5m8767_convert_voltage_to_sel(&buck_voltage_val2, + pdata->buck3_init); + + regmap_write(s5m8767->iodev->regmap_pmic, S5M8767_REG_BUCK3DVS2, + buck_init); + + buck_init = s5m8767_convert_voltage_to_sel(&buck_voltage_val2, + pdata->buck4_init); + + regmap_write(s5m8767->iodev->regmap_pmic, S5M8767_REG_BUCK4DVS2, + buck_init); + + for (i = 0; i < 8; i++) { + if (s5m8767->buck2_gpiodvs) { + s5m8767->buck2_vol[i] = + s5m8767_convert_voltage_to_sel( + &buck_voltage_val2, + pdata->buck2_voltage[i]); + } + + if (s5m8767->buck3_gpiodvs) { + s5m8767->buck3_vol[i] = + s5m8767_convert_voltage_to_sel( + &buck_voltage_val2, + pdata->buck3_voltage[i]); + } + + if (s5m8767->buck4_gpiodvs) { + s5m8767->buck4_vol[i] = + s5m8767_convert_voltage_to_sel( + &buck_voltage_val2, + pdata->buck4_voltage[i]); + } + } + + if (pdata->buck2_gpiodvs || pdata->buck3_gpiodvs || + pdata->buck4_gpiodvs) { + + if (!gpio_is_valid(pdata->buck_gpios[0]) || + !gpio_is_valid(pdata->buck_gpios[1]) || + !gpio_is_valid(pdata->buck_gpios[2])) { + dev_err(&pdev->dev, "GPIO NOT VALID\n"); + return -EINVAL; + } + + ret = devm_gpio_request(&pdev->dev, pdata->buck_gpios[0], + "S5M8767 SET1"); + if (ret) + return ret; + + ret = devm_gpio_request(&pdev->dev, pdata->buck_gpios[1], + "S5M8767 SET2"); + if (ret) + return ret; + + ret = devm_gpio_request(&pdev->dev, pdata->buck_gpios[2], + "S5M8767 SET3"); + if (ret) + return ret; + + /* SET1 GPIO */ + gpio_direction_output(pdata->buck_gpios[0], + (s5m8767->buck_gpioindex >> 2) & 0x1); + /* SET2 GPIO */ + gpio_direction_output(pdata->buck_gpios[1], + (s5m8767->buck_gpioindex >> 1) & 0x1); + /* SET3 GPIO */ + gpio_direction_output(pdata->buck_gpios[2], + (s5m8767->buck_gpioindex >> 0) & 0x1); + } + + ret = devm_gpio_request(&pdev->dev, pdata->buck_ds[0], "S5M8767 DS2"); + if (ret) + return ret; + + ret = devm_gpio_request(&pdev->dev, pdata->buck_ds[1], "S5M8767 DS3"); + if (ret) + return ret; + + ret = devm_gpio_request(&pdev->dev, pdata->buck_ds[2], "S5M8767 DS4"); + if (ret) + return ret; + + /* DS2 GPIO */ + gpio_direction_output(pdata->buck_ds[0], 0x0); + /* DS3 GPIO */ + gpio_direction_output(pdata->buck_ds[1], 0x0); + /* DS4 GPIO */ + gpio_direction_output(pdata->buck_ds[2], 0x0); + + regmap_update_bits(s5m8767->iodev->regmap_pmic, + S5M8767_REG_BUCK2CTRL, 1 << 1, + (pdata->buck2_gpiodvs) ? (1 << 1) : (0 << 1)); + regmap_update_bits(s5m8767->iodev->regmap_pmic, + S5M8767_REG_BUCK3CTRL, 1 << 1, + (pdata->buck3_gpiodvs) ? (1 << 1) : (0 << 1)); + regmap_update_bits(s5m8767->iodev->regmap_pmic, + S5M8767_REG_BUCK4CTRL, 1 << 1, + (pdata->buck4_gpiodvs) ? (1 << 1) : (0 << 1)); + + /* Initialize GPIO DVS registers */ + for (i = 0; i < 8; i++) { + if (s5m8767->buck2_gpiodvs) { + regmap_write(s5m8767->iodev->regmap_pmic, + S5M8767_REG_BUCK2DVS1 + i, + s5m8767->buck2_vol[i]); + } + + if (s5m8767->buck3_gpiodvs) { + regmap_write(s5m8767->iodev->regmap_pmic, + S5M8767_REG_BUCK3DVS1 + i, + s5m8767->buck3_vol[i]); + } + + if (s5m8767->buck4_gpiodvs) { + regmap_write(s5m8767->iodev->regmap_pmic, + S5M8767_REG_BUCK4DVS1 + i, + s5m8767->buck4_vol[i]); + } + } + + if (s5m8767->buck2_ramp) + regmap_update_bits(s5m8767->iodev->regmap_pmic, + S5M8767_REG_DVSRAMP, 0x08, 0x08); + + if (s5m8767->buck3_ramp) + regmap_update_bits(s5m8767->iodev->regmap_pmic, + S5M8767_REG_DVSRAMP, 0x04, 0x04); + + if (s5m8767->buck4_ramp) + regmap_update_bits(s5m8767->iodev->regmap_pmic, + S5M8767_REG_DVSRAMP, 0x02, 0x02); + + if (s5m8767->buck2_ramp || s5m8767->buck3_ramp + || s5m8767->buck4_ramp) { + unsigned int val; + switch (s5m8767->ramp_delay) { + case 5: + val = S5M8767_DVS_BUCK_RAMP_5; + break; + case 10: + val = S5M8767_DVS_BUCK_RAMP_10; + break; + case 25: + val = S5M8767_DVS_BUCK_RAMP_25; + break; + case 50: + val = S5M8767_DVS_BUCK_RAMP_50; + break; + case 100: + val = S5M8767_DVS_BUCK_RAMP_100; + break; + default: + val = S5M8767_DVS_BUCK_RAMP_10; + } + regmap_update_bits(s5m8767->iodev->regmap_pmic, + S5M8767_REG_DVSRAMP, + S5M8767_DVS_BUCK_RAMP_MASK, + val << S5M8767_DVS_BUCK_RAMP_SHIFT); + } + + for (i = 0; i < pdata->num_regulators; i++) { + const struct sec_voltage_desc *desc; + int id = pdata->regulators[i].id; + int enable_reg, enable_val; + struct regulator_dev *rdev; + + desc = reg_voltage_map[id]; + if (desc) { + regulators[id].n_voltages = + (desc->max - desc->min) / desc->step + 1; + regulators[id].min_uV = desc->min; + regulators[id].uV_step = desc->step; + regulators[id].vsel_reg = + s5m8767_get_vsel_reg(id, s5m8767); + if (id < S5M8767_BUCK1) + regulators[id].vsel_mask = 0x3f; + else + regulators[id].vsel_mask = 0xff; + + ret = s5m8767_get_register(s5m8767, id, &enable_reg, + &enable_val); + if (ret) { + dev_err(s5m8767->dev, "error reading registers\n"); + return ret; + } + regulators[id].enable_reg = enable_reg; + regulators[id].enable_mask = S5M8767_ENCTRL_MASK; + regulators[id].enable_val = enable_val; + } + + config.dev = s5m8767->dev; + config.init_data = pdata->regulators[i].initdata; + config.driver_data = s5m8767; + config.regmap = iodev->regmap_pmic; + config.of_node = pdata->regulators[i].reg_node; + config.ena_gpiod = NULL; + if (pdata->regulators[i].ext_control_gpiod) + s5m8767_regulator_config_ext_control(s5m8767, + &pdata->regulators[i], &config); + + rdev = devm_regulator_register(&pdev->dev, ®ulators[id], + &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(s5m8767->dev, "regulator init failed for %d\n", + id); + return ret; + } + + if (pdata->regulators[i].ext_control_gpiod) { + ret = s5m8767_enable_ext_control(s5m8767, rdev); + if (ret < 0) { + dev_err(s5m8767->dev, + "failed to enable gpio control over %s: %d\n", + rdev->desc->name, ret); + return ret; + } + } + } + + return 0; +} + +static const struct platform_device_id s5m8767_pmic_id[] = { + { "s5m8767-pmic", 0}, + { }, +}; +MODULE_DEVICE_TABLE(platform, s5m8767_pmic_id); + +static struct platform_driver s5m8767_pmic_driver = { + .driver = { + .name = "s5m8767-pmic", + }, + .probe = s5m8767_pmic_probe, + .id_table = s5m8767_pmic_id, +}; + +static int __init s5m8767_pmic_init(void) +{ + return platform_driver_register(&s5m8767_pmic_driver); +} +subsys_initcall(s5m8767_pmic_init); + +static void __exit s5m8767_pmic_exit(void) +{ + platform_driver_unregister(&s5m8767_pmic_driver); +} +module_exit(s5m8767_pmic_exit); + +/* Module information */ +MODULE_AUTHOR("Sangbeom Kim <sbkim73@samsung.com>"); +MODULE_DESCRIPTION("SAMSUNG S5M8767 Regulator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/sc2731-regulator.c b/drivers/regulator/sc2731-regulator.c new file mode 100644 index 000000000..eb2bdf060 --- /dev/null +++ b/drivers/regulator/sc2731-regulator.c @@ -0,0 +1,256 @@ + //SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017 Spreadtrum Communications Inc. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +/* + * SC2731 regulator lock register + */ +#define SC2731_PWR_WR_PROT 0xf0c +#define SC2731_WR_UNLOCK_VALUE 0x6e7f + +/* + * SC2731 enable register + */ +#define SC2731_POWER_PD_SW 0xc28 +#define SC2731_LDO_CAMA0_PD 0xcfc +#define SC2731_LDO_CAMA1_PD 0xd04 +#define SC2731_LDO_CAMMOT_PD 0xd0c +#define SC2731_LDO_VLDO_PD 0xd6c +#define SC2731_LDO_EMMCCORE_PD 0xd2c +#define SC2731_LDO_SDCORE_PD 0xd74 +#define SC2731_LDO_SDIO_PD 0xd70 +#define SC2731_LDO_WIFIPA_PD 0xd4c +#define SC2731_LDO_USB33_PD 0xd5c +#define SC2731_LDO_CAMD0_PD 0xd7c +#define SC2731_LDO_CAMD1_PD 0xd84 +#define SC2731_LDO_CON_PD 0xd8c +#define SC2731_LDO_CAMIO_PD 0xd94 +#define SC2731_LDO_SRAM_PD 0xd78 + +/* + * SC2731 enable mask + */ +#define SC2731_DCDC_CPU0_PD_MASK BIT(4) +#define SC2731_DCDC_CPU1_PD_MASK BIT(3) +#define SC2731_DCDC_RF_PD_MASK BIT(11) +#define SC2731_LDO_CAMA0_PD_MASK BIT(0) +#define SC2731_LDO_CAMA1_PD_MASK BIT(0) +#define SC2731_LDO_CAMMOT_PD_MASK BIT(0) +#define SC2731_LDO_VLDO_PD_MASK BIT(0) +#define SC2731_LDO_EMMCCORE_PD_MASK BIT(0) +#define SC2731_LDO_SDCORE_PD_MASK BIT(0) +#define SC2731_LDO_SDIO_PD_MASK BIT(0) +#define SC2731_LDO_WIFIPA_PD_MASK BIT(0) +#define SC2731_LDO_USB33_PD_MASK BIT(0) +#define SC2731_LDO_CAMD0_PD_MASK BIT(0) +#define SC2731_LDO_CAMD1_PD_MASK BIT(0) +#define SC2731_LDO_CON_PD_MASK BIT(0) +#define SC2731_LDO_CAMIO_PD_MASK BIT(0) +#define SC2731_LDO_SRAM_PD_MASK BIT(0) + +/* + * SC2731 vsel register + */ +#define SC2731_DCDC_CPU0_VOL 0xc54 +#define SC2731_DCDC_CPU1_VOL 0xc64 +#define SC2731_DCDC_RF_VOL 0xcb8 +#define SC2731_LDO_CAMA0_VOL 0xd00 +#define SC2731_LDO_CAMA1_VOL 0xd08 +#define SC2731_LDO_CAMMOT_VOL 0xd10 +#define SC2731_LDO_VLDO_VOL 0xd28 +#define SC2731_LDO_EMMCCORE_VOL 0xd30 +#define SC2731_LDO_SDCORE_VOL 0xd38 +#define SC2731_LDO_SDIO_VOL 0xd40 +#define SC2731_LDO_WIFIPA_VOL 0xd50 +#define SC2731_LDO_USB33_VOL 0xd60 +#define SC2731_LDO_CAMD0_VOL 0xd80 +#define SC2731_LDO_CAMD1_VOL 0xd88 +#define SC2731_LDO_CON_VOL 0xd90 +#define SC2731_LDO_CAMIO_VOL 0xd98 +#define SC2731_LDO_SRAM_VOL 0xdB0 + +/* + * SC2731 vsel register mask + */ +#define SC2731_DCDC_CPU0_VOL_MASK GENMASK(8, 0) +#define SC2731_DCDC_CPU1_VOL_MASK GENMASK(8, 0) +#define SC2731_DCDC_RF_VOL_MASK GENMASK(8, 0) +#define SC2731_LDO_CAMA0_VOL_MASK GENMASK(7, 0) +#define SC2731_LDO_CAMA1_VOL_MASK GENMASK(7, 0) +#define SC2731_LDO_CAMMOT_VOL_MASK GENMASK(7, 0) +#define SC2731_LDO_VLDO_VOL_MASK GENMASK(7, 0) +#define SC2731_LDO_EMMCCORE_VOL_MASK GENMASK(7, 0) +#define SC2731_LDO_SDCORE_VOL_MASK GENMASK(7, 0) +#define SC2731_LDO_SDIO_VOL_MASK GENMASK(7, 0) +#define SC2731_LDO_WIFIPA_VOL_MASK GENMASK(7, 0) +#define SC2731_LDO_USB33_VOL_MASK GENMASK(7, 0) +#define SC2731_LDO_CAMD0_VOL_MASK GENMASK(6, 0) +#define SC2731_LDO_CAMD1_VOL_MASK GENMASK(6, 0) +#define SC2731_LDO_CON_VOL_MASK GENMASK(6, 0) +#define SC2731_LDO_CAMIO_VOL_MASK GENMASK(6, 0) +#define SC2731_LDO_SRAM_VOL_MASK GENMASK(6, 0) + +enum sc2731_regulator_id { + SC2731_BUCK_CPU0, + SC2731_BUCK_CPU1, + SC2731_BUCK_RF, + SC2731_LDO_CAMA0, + SC2731_LDO_CAMA1, + SC2731_LDO_CAMMOT, + SC2731_LDO_VLDO, + SC2731_LDO_EMMCCORE, + SC2731_LDO_SDCORE, + SC2731_LDO_SDIO, + SC2731_LDO_WIFIPA, + SC2731_LDO_USB33, + SC2731_LDO_CAMD0, + SC2731_LDO_CAMD1, + SC2731_LDO_CON, + SC2731_LDO_CAMIO, + SC2731_LDO_SRAM, +}; + +static const struct regulator_ops sc2731_regu_linear_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +#define SC2731_REGU_LINEAR(_id, en_reg, en_mask, vreg, vmask, \ + vstep, vmin, vmax) { \ + .name = #_id, \ + .of_match = of_match_ptr(#_id), \ + .ops = &sc2731_regu_linear_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = SC2731_##_id, \ + .owner = THIS_MODULE, \ + .min_uV = vmin, \ + .n_voltages = ((vmax) - (vmin)) / (vstep) + 1, \ + .uV_step = vstep, \ + .enable_is_inverted = true, \ + .enable_val = 0, \ + .enable_reg = en_reg, \ + .enable_mask = en_mask, \ + .vsel_reg = vreg, \ + .vsel_mask = vmask, \ +} + +static struct regulator_desc regulators[] = { + SC2731_REGU_LINEAR(BUCK_CPU0, SC2731_POWER_PD_SW, + SC2731_DCDC_CPU0_PD_MASK, SC2731_DCDC_CPU0_VOL, + SC2731_DCDC_CPU0_VOL_MASK, 3125, 400000, 1996875), + SC2731_REGU_LINEAR(BUCK_CPU1, SC2731_POWER_PD_SW, + SC2731_DCDC_CPU1_PD_MASK, SC2731_DCDC_CPU1_VOL, + SC2731_DCDC_CPU1_VOL_MASK, 3125, 400000, 1996875), + SC2731_REGU_LINEAR(BUCK_RF, SC2731_POWER_PD_SW, SC2731_DCDC_RF_PD_MASK, + SC2731_DCDC_RF_VOL, SC2731_DCDC_RF_VOL_MASK, + 3125, 600000, 2196875), + SC2731_REGU_LINEAR(LDO_CAMA0, SC2731_LDO_CAMA0_PD, + SC2731_LDO_CAMA0_PD_MASK, SC2731_LDO_CAMA0_VOL, + SC2731_LDO_CAMA0_VOL_MASK, 10000, 1200000, 3750000), + SC2731_REGU_LINEAR(LDO_CAMA1, SC2731_LDO_CAMA1_PD, + SC2731_LDO_CAMA1_PD_MASK, SC2731_LDO_CAMA1_VOL, + SC2731_LDO_CAMA1_VOL_MASK, 10000, 1200000, 3750000), + SC2731_REGU_LINEAR(LDO_CAMMOT, SC2731_LDO_CAMMOT_PD, + SC2731_LDO_CAMMOT_PD_MASK, SC2731_LDO_CAMMOT_VOL, + SC2731_LDO_CAMMOT_VOL_MASK, 10000, 1200000, 3750000), + SC2731_REGU_LINEAR(LDO_VLDO, SC2731_LDO_VLDO_PD, + SC2731_LDO_VLDO_PD_MASK, SC2731_LDO_VLDO_VOL, + SC2731_LDO_VLDO_VOL_MASK, 10000, 1200000, 3750000), + SC2731_REGU_LINEAR(LDO_EMMCCORE, SC2731_LDO_EMMCCORE_PD, + SC2731_LDO_EMMCCORE_PD_MASK, SC2731_LDO_EMMCCORE_VOL, + SC2731_LDO_EMMCCORE_VOL_MASK, 10000, 1200000, + 3750000), + SC2731_REGU_LINEAR(LDO_SDCORE, SC2731_LDO_SDCORE_PD, + SC2731_LDO_SDCORE_PD_MASK, SC2731_LDO_SDCORE_VOL, + SC2731_LDO_SDCORE_VOL_MASK, 10000, 1200000, 3750000), + SC2731_REGU_LINEAR(LDO_SDIO, SC2731_LDO_SDIO_PD, + SC2731_LDO_SDIO_PD_MASK, SC2731_LDO_SDIO_VOL, + SC2731_LDO_SDIO_VOL_MASK, 10000, 1200000, 3750000), + SC2731_REGU_LINEAR(LDO_WIFIPA, SC2731_LDO_WIFIPA_PD, + SC2731_LDO_WIFIPA_PD_MASK, SC2731_LDO_WIFIPA_VOL, + SC2731_LDO_WIFIPA_VOL_MASK, 10000, 1200000, 3750000), + SC2731_REGU_LINEAR(LDO_USB33, SC2731_LDO_USB33_PD, + SC2731_LDO_USB33_PD_MASK, SC2731_LDO_USB33_VOL, + SC2731_LDO_USB33_VOL_MASK, 10000, 1200000, 3750000), + SC2731_REGU_LINEAR(LDO_CAMD0, SC2731_LDO_CAMD0_PD, + SC2731_LDO_CAMD0_PD_MASK, SC2731_LDO_CAMD0_VOL, + SC2731_LDO_CAMD0_VOL_MASK, 6250, 1000000, 1793750), + SC2731_REGU_LINEAR(LDO_CAMD1, SC2731_LDO_CAMD1_PD, + SC2731_LDO_CAMD1_PD_MASK, SC2731_LDO_CAMD1_VOL, + SC2731_LDO_CAMD1_VOL_MASK, 6250, 1000000, 1793750), + SC2731_REGU_LINEAR(LDO_CON, SC2731_LDO_CON_PD, + SC2731_LDO_CON_PD_MASK, SC2731_LDO_CON_VOL, + SC2731_LDO_CON_VOL_MASK, 6250, 1000000, 1793750), + SC2731_REGU_LINEAR(LDO_CAMIO, SC2731_LDO_CAMIO_PD, + SC2731_LDO_CAMIO_PD_MASK, SC2731_LDO_CAMIO_VOL, + SC2731_LDO_CAMIO_VOL_MASK, 6250, 1000000, 1793750), + SC2731_REGU_LINEAR(LDO_SRAM, SC2731_LDO_SRAM_PD, + SC2731_LDO_SRAM_PD_MASK, SC2731_LDO_SRAM_VOL, + SC2731_LDO_SRAM_VOL_MASK, 6250, 1000000, 1793750), +}; + +static int sc2731_regulator_unlock(struct regmap *regmap) +{ + return regmap_write(regmap, SC2731_PWR_WR_PROT, + SC2731_WR_UNLOCK_VALUE); +} + +static int sc2731_regulator_probe(struct platform_device *pdev) +{ + int i, ret; + struct regmap *regmap; + struct regulator_config config = { }; + struct regulator_dev *rdev; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) { + dev_err(&pdev->dev, "failed to get regmap.\n"); + return -ENODEV; + } + + ret = sc2731_regulator_unlock(regmap); + if (ret) { + dev_err(&pdev->dev, "failed to release regulator lock\n"); + return ret; + } + + config.dev = &pdev->dev; + config.regmap = regmap; + + for (i = 0; i < ARRAY_SIZE(regulators); i++) { + rdev = devm_regulator_register(&pdev->dev, ®ulators[i], + &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + regulators[i].name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static struct platform_driver sc2731_regulator_driver = { + .driver = { + .name = "sc27xx-regulator", + }, + .probe = sc2731_regulator_probe, +}; + +module_platform_driver(sc2731_regulator_driver); + +MODULE_AUTHOR("Chen Junhui <erick.chen@spreadtrum.com>"); +MODULE_DESCRIPTION("Spreadtrum SC2731 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/sky81452-regulator.c b/drivers/regulator/sky81452-regulator.c new file mode 100644 index 000000000..647860611 --- /dev/null +++ b/drivers/regulator/sky81452-regulator.c @@ -0,0 +1,102 @@ +/* + * sky81452-regulator.c SKY81452 regulator driver + * + * Copyright 2014 Skyworks Solutions Inc. + * Author : Gyungoh Yoo <jack.yoo@skyworksinc.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +/* registers */ +#define SKY81452_REG1 0x01 +#define SKY81452_REG3 0x03 + +/* bit mask */ +#define SKY81452_LEN 0x40 +#define SKY81452_LOUT 0x1F + +static struct regulator_ops sky81452_reg_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct regulator_linear_range sky81452_reg_ranges[] = { + REGULATOR_LINEAR_RANGE(4500000, 0, 14, 250000), + REGULATOR_LINEAR_RANGE(9000000, 15, 31, 1000000), +}; + +static const struct regulator_desc sky81452_reg = { + .name = "LOUT", + .of_match = of_match_ptr("lout"), + .regulators_node = of_match_ptr("regulator"), + .ops = &sky81452_reg_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = SKY81452_LOUT + 1, + .linear_ranges = sky81452_reg_ranges, + .n_linear_ranges = ARRAY_SIZE(sky81452_reg_ranges), + .vsel_reg = SKY81452_REG3, + .vsel_mask = SKY81452_LOUT, + .enable_reg = SKY81452_REG1, + .enable_mask = SKY81452_LEN, +}; + +static int sky81452_reg_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct regulator_init_data *init_data = dev_get_platdata(dev); + struct regulator_config config = { }; + struct regulator_dev *rdev; + + config.dev = dev->parent; + config.init_data = init_data; + config.of_node = dev->of_node; + config.regmap = dev_get_drvdata(dev->parent); + + rdev = devm_regulator_register(dev, &sky81452_reg, &config); + if (IS_ERR(rdev)) { + dev_err(dev, "failed to register. err=%ld\n", PTR_ERR(rdev)); + return PTR_ERR(rdev); + } + + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static struct platform_driver sky81452_reg_driver = { + .driver = { + .name = "sky81452-regulator", + }, + .probe = sky81452_reg_probe, +}; + +module_platform_driver(sky81452_reg_driver); + +MODULE_DESCRIPTION("Skyworks SKY81452 Regulator driver"); +MODULE_AUTHOR("Gyungoh Yoo <jack.yoo@skyworksinc.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/stm32-vrefbuf.c b/drivers/regulator/stm32-vrefbuf.c new file mode 100644 index 000000000..e0a9c445e --- /dev/null +++ b/drivers/regulator/stm32-vrefbuf.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) STMicroelectronics 2017 + * + * Author: Fabrice Gasnier <fabrice.gasnier@st.com> + * + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +/* STM32 VREFBUF registers */ +#define STM32_VREFBUF_CSR 0x00 + +/* STM32 VREFBUF CSR bitfields */ +#define STM32_VRS GENMASK(6, 4) +#define STM32_VRR BIT(3) +#define STM32_HIZ BIT(1) +#define STM32_ENVR BIT(0) + +struct stm32_vrefbuf { + void __iomem *base; + struct clk *clk; +}; + +static const unsigned int stm32_vrefbuf_voltages[] = { + /* Matches resp. VRS = 000b, 001b, 010b, 011b */ + 2500000, 2048000, 1800000, 1500000, +}; + +static int stm32_vrefbuf_enable(struct regulator_dev *rdev) +{ + struct stm32_vrefbuf *priv = rdev_get_drvdata(rdev); + u32 val = readl_relaxed(priv->base + STM32_VREFBUF_CSR); + int ret; + + val = (val & ~STM32_HIZ) | STM32_ENVR; + writel_relaxed(val, priv->base + STM32_VREFBUF_CSR); + + /* + * Vrefbuf startup time depends on external capacitor: wait here for + * VRR to be set. That means output has reached expected value. + * ~650us sleep should be enough for caps up to 1.5uF. Use 10ms as + * arbitrary timeout. + */ + ret = readl_poll_timeout(priv->base + STM32_VREFBUF_CSR, val, + val & STM32_VRR, 650, 10000); + if (ret) { + dev_err(&rdev->dev, "stm32 vrefbuf timed out!\n"); + val = readl_relaxed(priv->base + STM32_VREFBUF_CSR); + val = (val & ~STM32_ENVR) | STM32_HIZ; + writel_relaxed(val, priv->base + STM32_VREFBUF_CSR); + } + + return ret; +} + +static int stm32_vrefbuf_disable(struct regulator_dev *rdev) +{ + struct stm32_vrefbuf *priv = rdev_get_drvdata(rdev); + u32 val = readl_relaxed(priv->base + STM32_VREFBUF_CSR); + + val = (val & ~STM32_ENVR) | STM32_HIZ; + writel_relaxed(val, priv->base + STM32_VREFBUF_CSR); + + return 0; +} + +static int stm32_vrefbuf_is_enabled(struct regulator_dev *rdev) +{ + struct stm32_vrefbuf *priv = rdev_get_drvdata(rdev); + + return readl_relaxed(priv->base + STM32_VREFBUF_CSR) & STM32_ENVR; +} + +static int stm32_vrefbuf_set_voltage_sel(struct regulator_dev *rdev, + unsigned sel) +{ + struct stm32_vrefbuf *priv = rdev_get_drvdata(rdev); + u32 val = readl_relaxed(priv->base + STM32_VREFBUF_CSR); + + val = (val & ~STM32_VRS) | FIELD_PREP(STM32_VRS, sel); + writel_relaxed(val, priv->base + STM32_VREFBUF_CSR); + + return 0; +} + +static int stm32_vrefbuf_get_voltage_sel(struct regulator_dev *rdev) +{ + struct stm32_vrefbuf *priv = rdev_get_drvdata(rdev); + u32 val = readl_relaxed(priv->base + STM32_VREFBUF_CSR); + + return FIELD_GET(STM32_VRS, val); +} + +static const struct regulator_ops stm32_vrefbuf_volt_ops = { + .enable = stm32_vrefbuf_enable, + .disable = stm32_vrefbuf_disable, + .is_enabled = stm32_vrefbuf_is_enabled, + .get_voltage_sel = stm32_vrefbuf_get_voltage_sel, + .set_voltage_sel = stm32_vrefbuf_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, +}; + +static const struct regulator_desc stm32_vrefbuf_regu = { + .name = "vref", + .supply_name = "vdda", + .volt_table = stm32_vrefbuf_voltages, + .n_voltages = ARRAY_SIZE(stm32_vrefbuf_voltages), + .ops = &stm32_vrefbuf_volt_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; + +static int stm32_vrefbuf_probe(struct platform_device *pdev) +{ + struct resource *res; + struct stm32_vrefbuf *priv; + struct regulator_config config = { }; + struct regulator_dev *rdev; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(&pdev->dev, "clk prepare failed with error %d\n", ret); + return ret; + } + + config.dev = &pdev->dev; + config.driver_data = priv; + config.of_node = pdev->dev.of_node; + config.init_data = of_get_regulator_init_data(&pdev->dev, + pdev->dev.of_node, + &stm32_vrefbuf_regu); + + rdev = regulator_register(&stm32_vrefbuf_regu, &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(&pdev->dev, "register failed with error %d\n", ret); + goto err_clk_dis; + } + platform_set_drvdata(pdev, rdev); + + return 0; + +err_clk_dis: + clk_disable_unprepare(priv->clk); + + return ret; +} + +static int stm32_vrefbuf_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + struct stm32_vrefbuf *priv = rdev_get_drvdata(rdev); + + regulator_unregister(rdev); + clk_disable_unprepare(priv->clk); + + return 0; +}; + +static const struct of_device_id stm32_vrefbuf_of_match[] = { + { .compatible = "st,stm32-vrefbuf", }, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_vrefbuf_of_match); + +static struct platform_driver stm32_vrefbuf_driver = { + .probe = stm32_vrefbuf_probe, + .remove = stm32_vrefbuf_remove, + .driver = { + .name = "stm32-vrefbuf", + .of_match_table = of_match_ptr(stm32_vrefbuf_of_match), + }, +}; +module_platform_driver(stm32_vrefbuf_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics STM32 VREFBUF driver"); +MODULE_ALIAS("platform:stm32-vrefbuf"); diff --git a/drivers/regulator/stw481x-vmmc.c b/drivers/regulator/stw481x-vmmc.c new file mode 100644 index 000000000..342f5da79 --- /dev/null +++ b/drivers/regulator/stw481x-vmmc.c @@ -0,0 +1,104 @@ +/* + * Regulator driver for STw4810/STw4811 VMMC regulator. + * + * Copyright (C) 2013 ST-Ericsson SA + * Written on behalf of Linaro for ST-Ericsson + * + * Author: Linus Walleij <linus.walleij@linaro.org> + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/mfd/stw481x.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +static const unsigned int stw481x_vmmc_voltages[] = { + 1800000, + 1800000, + 2850000, + 3000000, + 1850000, + 2600000, + 2700000, + 3300000, +}; + +static struct regulator_ops stw481x_vmmc_ops = { + .list_voltage = regulator_list_voltage_table, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static struct regulator_desc vmmc_regulator = { + .name = "VMMC", + .id = 0, + .ops = &stw481x_vmmc_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(stw481x_vmmc_voltages), + .volt_table = stw481x_vmmc_voltages, + .enable_time = 200, /* FIXME: look this up */ + .enable_reg = STW_CONF1, + .enable_mask = STW_CONF1_PDN_VMMC | STW_CONF1_MMC_LS_STATUS, + .enable_val = STW_CONF1_PDN_VMMC, + .vsel_reg = STW_CONF1, + .vsel_mask = STW_CONF1_VMMC_MASK, +}; + +static int stw481x_vmmc_regulator_probe(struct platform_device *pdev) +{ + struct stw481x *stw481x = dev_get_platdata(&pdev->dev); + struct regulator_config config = { }; + struct regulator_dev *rdev; + int ret; + + /* First disable the external VMMC if it's active */ + ret = regmap_update_bits(stw481x->map, STW_CONF2, + STW_CONF2_VMMC_EXT, 0); + if (ret) { + dev_err(&pdev->dev, "could not disable external VMMC\n"); + return ret; + } + + /* Register VMMC regulator */ + config.dev = &pdev->dev; + config.driver_data = stw481x; + config.regmap = stw481x->map; + config.of_node = pdev->dev.of_node; + config.init_data = of_get_regulator_init_data(&pdev->dev, + pdev->dev.of_node, + &vmmc_regulator); + + rdev = devm_regulator_register(&pdev->dev, &vmmc_regulator, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, + "error initializing STw481x VMMC regulator\n"); + return PTR_ERR(rdev); + } + + dev_info(&pdev->dev, "initialized STw481x VMMC regulator\n"); + return 0; +} + +static const struct of_device_id stw481x_vmmc_match[] = { + { .compatible = "st,stw481x-vmmc", }, + {}, +}; + +static struct platform_driver stw481x_vmmc_regulator_driver = { + .driver = { + .name = "stw481x-vmmc-regulator", + .of_match_table = stw481x_vmmc_match, + }, + .probe = stw481x_vmmc_regulator_probe, +}; + +module_platform_driver(stw481x_vmmc_regulator_driver); diff --git a/drivers/regulator/sy8106a-regulator.c b/drivers/regulator/sy8106a-regulator.c new file mode 100644 index 000000000..65fbd1f0b --- /dev/null +++ b/drivers/regulator/sy8106a-regulator.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// sy8106a-regulator.c - Regulator device driver for SY8106A +// +// Copyright (C) 2016 OndÅ™ej Jirman <megous@megous.com> +// Copyright (c) 2017-2018 Icenowy Zheng <icenowy@aosc.io> + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +#define SY8106A_REG_VOUT1_SEL 0x01 +#define SY8106A_REG_VOUT_COM 0x02 +#define SY8106A_REG_VOUT1_SEL_MASK 0x7f +#define SY8106A_DISABLE_REG BIT(0) +/* + * The I2C controlled voltage will only work when this bit is set; otherwise + * it will behave like a fixed regulator. + */ +#define SY8106A_GO_BIT BIT(7) + +struct sy8106a { + struct regulator_dev *rdev; + struct regmap *regmap; + u32 fixed_voltage; +}; + +static const struct regmap_config sy8106a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct regulator_ops sy8106a_ops = { + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + /* Enabling/disabling the regulator is not yet implemented */ +}; + +/* Default limits measured in millivolts */ +#define SY8106A_MIN_MV 680 +#define SY8106A_MAX_MV 1950 +#define SY8106A_STEP_MV 10 + +static const struct regulator_desc sy8106a_reg = { + .name = "SY8106A", + .id = 0, + .ops = &sy8106a_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = ((SY8106A_MAX_MV - SY8106A_MIN_MV) / SY8106A_STEP_MV) + 1, + .min_uV = (SY8106A_MIN_MV * 1000), + .uV_step = (SY8106A_STEP_MV * 1000), + .vsel_reg = SY8106A_REG_VOUT1_SEL, + .vsel_mask = SY8106A_REG_VOUT1_SEL_MASK, + /* + * This ramp_delay is a conservative default value which works on + * H3/H5 boards VDD-CPUX situations. + */ + .ramp_delay = 200, + .owner = THIS_MODULE, +}; + +/* + * I2C driver interface functions + */ +static int sy8106a_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct sy8106a *chip; + struct device *dev = &i2c->dev; + struct regulator_dev *rdev = NULL; + struct regulator_config config = { }; + unsigned int reg, vsel; + int error; + + chip = devm_kzalloc(&i2c->dev, sizeof(struct sy8106a), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + error = of_property_read_u32(dev->of_node, "silergy,fixed-microvolt", + &chip->fixed_voltage); + if (error) + return error; + + if (chip->fixed_voltage < SY8106A_MIN_MV * 1000 || + chip->fixed_voltage > SY8106A_MAX_MV * 1000) + return -EINVAL; + + chip->regmap = devm_regmap_init_i2c(i2c, &sy8106a_regmap_config); + if (IS_ERR(chip->regmap)) { + error = PTR_ERR(chip->regmap); + dev_err(dev, "Failed to allocate register map: %d\n", error); + return error; + } + + config.dev = &i2c->dev; + config.regmap = chip->regmap; + config.driver_data = chip; + + config.of_node = dev->of_node; + config.init_data = of_get_regulator_init_data(dev, dev->of_node, + &sy8106a_reg); + + if (!config.init_data) + return -ENOMEM; + + /* Ensure GO_BIT is enabled when probing */ + error = regmap_read(chip->regmap, SY8106A_REG_VOUT1_SEL, ®); + if (error) + return error; + + if (!(reg & SY8106A_GO_BIT)) { + vsel = (chip->fixed_voltage / 1000 - SY8106A_MIN_MV) / + SY8106A_STEP_MV; + + error = regmap_write(chip->regmap, SY8106A_REG_VOUT1_SEL, + vsel | SY8106A_GO_BIT); + if (error) + return error; + } + + /* Probe regulator */ + rdev = devm_regulator_register(&i2c->dev, &sy8106a_reg, &config); + if (IS_ERR(rdev)) { + error = PTR_ERR(rdev); + dev_err(&i2c->dev, "Failed to register SY8106A regulator: %d\n", error); + return error; + } + + chip->rdev = rdev; + + i2c_set_clientdata(i2c, chip); + + return 0; +} + +static const struct of_device_id sy8106a_i2c_of_match[] = { + { .compatible = "silergy,sy8106a" }, + { }, +}; +MODULE_DEVICE_TABLE(of, sy8106a_i2c_of_match); + +static const struct i2c_device_id sy8106a_i2c_id[] = { + { "sy8106a", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, sy8106a_i2c_id); + +static struct i2c_driver sy8106a_regulator_driver = { + .driver = { + .name = "sy8106a", + .of_match_table = of_match_ptr(sy8106a_i2c_of_match), + }, + .probe = sy8106a_i2c_probe, + .id_table = sy8106a_i2c_id, +}; + +module_i2c_driver(sy8106a_regulator_driver); + +MODULE_AUTHOR("OndÅ™ej Jirman <megous@megous.com>"); +MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>"); +MODULE_DESCRIPTION("Regulator device driver for Silergy SY8106A"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/ti-abb-regulator.c b/drivers/regulator/ti-abb-regulator.c new file mode 100644 index 000000000..016330f90 --- /dev/null +++ b/drivers/regulator/ti-abb-regulator.c @@ -0,0 +1,902 @@ +/* + * Texas Instruments SoC Adaptive Body Bias(ABB) Regulator + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Mike Turquette <mturquette@ti.com> + * + * Copyright (C) 2012-2013 Texas Instruments, Inc. + * Andrii Tseglytskyi <andrii.tseglytskyi@ti.com> + * Nishanth Menon <nm@ti.com> + * + * 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. + * + * 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/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> + +/* + * ABB LDO operating states: + * NOMINAL_OPP: bypasses the ABB LDO + * FAST_OPP: sets ABB LDO to Forward Body-Bias + * SLOW_OPP: sets ABB LDO to Reverse Body-Bias + */ +#define TI_ABB_NOMINAL_OPP 0 +#define TI_ABB_FAST_OPP 1 +#define TI_ABB_SLOW_OPP 3 + +/** + * struct ti_abb_info - ABB information per voltage setting + * @opp_sel: one of TI_ABB macro + * @vset: (optional) vset value that LDOVBB needs to be overriden with. + * + * Array of per voltage entries organized in the same order as regulator_desc's + * volt_table list. (selector is used to index from this array) + */ +struct ti_abb_info { + u32 opp_sel; + u32 vset; +}; + +/** + * struct ti_abb_reg - Register description for ABB block + * @setup_off: setup register offset from base + * @control_off: control register offset from base + * @sr2_wtcnt_value_mask: setup register- sr2_wtcnt_value mask + * @fbb_sel_mask: setup register- FBB sel mask + * @rbb_sel_mask: setup register- RBB sel mask + * @sr2_en_mask: setup register- enable mask + * @opp_change_mask: control register - mask to trigger LDOVBB change + * @opp_sel_mask: control register - mask for mode to operate + */ +struct ti_abb_reg { + u32 setup_off; + u32 control_off; + + /* Setup register fields */ + u32 sr2_wtcnt_value_mask; + u32 fbb_sel_mask; + u32 rbb_sel_mask; + u32 sr2_en_mask; + + /* Control register fields */ + u32 opp_change_mask; + u32 opp_sel_mask; +}; + +/** + * struct ti_abb - ABB instance data + * @rdesc: regulator descriptor + * @clk: clock(usually sysclk) supplying ABB block + * @base: base address of ABB block + * @setup_reg: setup register of ABB block + * @control_reg: control register of ABB block + * @int_base: interrupt register base address + * @efuse_base: (optional) efuse base address for ABB modes + * @ldo_base: (optional) LDOVBB vset override base address + * @regs: pointer to struct ti_abb_reg for ABB block + * @txdone_mask: mask on int_base for tranxdone interrupt + * @ldovbb_override_mask: mask to ldo_base for overriding default LDO VBB + * vset with value from efuse + * @ldovbb_vset_mask: mask to ldo_base for providing the VSET override + * @info: array to per voltage ABB configuration + * @current_info_idx: current index to info + * @settling_time: SoC specific settling time for LDO VBB + */ +struct ti_abb { + struct regulator_desc rdesc; + struct clk *clk; + void __iomem *base; + void __iomem *setup_reg; + void __iomem *control_reg; + void __iomem *int_base; + void __iomem *efuse_base; + void __iomem *ldo_base; + + const struct ti_abb_reg *regs; + u32 txdone_mask; + u32 ldovbb_override_mask; + u32 ldovbb_vset_mask; + + struct ti_abb_info *info; + int current_info_idx; + + u32 settling_time; +}; + +/** + * ti_abb_rmw() - handy wrapper to set specific register bits + * @mask: mask for register field + * @value: value shifted to mask location and written + * @reg: register address + * + * Return: final register value (may be unused) + */ +static inline u32 ti_abb_rmw(u32 mask, u32 value, void __iomem *reg) +{ + u32 val; + + val = readl(reg); + val &= ~mask; + val |= (value << __ffs(mask)) & mask; + writel(val, reg); + + return val; +} + +/** + * ti_abb_check_txdone() - handy wrapper to check ABB tranxdone status + * @abb: pointer to the abb instance + * + * Return: true or false + */ +static inline bool ti_abb_check_txdone(const struct ti_abb *abb) +{ + return !!(readl(abb->int_base) & abb->txdone_mask); +} + +/** + * ti_abb_clear_txdone() - handy wrapper to clear ABB tranxdone status + * @abb: pointer to the abb instance + */ +static inline void ti_abb_clear_txdone(const struct ti_abb *abb) +{ + writel(abb->txdone_mask, abb->int_base); +}; + +/** + * ti_abb_wait_tranx() - waits for ABB tranxdone event + * @dev: device + * @abb: pointer to the abb instance + * + * Return: 0 on success or -ETIMEDOUT if the event is not cleared on time. + */ +static int ti_abb_wait_txdone(struct device *dev, struct ti_abb *abb) +{ + int timeout = 0; + bool status; + + while (timeout++ <= abb->settling_time) { + status = ti_abb_check_txdone(abb); + if (status) + return 0; + + udelay(1); + } + + dev_warn_ratelimited(dev, "%s:TRANXDONE timeout(%duS) int=0x%08x\n", + __func__, timeout, readl(abb->int_base)); + return -ETIMEDOUT; +} + +/** + * ti_abb_clear_all_txdone() - clears ABB tranxdone event + * @dev: device + * @abb: pointer to the abb instance + * + * Return: 0 on success or -ETIMEDOUT if the event is not cleared on time. + */ +static int ti_abb_clear_all_txdone(struct device *dev, const struct ti_abb *abb) +{ + int timeout = 0; + bool status; + + while (timeout++ <= abb->settling_time) { + ti_abb_clear_txdone(abb); + + status = ti_abb_check_txdone(abb); + if (!status) + return 0; + + udelay(1); + } + + dev_warn_ratelimited(dev, "%s:TRANXDONE timeout(%duS) int=0x%08x\n", + __func__, timeout, readl(abb->int_base)); + return -ETIMEDOUT; +} + +/** + * ti_abb_program_ldovbb() - program LDOVBB register for override value + * @dev: device + * @abb: pointer to the abb instance + * @info: ABB info to program + */ +static void ti_abb_program_ldovbb(struct device *dev, const struct ti_abb *abb, + struct ti_abb_info *info) +{ + u32 val; + + val = readl(abb->ldo_base); + /* clear up previous values */ + val &= ~(abb->ldovbb_override_mask | abb->ldovbb_vset_mask); + + switch (info->opp_sel) { + case TI_ABB_SLOW_OPP: + case TI_ABB_FAST_OPP: + val |= abb->ldovbb_override_mask; + val |= info->vset << __ffs(abb->ldovbb_vset_mask); + break; + } + + writel(val, abb->ldo_base); +} + +/** + * ti_abb_set_opp() - Setup ABB and LDO VBB for required bias + * @rdev: regulator device + * @abb: pointer to the abb instance + * @info: ABB info to program + * + * Return: 0 on success or appropriate error value when fails + */ +static int ti_abb_set_opp(struct regulator_dev *rdev, struct ti_abb *abb, + struct ti_abb_info *info) +{ + const struct ti_abb_reg *regs = abb->regs; + struct device *dev = &rdev->dev; + int ret; + + ret = ti_abb_clear_all_txdone(dev, abb); + if (ret) + goto out; + + ti_abb_rmw(regs->fbb_sel_mask | regs->rbb_sel_mask, 0, abb->setup_reg); + + switch (info->opp_sel) { + case TI_ABB_SLOW_OPP: + ti_abb_rmw(regs->rbb_sel_mask, 1, abb->setup_reg); + break; + case TI_ABB_FAST_OPP: + ti_abb_rmw(regs->fbb_sel_mask, 1, abb->setup_reg); + break; + } + + /* program next state of ABB ldo */ + ti_abb_rmw(regs->opp_sel_mask, info->opp_sel, abb->control_reg); + + /* + * program LDO VBB vset override if needed for !bypass mode + * XXX: Do not switch sequence - for !bypass, LDO override reset *must* + * be performed *before* switch to bias mode else VBB glitches. + */ + if (abb->ldo_base && info->opp_sel != TI_ABB_NOMINAL_OPP) + ti_abb_program_ldovbb(dev, abb, info); + + /* Initiate ABB ldo change */ + ti_abb_rmw(regs->opp_change_mask, 1, abb->control_reg); + + /* Wait for ABB LDO to complete transition to new Bias setting */ + ret = ti_abb_wait_txdone(dev, abb); + if (ret) + goto out; + + ret = ti_abb_clear_all_txdone(dev, abb); + if (ret) + goto out; + + /* + * Reset LDO VBB vset override bypass mode + * XXX: Do not switch sequence - for bypass, LDO override reset *must* + * be performed *after* switch to bypass else VBB glitches. + */ + if (abb->ldo_base && info->opp_sel == TI_ABB_NOMINAL_OPP) + ti_abb_program_ldovbb(dev, abb, info); + +out: + return ret; +} + +/** + * ti_abb_set_voltage_sel() - regulator accessor function to set ABB LDO + * @rdev: regulator device + * @sel: selector to index into required ABB LDO settings (maps to + * regulator descriptor's volt_table) + * + * Return: 0 on success or appropriate error value when fails + */ +static int ti_abb_set_voltage_sel(struct regulator_dev *rdev, unsigned sel) +{ + const struct regulator_desc *desc = rdev->desc; + struct ti_abb *abb = rdev_get_drvdata(rdev); + struct device *dev = &rdev->dev; + struct ti_abb_info *info, *oinfo; + int ret = 0; + + if (!abb) { + dev_err_ratelimited(dev, "%s: No regulator drvdata\n", + __func__); + return -ENODEV; + } + + if (!desc->n_voltages || !abb->info) { + dev_err_ratelimited(dev, + "%s: No valid voltage table entries?\n", + __func__); + return -EINVAL; + } + + if (sel >= desc->n_voltages) { + dev_err(dev, "%s: sel idx(%d) >= n_voltages(%d)\n", __func__, + sel, desc->n_voltages); + return -EINVAL; + } + + /* If we are in the same index as we were, nothing to do here! */ + if (sel == abb->current_info_idx) { + dev_dbg(dev, "%s: Already at sel=%d\n", __func__, sel); + return ret; + } + + info = &abb->info[sel]; + /* + * When Linux kernel is starting up, we are'nt sure of the + * Bias configuration that bootloader has configured. + * So, we get to know the actual setting the first time + * we are asked to transition. + */ + if (abb->current_info_idx == -EINVAL) + goto just_set_abb; + + /* If data is exactly the same, then just update index, no change */ + oinfo = &abb->info[abb->current_info_idx]; + if (!memcmp(info, oinfo, sizeof(*info))) { + dev_dbg(dev, "%s: Same data new idx=%d, old idx=%d\n", __func__, + sel, abb->current_info_idx); + goto out; + } + +just_set_abb: + ret = ti_abb_set_opp(rdev, abb, info); + +out: + if (!ret) + abb->current_info_idx = sel; + else + dev_err_ratelimited(dev, + "%s: Volt[%d] idx[%d] mode[%d] Fail(%d)\n", + __func__, desc->volt_table[sel], sel, + info->opp_sel, ret); + return ret; +} + +/** + * ti_abb_get_voltage_sel() - Regulator accessor to get current ABB LDO setting + * @rdev: regulator device + * + * Return: 0 on success or appropriate error value when fails + */ +static int ti_abb_get_voltage_sel(struct regulator_dev *rdev) +{ + const struct regulator_desc *desc = rdev->desc; + struct ti_abb *abb = rdev_get_drvdata(rdev); + struct device *dev = &rdev->dev; + + if (!abb) { + dev_err_ratelimited(dev, "%s: No regulator drvdata\n", + __func__); + return -ENODEV; + } + + if (!desc->n_voltages || !abb->info) { + dev_err_ratelimited(dev, + "%s: No valid voltage table entries?\n", + __func__); + return -EINVAL; + } + + if (abb->current_info_idx >= (int)desc->n_voltages) { + dev_err(dev, "%s: Corrupted data? idx(%d) >= n_voltages(%d)\n", + __func__, abb->current_info_idx, desc->n_voltages); + return -EINVAL; + } + + return abb->current_info_idx; +} + +/** + * ti_abb_init_timings() - setup ABB clock timing for the current platform + * @dev: device + * @abb: pointer to the abb instance + * + * Return: 0 if timing is updated, else returns error result. + */ +static int ti_abb_init_timings(struct device *dev, struct ti_abb *abb) +{ + u32 clock_cycles; + u32 clk_rate, sr2_wt_cnt_val, cycle_rate; + const struct ti_abb_reg *regs = abb->regs; + int ret; + char *pname = "ti,settling-time"; + + /* read device tree properties */ + ret = of_property_read_u32(dev->of_node, pname, &abb->settling_time); + if (ret) { + dev_err(dev, "Unable to get property '%s'(%d)\n", pname, ret); + return ret; + } + + /* ABB LDO cannot be settle in 0 time */ + if (!abb->settling_time) { + dev_err(dev, "Invalid property:'%s' set as 0!\n", pname); + return -EINVAL; + } + + pname = "ti,clock-cycles"; + ret = of_property_read_u32(dev->of_node, pname, &clock_cycles); + if (ret) { + dev_err(dev, "Unable to get property '%s'(%d)\n", pname, ret); + return ret; + } + /* ABB LDO cannot be settle in 0 clock cycles */ + if (!clock_cycles) { + dev_err(dev, "Invalid property:'%s' set as 0!\n", pname); + return -EINVAL; + } + + abb->clk = devm_clk_get(dev, NULL); + if (IS_ERR(abb->clk)) { + ret = PTR_ERR(abb->clk); + dev_err(dev, "%s: Unable to get clk(%d)\n", __func__, ret); + return ret; + } + + /* + * SR2_WTCNT_VALUE is the settling time for the ABB ldo after a + * transition and must be programmed with the correct time at boot. + * The value programmed into the register is the number of SYS_CLK + * clock cycles that match a given wall time profiled for the ldo. + * This value depends on: + * settling time of ldo in micro-seconds (varies per OMAP family) + * # of clock cycles per SYS_CLK period (varies per OMAP family) + * the SYS_CLK frequency in MHz (varies per board) + * The formula is: + * + * ldo settling time (in micro-seconds) + * SR2_WTCNT_VALUE = ------------------------------------------ + * (# system clock cycles) * (sys_clk period) + * + * Put another way: + * + * SR2_WTCNT_VALUE = settling time / (# SYS_CLK cycles / SYS_CLK rate)) + * + * To avoid dividing by zero multiply both "# clock cycles" and + * "settling time" by 10 such that the final result is the one we want. + */ + + /* Convert SYS_CLK rate to MHz & prevent divide by zero */ + clk_rate = DIV_ROUND_CLOSEST(clk_get_rate(abb->clk), 1000000); + + /* Calculate cycle rate */ + cycle_rate = DIV_ROUND_CLOSEST(clock_cycles * 10, clk_rate); + + /* Calulate SR2_WTCNT_VALUE */ + sr2_wt_cnt_val = DIV_ROUND_CLOSEST(abb->settling_time * 10, cycle_rate); + + dev_dbg(dev, "%s: Clk_rate=%ld, sr2_cnt=0x%08x\n", __func__, + clk_get_rate(abb->clk), sr2_wt_cnt_val); + + ti_abb_rmw(regs->sr2_wtcnt_value_mask, sr2_wt_cnt_val, abb->setup_reg); + + return 0; +} + +/** + * ti_abb_init_table() - Initialize ABB table from device tree + * @dev: device + * @abb: pointer to the abb instance + * @rinit_data: regulator initdata + * + * Return: 0 on success or appropriate error value when fails + */ +static int ti_abb_init_table(struct device *dev, struct ti_abb *abb, + struct regulator_init_data *rinit_data) +{ + struct ti_abb_info *info; + const u32 num_values = 6; + char *pname = "ti,abb_info"; + u32 i; + unsigned int *volt_table; + int num_entries, min_uV = INT_MAX, max_uV = 0; + struct regulation_constraints *c = &rinit_data->constraints; + + /* + * Each abb_info is a set of n-tuple, where n is num_values, consisting + * of voltage and a set of detection logic for ABB information for that + * voltage to apply. + */ + num_entries = of_property_count_u32_elems(dev->of_node, pname); + if (num_entries < 0) { + dev_err(dev, "No '%s' property?\n", pname); + return num_entries; + } + + if (!num_entries || (num_entries % num_values)) { + dev_err(dev, "All '%s' list entries need %d vals\n", pname, + num_values); + return -EINVAL; + } + num_entries /= num_values; + + info = devm_kcalloc(dev, num_entries, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + abb->info = info; + + volt_table = devm_kcalloc(dev, num_entries, sizeof(unsigned int), + GFP_KERNEL); + if (!volt_table) + return -ENOMEM; + + abb->rdesc.n_voltages = num_entries; + abb->rdesc.volt_table = volt_table; + /* We do not know where the OPP voltage is at the moment */ + abb->current_info_idx = -EINVAL; + + for (i = 0; i < num_entries; i++, info++, volt_table++) { + u32 efuse_offset, rbb_mask, fbb_mask, vset_mask; + u32 efuse_val; + + /* NOTE: num_values should equal to entries picked up here */ + of_property_read_u32_index(dev->of_node, pname, i * num_values, + volt_table); + of_property_read_u32_index(dev->of_node, pname, + i * num_values + 1, &info->opp_sel); + of_property_read_u32_index(dev->of_node, pname, + i * num_values + 2, &efuse_offset); + of_property_read_u32_index(dev->of_node, pname, + i * num_values + 3, &rbb_mask); + of_property_read_u32_index(dev->of_node, pname, + i * num_values + 4, &fbb_mask); + of_property_read_u32_index(dev->of_node, pname, + i * num_values + 5, &vset_mask); + + dev_dbg(dev, + "[%d]v=%d ABB=%d ef=0x%x rbb=0x%x fbb=0x%x vset=0x%x\n", + i, *volt_table, info->opp_sel, efuse_offset, rbb_mask, + fbb_mask, vset_mask); + + /* Find min/max for voltage set */ + if (min_uV > *volt_table) + min_uV = *volt_table; + if (max_uV < *volt_table) + max_uV = *volt_table; + + if (!abb->efuse_base) { + /* Ignore invalid data, but warn to help cleanup */ + if (efuse_offset || rbb_mask || fbb_mask || vset_mask) + dev_err(dev, "prop '%s': v=%d,bad efuse/mask\n", + pname, *volt_table); + goto check_abb; + } + + efuse_val = readl(abb->efuse_base + efuse_offset); + + /* Use ABB recommendation from Efuse */ + if (efuse_val & rbb_mask) + info->opp_sel = TI_ABB_SLOW_OPP; + else if (efuse_val & fbb_mask) + info->opp_sel = TI_ABB_FAST_OPP; + else if (rbb_mask || fbb_mask) + info->opp_sel = TI_ABB_NOMINAL_OPP; + + dev_dbg(dev, + "[%d]v=%d efusev=0x%x final ABB=%d\n", + i, *volt_table, efuse_val, info->opp_sel); + + /* Use recommended Vset bits from Efuse */ + if (!abb->ldo_base) { + if (vset_mask) + dev_err(dev, "prop'%s':v=%d vst=%x LDO base?\n", + pname, *volt_table, vset_mask); + continue; + } + info->vset = (efuse_val & vset_mask) >> __ffs(vset_mask); + dev_dbg(dev, "[%d]v=%d vset=%x\n", i, *volt_table, info->vset); +check_abb: + switch (info->opp_sel) { + case TI_ABB_NOMINAL_OPP: + case TI_ABB_FAST_OPP: + case TI_ABB_SLOW_OPP: + /* Valid values */ + break; + default: + dev_err(dev, "%s:[%d]v=%d, ABB=%d is invalid! Abort!\n", + __func__, i, *volt_table, info->opp_sel); + return -EINVAL; + } + } + + /* Setup the min/max voltage constraints from the supported list */ + c->min_uV = min_uV; + c->max_uV = max_uV; + + return 0; +} + +static struct regulator_ops ti_abb_reg_ops = { + .list_voltage = regulator_list_voltage_table, + + .set_voltage_sel = ti_abb_set_voltage_sel, + .get_voltage_sel = ti_abb_get_voltage_sel, +}; + +/* Default ABB block offsets, IF this changes in future, create new one */ +static const struct ti_abb_reg abb_regs_v1 = { + /* WARNING: registers are wrongly documented in TRM */ + .setup_off = 0x04, + .control_off = 0x00, + + .sr2_wtcnt_value_mask = (0xff << 8), + .fbb_sel_mask = (0x01 << 2), + .rbb_sel_mask = (0x01 << 1), + .sr2_en_mask = (0x01 << 0), + + .opp_change_mask = (0x01 << 2), + .opp_sel_mask = (0x03 << 0), +}; + +static const struct ti_abb_reg abb_regs_v2 = { + .setup_off = 0x00, + .control_off = 0x04, + + .sr2_wtcnt_value_mask = (0xff << 8), + .fbb_sel_mask = (0x01 << 2), + .rbb_sel_mask = (0x01 << 1), + .sr2_en_mask = (0x01 << 0), + + .opp_change_mask = (0x01 << 2), + .opp_sel_mask = (0x03 << 0), +}; + +static const struct ti_abb_reg abb_regs_generic = { + .sr2_wtcnt_value_mask = (0xff << 8), + .fbb_sel_mask = (0x01 << 2), + .rbb_sel_mask = (0x01 << 1), + .sr2_en_mask = (0x01 << 0), + + .opp_change_mask = (0x01 << 2), + .opp_sel_mask = (0x03 << 0), +}; + +static const struct of_device_id ti_abb_of_match[] = { + {.compatible = "ti,abb-v1", .data = &abb_regs_v1}, + {.compatible = "ti,abb-v2", .data = &abb_regs_v2}, + {.compatible = "ti,abb-v3", .data = &abb_regs_generic}, + { }, +}; + +MODULE_DEVICE_TABLE(of, ti_abb_of_match); + +/** + * ti_abb_probe() - Initialize an ABB ldo instance + * @pdev: ABB platform device + * + * Initializes an individual ABB LDO for required Body-Bias. ABB is used to + * addional bias supply to SoC modules for power savings or mandatory stability + * configuration at certain Operating Performance Points(OPPs). + * + * Return: 0 on success or appropriate error value when fails + */ +static int ti_abb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *match; + struct resource *res; + struct ti_abb *abb; + struct regulator_init_data *initdata = NULL; + struct regulator_dev *rdev = NULL; + struct regulator_desc *desc; + struct regulation_constraints *c; + struct regulator_config config = { }; + char *pname; + int ret = 0; + + match = of_match_device(ti_abb_of_match, dev); + if (!match) { + /* We do not expect this to happen */ + dev_err(dev, "%s: Unable to match device\n", __func__); + return -ENODEV; + } + if (!match->data) { + dev_err(dev, "%s: Bad data in match\n", __func__); + return -EINVAL; + } + + abb = devm_kzalloc(dev, sizeof(struct ti_abb), GFP_KERNEL); + if (!abb) + return -ENOMEM; + abb->regs = match->data; + + /* Map ABB resources */ + if (abb->regs->setup_off || abb->regs->control_off) { + pname = "base-address"; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, pname); + abb->base = devm_ioremap_resource(dev, res); + if (IS_ERR(abb->base)) + return PTR_ERR(abb->base); + + abb->setup_reg = abb->base + abb->regs->setup_off; + abb->control_reg = abb->base + abb->regs->control_off; + + } else { + pname = "control-address"; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, pname); + abb->control_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(abb->control_reg)) + return PTR_ERR(abb->control_reg); + + pname = "setup-address"; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, pname); + abb->setup_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(abb->setup_reg)) + return PTR_ERR(abb->setup_reg); + } + + pname = "int-address"; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, pname); + if (!res) { + dev_err(dev, "Missing '%s' IO resource\n", pname); + return -ENODEV; + } + /* + * We may have shared interrupt register offsets which are + * write-1-to-clear between domains ensuring exclusivity. + */ + abb->int_base = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (!abb->int_base) { + dev_err(dev, "Unable to map '%s'\n", pname); + return -ENOMEM; + } + + /* Map Optional resources */ + pname = "efuse-address"; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, pname); + if (!res) { + dev_dbg(dev, "Missing '%s' IO resource\n", pname); + ret = -ENODEV; + goto skip_opt; + } + + /* + * We may have shared efuse register offsets which are read-only + * between domains + */ + abb->efuse_base = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (!abb->efuse_base) { + dev_err(dev, "Unable to map '%s'\n", pname); + return -ENOMEM; + } + + pname = "ldo-address"; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, pname); + if (!res) { + dev_dbg(dev, "Missing '%s' IO resource\n", pname); + ret = -ENODEV; + goto skip_opt; + } + abb->ldo_base = devm_ioremap_resource(dev, res); + if (IS_ERR(abb->ldo_base)) + return PTR_ERR(abb->ldo_base); + + /* IF ldo_base is set, the following are mandatory */ + pname = "ti,ldovbb-override-mask"; + ret = + of_property_read_u32(pdev->dev.of_node, pname, + &abb->ldovbb_override_mask); + if (ret) { + dev_err(dev, "Missing '%s' (%d)\n", pname, ret); + return ret; + } + if (!abb->ldovbb_override_mask) { + dev_err(dev, "Invalid property:'%s' set as 0!\n", pname); + return -EINVAL; + } + + pname = "ti,ldovbb-vset-mask"; + ret = + of_property_read_u32(pdev->dev.of_node, pname, + &abb->ldovbb_vset_mask); + if (ret) { + dev_err(dev, "Missing '%s' (%d)\n", pname, ret); + return ret; + } + if (!abb->ldovbb_vset_mask) { + dev_err(dev, "Invalid property:'%s' set as 0!\n", pname); + return -EINVAL; + } + +skip_opt: + pname = "ti,tranxdone-status-mask"; + ret = + of_property_read_u32(pdev->dev.of_node, pname, + &abb->txdone_mask); + if (ret) { + dev_err(dev, "Missing '%s' (%d)\n", pname, ret); + return ret; + } + if (!abb->txdone_mask) { + dev_err(dev, "Invalid property:'%s' set as 0!\n", pname); + return -EINVAL; + } + + initdata = of_get_regulator_init_data(dev, pdev->dev.of_node, + &abb->rdesc); + if (!initdata) { + dev_err(dev, "%s: Unable to alloc regulator init data\n", + __func__); + return -ENOMEM; + } + + /* init ABB opp_sel table */ + ret = ti_abb_init_table(dev, abb, initdata); + if (ret) + return ret; + + /* init ABB timing */ + ret = ti_abb_init_timings(dev, abb); + if (ret) + return ret; + + desc = &abb->rdesc; + desc->name = dev_name(dev); + desc->owner = THIS_MODULE; + desc->type = REGULATOR_VOLTAGE; + desc->ops = &ti_abb_reg_ops; + + c = &initdata->constraints; + if (desc->n_voltages > 1) + c->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE; + c->always_on = true; + + config.dev = dev; + config.init_data = initdata; + config.driver_data = abb; + config.of_node = pdev->dev.of_node; + + rdev = devm_regulator_register(dev, desc, &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(dev, "%s: failed to register regulator(%d)\n", + __func__, ret); + return ret; + } + platform_set_drvdata(pdev, rdev); + + /* Enable the ldo if not already done by bootloader */ + ti_abb_rmw(abb->regs->sr2_en_mask, 1, abb->setup_reg); + + return 0; +} + +MODULE_ALIAS("platform:ti_abb"); + +static struct platform_driver ti_abb_driver = { + .probe = ti_abb_probe, + .driver = { + .name = "ti_abb", + .of_match_table = of_match_ptr(ti_abb_of_match), + }, +}; +module_platform_driver(ti_abb_driver); + +MODULE_DESCRIPTION("Texas Instruments ABB LDO regulator driver"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/tps51632-regulator.c b/drivers/regulator/tps51632-regulator.c new file mode 100644 index 000000000..c139890c1 --- /dev/null +++ b/drivers/regulator/tps51632-regulator.c @@ -0,0 +1,388 @@ +/* + * tps51632-regulator.c -- TI TPS51632 + * + * Regulator driver for TPS51632 3-2-1 Phase D-Cap Step Down Driverless + * Controller with serial VID control and DVFS. + * + * Copyright (c) 2012, NVIDIA Corporation. + * + * Author: Laxman Dewangan <ldewangan@nvidia.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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/tps51632-regulator.h> +#include <linux/slab.h> + +/* Register definitions */ +#define TPS51632_VOLTAGE_SELECT_REG 0x0 +#define TPS51632_VOLTAGE_BASE_REG 0x1 +#define TPS51632_OFFSET_REG 0x2 +#define TPS51632_IMON_REG 0x3 +#define TPS51632_VMAX_REG 0x4 +#define TPS51632_DVFS_CONTROL_REG 0x5 +#define TPS51632_POWER_STATE_REG 0x6 +#define TPS51632_SLEW_REGS 0x7 +#define TPS51632_FAULT_REG 0x14 + +#define TPS51632_MAX_REG 0x15 + +#define TPS51632_VOUT_MASK 0x7F +#define TPS51632_VOUT_OFFSET_MASK 0x1F +#define TPS51632_VMAX_MASK 0x7F +#define TPS51632_VMAX_LOCK 0x80 + +/* TPS51632_DVFS_CONTROL_REG */ +#define TPS51632_DVFS_PWMEN 0x1 +#define TPS51632_DVFS_STEP_20 0x2 +#define TPS51632_DVFS_VMAX_PG 0x4 +#define TPS51632_DVFS_PWMRST 0x8 +#define TPS51632_DVFS_OCA_EN 0x10 +#define TPS51632_DVFS_FCCM 0x20 + +/* TPS51632_POWER_STATE_REG */ +#define TPS51632_POWER_STATE_MASK 0x03 +#define TPS51632_POWER_STATE_MULTI_PHASE_CCM 0x0 +#define TPS51632_POWER_STATE_SINGLE_PHASE_CCM 0x1 +#define TPS51632_POWER_STATE_SINGLE_PHASE_DCM 0x2 + +#define TPS51632_MIN_VOLTAGE 500000 +#define TPS51632_MAX_VOLTAGE 1520000 +#define TPS51632_VOLTAGE_STEP_10mV 10000 +#define TPS51632_VOLTAGE_STEP_20mV 20000 +#define TPS51632_MAX_VSEL 0x7F +#define TPS51632_MIN_VSEL 0x19 +#define TPS51632_DEFAULT_RAMP_DELAY 6000 +#define TPS51632_VOLT_VSEL(uV) \ + (DIV_ROUND_UP(uV - TPS51632_MIN_VOLTAGE, \ + TPS51632_VOLTAGE_STEP_10mV) + \ + TPS51632_MIN_VSEL) + +/* TPS51632 chip information */ +struct tps51632_chip { + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *rdev; + struct regmap *regmap; +}; + +static int tps51632_dcdc_set_ramp_delay(struct regulator_dev *rdev, + int ramp_delay) +{ + struct tps51632_chip *tps = rdev_get_drvdata(rdev); + int bit; + int ret; + + if (ramp_delay == 0) + bit = 0; + else + bit = DIV_ROUND_UP(ramp_delay, 6000) - 1; + + ret = regmap_write(tps->regmap, TPS51632_SLEW_REGS, BIT(bit)); + if (ret < 0) + dev_err(tps->dev, "SLEW reg write failed, err %d\n", ret); + return ret; +} + +static struct regulator_ops tps51632_dcdc_ops = { + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = tps51632_dcdc_set_ramp_delay, +}; + +static int tps51632_init_dcdc(struct tps51632_chip *tps, + struct tps51632_regulator_platform_data *pdata) +{ + int ret; + uint8_t control = 0; + int vsel; + + if (!pdata->enable_pwm_dvfs) + goto skip_pwm_config; + + control |= TPS51632_DVFS_PWMEN; + vsel = TPS51632_VOLT_VSEL(pdata->base_voltage_uV); + ret = regmap_write(tps->regmap, TPS51632_VOLTAGE_BASE_REG, vsel); + if (ret < 0) { + dev_err(tps->dev, "BASE reg write failed, err %d\n", ret); + return ret; + } + + if (pdata->dvfs_step_20mV) + control |= TPS51632_DVFS_STEP_20; + + if (pdata->max_voltage_uV) { + unsigned int vmax; + /** + * TPS51632 hw behavior: VMAX register can be write only + * once as it get locked after first write. The lock get + * reset only when device is power-reset. + * Write register only when lock bit is not enabled. + */ + ret = regmap_read(tps->regmap, TPS51632_VMAX_REG, &vmax); + if (ret < 0) { + dev_err(tps->dev, "VMAX read failed, err %d\n", ret); + return ret; + } + if (!(vmax & TPS51632_VMAX_LOCK)) { + vsel = TPS51632_VOLT_VSEL(pdata->max_voltage_uV); + ret = regmap_write(tps->regmap, TPS51632_VMAX_REG, + vsel); + if (ret < 0) { + dev_err(tps->dev, + "VMAX write failed, err %d\n", ret); + return ret; + } + } + } + +skip_pwm_config: + ret = regmap_write(tps->regmap, TPS51632_DVFS_CONTROL_REG, control); + if (ret < 0) + dev_err(tps->dev, "DVFS reg write failed, err %d\n", ret); + return ret; +} + +static bool is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TPS51632_OFFSET_REG: + case TPS51632_FAULT_REG: + case TPS51632_IMON_REG: + return true; + default: + return false; + } +} + +static bool is_read_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x08 ... 0x0F: + return false; + default: + return true; + } +} + +static bool is_write_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TPS51632_VOLTAGE_SELECT_REG: + case TPS51632_VOLTAGE_BASE_REG: + case TPS51632_VMAX_REG: + case TPS51632_DVFS_CONTROL_REG: + case TPS51632_POWER_STATE_REG: + case TPS51632_SLEW_REGS: + return true; + default: + return false; + } +} + +static const struct regmap_config tps51632_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = is_write_reg, + .readable_reg = is_read_reg, + .volatile_reg = is_volatile_reg, + .max_register = TPS51632_MAX_REG - 1, + .cache_type = REGCACHE_RBTREE, +}; + +#if defined(CONFIG_OF) +static const struct of_device_id tps51632_of_match[] = { + { .compatible = "ti,tps51632",}, + {}, +}; +MODULE_DEVICE_TABLE(of, tps51632_of_match); + +static struct tps51632_regulator_platform_data * + of_get_tps51632_platform_data(struct device *dev, + const struct regulator_desc *desc) +{ + struct tps51632_regulator_platform_data *pdata; + struct device_node *np = dev->of_node; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + pdata->reg_init_data = of_get_regulator_init_data(dev, dev->of_node, + desc); + if (!pdata->reg_init_data) { + dev_err(dev, "Not able to get OF regulator init data\n"); + return NULL; + } + + pdata->enable_pwm_dvfs = + of_property_read_bool(np, "ti,enable-pwm-dvfs"); + pdata->dvfs_step_20mV = of_property_read_bool(np, "ti,dvfs-step-20mV"); + + pdata->base_voltage_uV = pdata->reg_init_data->constraints.min_uV ? : + TPS51632_MIN_VOLTAGE; + pdata->max_voltage_uV = pdata->reg_init_data->constraints.max_uV ? : + TPS51632_MAX_VOLTAGE; + return pdata; +} +#else +static struct tps51632_regulator_platform_data * + of_get_tps51632_platform_data(struct device *dev, + const struct regulator_desc *desc) +{ + return NULL; +} +#endif + +static int tps51632_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps51632_regulator_platform_data *pdata; + struct regulator_dev *rdev; + struct tps51632_chip *tps; + int ret; + struct regulator_config config = { }; + + if (client->dev.of_node) { + const struct of_device_id *match; + match = of_match_device(of_match_ptr(tps51632_of_match), + &client->dev); + if (!match) { + dev_err(&client->dev, "Error: No device match found\n"); + return -ENODEV; + } + } + + tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); + if (!tps) + return -ENOMEM; + + tps->dev = &client->dev; + tps->desc.name = client->name; + tps->desc.id = 0; + tps->desc.ramp_delay = TPS51632_DEFAULT_RAMP_DELAY; + tps->desc.min_uV = TPS51632_MIN_VOLTAGE; + tps->desc.uV_step = TPS51632_VOLTAGE_STEP_10mV; + tps->desc.linear_min_sel = TPS51632_MIN_VSEL; + tps->desc.n_voltages = TPS51632_MAX_VSEL + 1; + tps->desc.ops = &tps51632_dcdc_ops; + tps->desc.type = REGULATOR_VOLTAGE; + tps->desc.owner = THIS_MODULE; + + pdata = dev_get_platdata(&client->dev); + if (!pdata && client->dev.of_node) + pdata = of_get_tps51632_platform_data(&client->dev, &tps->desc); + if (!pdata) { + dev_err(&client->dev, "No Platform data\n"); + return -EINVAL; + } + + if (pdata->enable_pwm_dvfs) { + if ((pdata->base_voltage_uV < TPS51632_MIN_VOLTAGE) || + (pdata->base_voltage_uV > TPS51632_MAX_VOLTAGE)) { + dev_err(&client->dev, "Invalid base_voltage_uV setting\n"); + return -EINVAL; + } + + if ((pdata->max_voltage_uV) && + ((pdata->max_voltage_uV < TPS51632_MIN_VOLTAGE) || + (pdata->max_voltage_uV > TPS51632_MAX_VOLTAGE))) { + dev_err(&client->dev, "Invalid max_voltage_uV setting\n"); + return -EINVAL; + } + } + + if (pdata->enable_pwm_dvfs) + tps->desc.vsel_reg = TPS51632_VOLTAGE_BASE_REG; + else + tps->desc.vsel_reg = TPS51632_VOLTAGE_SELECT_REG; + tps->desc.vsel_mask = TPS51632_VOUT_MASK; + + tps->regmap = devm_regmap_init_i2c(client, &tps51632_regmap_config); + if (IS_ERR(tps->regmap)) { + ret = PTR_ERR(tps->regmap); + dev_err(&client->dev, "regmap init failed, err %d\n", ret); + return ret; + } + i2c_set_clientdata(client, tps); + + ret = tps51632_init_dcdc(tps, pdata); + if (ret < 0) { + dev_err(tps->dev, "Init failed, err = %d\n", ret); + return ret; + } + + /* Register the regulators */ + config.dev = &client->dev; + config.init_data = pdata->reg_init_data; + config.driver_data = tps; + config.regmap = tps->regmap; + config.of_node = client->dev.of_node; + + rdev = devm_regulator_register(&client->dev, &tps->desc, &config); + if (IS_ERR(rdev)) { + dev_err(tps->dev, "regulator register failed\n"); + return PTR_ERR(rdev); + } + + tps->rdev = rdev; + return 0; +} + +static const struct i2c_device_id tps51632_id[] = { + {.name = "tps51632",}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, tps51632_id); + +static struct i2c_driver tps51632_i2c_driver = { + .driver = { + .name = "tps51632", + .of_match_table = of_match_ptr(tps51632_of_match), + }, + .probe = tps51632_probe, + .id_table = tps51632_id, +}; + +static int __init tps51632_init(void) +{ + return i2c_add_driver(&tps51632_i2c_driver); +} +subsys_initcall(tps51632_init); + +static void __exit tps51632_cleanup(void) +{ + i2c_del_driver(&tps51632_i2c_driver); +} +module_exit(tps51632_cleanup); + +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_DESCRIPTION("TPS51632 voltage regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/tps6105x-regulator.c b/drivers/regulator/tps6105x-regulator.c new file mode 100644 index 000000000..584ef3ded --- /dev/null +++ b/drivers/regulator/tps6105x-regulator.c @@ -0,0 +1,114 @@ +/* + * Driver for TPS61050/61052 boost converters, typically used for white LEDs + * or audio amplifiers. + * + * Copyright (C) 2011 ST-Ericsson SA + * Written on behalf of Linaro for ST-Ericsson + * + * Author: Linus Walleij <linus.walleij@linaro.org> + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps6105x.h> + +static const unsigned int tps6105x_voltages[] = { + 4500000, + 5000000, + 5250000, + 5000000, /* There is an additional 5V */ +}; + +static struct regulator_ops tps6105x_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_table, +}; + +static const struct regulator_desc tps6105x_regulator_desc = { + .name = "tps6105x-boost", + .ops = &tps6105x_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = 0, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(tps6105x_voltages), + .volt_table = tps6105x_voltages, + .vsel_reg = TPS6105X_REG_0, + .vsel_mask = TPS6105X_REG0_VOLTAGE_MASK, + .enable_reg = TPS6105X_REG_0, + .enable_mask = TPS6105X_REG0_MODE_MASK, + .enable_val = TPS6105X_REG0_MODE_VOLTAGE << + TPS6105X_REG0_MODE_SHIFT, +}; + +/* + * Registers the chip as a voltage regulator + */ +static int tps6105x_regulator_probe(struct platform_device *pdev) +{ + struct tps6105x *tps6105x = dev_get_platdata(&pdev->dev); + struct tps6105x_platform_data *pdata = tps6105x->pdata; + struct regulator_config config = { }; + int ret; + + /* This instance is not set for regulator mode so bail out */ + if (pdata->mode != TPS6105X_MODE_VOLTAGE) { + dev_info(&pdev->dev, + "chip not in voltage mode mode, exit probe\n"); + return 0; + } + + config.dev = &tps6105x->client->dev; + config.init_data = pdata->regulator_data; + config.driver_data = tps6105x; + config.regmap = tps6105x->regmap; + + /* Register regulator with framework */ + tps6105x->regulator = devm_regulator_register(&pdev->dev, + &tps6105x_regulator_desc, + &config); + if (IS_ERR(tps6105x->regulator)) { + ret = PTR_ERR(tps6105x->regulator); + dev_err(&tps6105x->client->dev, + "failed to register regulator\n"); + return ret; + } + platform_set_drvdata(pdev, tps6105x); + + return 0; +} + +static struct platform_driver tps6105x_regulator_driver = { + .driver = { + .name = "tps6105x-regulator", + }, + .probe = tps6105x_regulator_probe, +}; + +static __init int tps6105x_regulator_init(void) +{ + return platform_driver_register(&tps6105x_regulator_driver); +} +subsys_initcall(tps6105x_regulator_init); + +static __exit void tps6105x_regulator_exit(void) +{ + platform_driver_unregister(&tps6105x_regulator_driver); +} +module_exit(tps6105x_regulator_exit); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("TPS6105x regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps6105x-regulator"); diff --git a/drivers/regulator/tps62360-regulator.c b/drivers/regulator/tps62360-regulator.c new file mode 100644 index 000000000..f6a6d36a6 --- /dev/null +++ b/drivers/regulator/tps62360-regulator.c @@ -0,0 +1,539 @@ +/* + * tps62360.c -- TI tps62360 + * + * Driver for processor core supply tps62360, tps62361B, tps62362 and tps62363. + * + * Copyright (c) 2012, NVIDIA Corporation. + * + * Author: Laxman Dewangan <ldewangan@nvidia.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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/regulator/of_regulator.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/tps62360.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/regmap.h> + +/* Register definitions */ +#define REG_VSET0 0 +#define REG_VSET1 1 +#define REG_VSET2 2 +#define REG_VSET3 3 +#define REG_CONTROL 4 +#define REG_TEMP 5 +#define REG_RAMPCTRL 6 +#define REG_CHIPID 8 + +#define FORCE_PWM_ENABLE BIT(7) + +enum chips {TPS62360, TPS62361, TPS62362, TPS62363}; + +#define TPS62360_BASE_VOLTAGE 770000 +#define TPS62360_N_VOLTAGES 64 + +#define TPS62361_BASE_VOLTAGE 500000 +#define TPS62361_N_VOLTAGES 128 + +/* tps 62360 chip information */ +struct tps62360_chip { + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *rdev; + struct regmap *regmap; + int vsel0_gpio; + int vsel1_gpio; + u8 voltage_reg_mask; + bool en_internal_pulldn; + bool en_discharge; + bool valid_gpios; + int lru_index[4]; + int curr_vset_vsel[4]; + int curr_vset_id; +}; + +/* + * find_voltage_set_register: Find new voltage configuration register + * (VSET) id. + * The finding of the new VSET register will be based on the LRU mechanism. + * Each VSET register will have different voltage configured . This + * Function will look if any of the VSET register have requested voltage set + * or not. + * - If it is already there then it will make that register as most + * recently used and return as found so that caller need not to set + * the VSET register but need to set the proper gpios to select this + * VSET register. + * - If requested voltage is not found then it will use the least + * recently mechanism to get new VSET register for new configuration + * and will return not_found so that caller need to set new VSET + * register and then gpios (both). + */ +static bool find_voltage_set_register(struct tps62360_chip *tps, + int req_vsel, int *vset_reg_id) +{ + int i; + bool found = false; + int new_vset_reg = tps->lru_index[3]; + int found_index = 3; + + for (i = 0; i < 4; ++i) { + if (tps->curr_vset_vsel[tps->lru_index[i]] == req_vsel) { + new_vset_reg = tps->lru_index[i]; + found_index = i; + found = true; + goto update_lru_index; + } + } + +update_lru_index: + for (i = found_index; i > 0; i--) + tps->lru_index[i] = tps->lru_index[i - 1]; + + tps->lru_index[0] = new_vset_reg; + *vset_reg_id = new_vset_reg; + return found; +} + +static int tps62360_dcdc_get_voltage_sel(struct regulator_dev *dev) +{ + struct tps62360_chip *tps = rdev_get_drvdata(dev); + int vsel; + unsigned int data; + int ret; + + ret = regmap_read(tps->regmap, REG_VSET0 + tps->curr_vset_id, &data); + if (ret < 0) { + dev_err(tps->dev, "%s(): register %d read failed with err %d\n", + __func__, REG_VSET0 + tps->curr_vset_id, ret); + return ret; + } + vsel = (int)data & tps->voltage_reg_mask; + return vsel; +} + +static int tps62360_dcdc_set_voltage_sel(struct regulator_dev *dev, + unsigned selector) +{ + struct tps62360_chip *tps = rdev_get_drvdata(dev); + int ret; + bool found = false; + int new_vset_id = tps->curr_vset_id; + + /* + * If gpios are available to select the VSET register then least + * recently used register for new configuration. + */ + if (tps->valid_gpios) + found = find_voltage_set_register(tps, selector, &new_vset_id); + + if (!found) { + ret = regmap_update_bits(tps->regmap, REG_VSET0 + new_vset_id, + tps->voltage_reg_mask, selector); + if (ret < 0) { + dev_err(tps->dev, + "%s(): register %d update failed with err %d\n", + __func__, REG_VSET0 + new_vset_id, ret); + return ret; + } + tps->curr_vset_id = new_vset_id; + tps->curr_vset_vsel[new_vset_id] = selector; + } + + /* Select proper VSET register vio gpios */ + if (tps->valid_gpios) { + gpio_set_value_cansleep(tps->vsel0_gpio, new_vset_id & 0x1); + gpio_set_value_cansleep(tps->vsel1_gpio, + (new_vset_id >> 1) & 0x1); + } + return 0; +} + +static int tps62360_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct tps62360_chip *tps = rdev_get_drvdata(rdev); + int i; + int val; + int ret; + + /* Enable force PWM mode in FAST mode only. */ + switch (mode) { + case REGULATOR_MODE_FAST: + val = FORCE_PWM_ENABLE; + break; + + case REGULATOR_MODE_NORMAL: + val = 0; + break; + + default: + return -EINVAL; + } + + if (!tps->valid_gpios) { + ret = regmap_update_bits(tps->regmap, + REG_VSET0 + tps->curr_vset_id, FORCE_PWM_ENABLE, val); + if (ret < 0) + dev_err(tps->dev, + "%s(): register %d update failed with err %d\n", + __func__, REG_VSET0 + tps->curr_vset_id, ret); + return ret; + } + + /* If gpios are valid then all register set need to be control */ + for (i = 0; i < 4; ++i) { + ret = regmap_update_bits(tps->regmap, + REG_VSET0 + i, FORCE_PWM_ENABLE, val); + if (ret < 0) { + dev_err(tps->dev, + "%s(): register %d update failed with err %d\n", + __func__, REG_VSET0 + i, ret); + return ret; + } + } + return ret; +} + +static unsigned int tps62360_get_mode(struct regulator_dev *rdev) +{ + struct tps62360_chip *tps = rdev_get_drvdata(rdev); + unsigned int data; + int ret; + + ret = regmap_read(tps->regmap, REG_VSET0 + tps->curr_vset_id, &data); + if (ret < 0) { + dev_err(tps->dev, "%s(): register %d read failed with err %d\n", + __func__, REG_VSET0 + tps->curr_vset_id, ret); + return ret; + } + return (data & FORCE_PWM_ENABLE) ? + REGULATOR_MODE_FAST : REGULATOR_MODE_NORMAL; +} + +static struct regulator_ops tps62360_dcdc_ops = { + .get_voltage_sel = tps62360_dcdc_get_voltage_sel, + .set_voltage_sel = tps62360_dcdc_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_mode = tps62360_set_mode, + .get_mode = tps62360_get_mode, +}; + +static int tps62360_init_dcdc(struct tps62360_chip *tps, + struct tps62360_regulator_platform_data *pdata) +{ + int ret; + unsigned int ramp_ctrl; + + /* Initialize internal pull up/down control */ + if (tps->en_internal_pulldn) + ret = regmap_write(tps->regmap, REG_CONTROL, 0xE0); + else + ret = regmap_write(tps->regmap, REG_CONTROL, 0x0); + if (ret < 0) { + dev_err(tps->dev, + "%s(): register %d write failed with err %d\n", + __func__, REG_CONTROL, ret); + return ret; + } + + /* Reset output discharge path to reduce power consumption */ + ret = regmap_update_bits(tps->regmap, REG_RAMPCTRL, BIT(2), 0); + if (ret < 0) { + dev_err(tps->dev, + "%s(): register %d update failed with err %d\n", + __func__, REG_RAMPCTRL, ret); + return ret; + } + + /* Get ramp value from ramp control register */ + ret = regmap_read(tps->regmap, REG_RAMPCTRL, &ramp_ctrl); + if (ret < 0) { + dev_err(tps->dev, + "%s(): register %d read failed with err %d\n", + __func__, REG_RAMPCTRL, ret); + return ret; + } + ramp_ctrl = (ramp_ctrl >> 5) & 0x7; + + /* ramp mV/us = 32/(2^ramp_ctrl) */ + tps->desc.ramp_delay = DIV_ROUND_UP(32000, BIT(ramp_ctrl)); + return ret; +} + +static const struct regmap_config tps62360_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = REG_CHIPID, + .cache_type = REGCACHE_RBTREE, +}; + +static struct tps62360_regulator_platform_data * + of_get_tps62360_platform_data(struct device *dev, + const struct regulator_desc *desc) +{ + struct tps62360_regulator_platform_data *pdata; + struct device_node *np = dev->of_node; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + pdata->reg_init_data = of_get_regulator_init_data(dev, dev->of_node, + desc); + if (!pdata->reg_init_data) { + dev_err(dev, "Not able to get OF regulator init data\n"); + return NULL; + } + + pdata->vsel0_gpio = of_get_named_gpio(np, "vsel0-gpio", 0); + pdata->vsel1_gpio = of_get_named_gpio(np, "vsel1-gpio", 0); + + if (of_find_property(np, "ti,vsel0-state-high", NULL)) + pdata->vsel0_def_state = 1; + + if (of_find_property(np, "ti,vsel1-state-high", NULL)) + pdata->vsel1_def_state = 1; + + if (of_find_property(np, "ti,enable-pull-down", NULL)) + pdata->en_internal_pulldn = true; + + if (of_find_property(np, "ti,enable-vout-discharge", NULL)) + pdata->en_discharge = true; + + return pdata; +} + +#if defined(CONFIG_OF) +static const struct of_device_id tps62360_of_match[] = { + { .compatible = "ti,tps62360", .data = (void *)TPS62360}, + { .compatible = "ti,tps62361", .data = (void *)TPS62361}, + { .compatible = "ti,tps62362", .data = (void *)TPS62362}, + { .compatible = "ti,tps62363", .data = (void *)TPS62363}, + {}, +}; +MODULE_DEVICE_TABLE(of, tps62360_of_match); +#endif + +static int tps62360_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regulator_config config = { }; + struct tps62360_regulator_platform_data *pdata; + struct regulator_dev *rdev; + struct tps62360_chip *tps; + int ret; + int i; + int chip_id; + + pdata = dev_get_platdata(&client->dev); + + tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); + if (!tps) + return -ENOMEM; + + tps->desc.name = client->name; + tps->desc.id = 0; + tps->desc.ops = &tps62360_dcdc_ops; + tps->desc.type = REGULATOR_VOLTAGE; + tps->desc.owner = THIS_MODULE; + tps->desc.uV_step = 10000; + + if (client->dev.of_node) { + const struct of_device_id *match; + match = of_match_device(of_match_ptr(tps62360_of_match), + &client->dev); + if (!match) { + dev_err(&client->dev, "Error: No device match found\n"); + return -ENODEV; + } + chip_id = (int)(long)match->data; + if (!pdata) + pdata = of_get_tps62360_platform_data(&client->dev, + &tps->desc); + } else if (id) { + chip_id = id->driver_data; + } else { + dev_err(&client->dev, "No device tree match or id table match found\n"); + return -ENODEV; + } + + if (!pdata) { + dev_err(&client->dev, "%s(): Platform data not found\n", + __func__); + return -EIO; + } + + tps->en_discharge = pdata->en_discharge; + tps->en_internal_pulldn = pdata->en_internal_pulldn; + tps->vsel0_gpio = pdata->vsel0_gpio; + tps->vsel1_gpio = pdata->vsel1_gpio; + tps->dev = &client->dev; + + switch (chip_id) { + case TPS62360: + case TPS62362: + tps->desc.min_uV = TPS62360_BASE_VOLTAGE; + tps->voltage_reg_mask = 0x3F; + tps->desc.n_voltages = TPS62360_N_VOLTAGES; + break; + case TPS62361: + case TPS62363: + tps->desc.min_uV = TPS62361_BASE_VOLTAGE; + tps->voltage_reg_mask = 0x7F; + tps->desc.n_voltages = TPS62361_N_VOLTAGES; + break; + default: + return -ENODEV; + } + + tps->regmap = devm_regmap_init_i2c(client, &tps62360_regmap_config); + if (IS_ERR(tps->regmap)) { + ret = PTR_ERR(tps->regmap); + dev_err(&client->dev, + "%s(): regmap allocation failed with err %d\n", + __func__, ret); + return ret; + } + i2c_set_clientdata(client, tps); + + tps->curr_vset_id = (pdata->vsel1_def_state & 1) * 2 + + (pdata->vsel0_def_state & 1); + tps->lru_index[0] = tps->curr_vset_id; + tps->valid_gpios = false; + + if (gpio_is_valid(tps->vsel0_gpio) && gpio_is_valid(tps->vsel1_gpio)) { + int gpio_flags; + gpio_flags = (pdata->vsel0_def_state) ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + ret = devm_gpio_request_one(&client->dev, tps->vsel0_gpio, + gpio_flags, "tps62360-vsel0"); + if (ret) { + dev_err(&client->dev, + "%s(): Could not obtain vsel0 GPIO %d: %d\n", + __func__, tps->vsel0_gpio, ret); + return ret; + } + + gpio_flags = (pdata->vsel1_def_state) ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + ret = devm_gpio_request_one(&client->dev, tps->vsel1_gpio, + gpio_flags, "tps62360-vsel1"); + if (ret) { + dev_err(&client->dev, + "%s(): Could not obtain vsel1 GPIO %d: %d\n", + __func__, tps->vsel1_gpio, ret); + return ret; + } + tps->valid_gpios = true; + + /* + * Initialize the lru index with vset_reg id + * The index 0 will be most recently used and + * set with the tps->curr_vset_id */ + for (i = 0; i < 4; ++i) + tps->lru_index[i] = i; + tps->lru_index[0] = tps->curr_vset_id; + tps->lru_index[tps->curr_vset_id] = 0; + } + + ret = tps62360_init_dcdc(tps, pdata); + if (ret < 0) { + dev_err(tps->dev, "%s(): Init failed with err = %d\n", + __func__, ret); + return ret; + } + + config.dev = &client->dev; + config.init_data = pdata->reg_init_data; + config.driver_data = tps; + config.of_node = client->dev.of_node; + + /* Register the regulators */ + rdev = devm_regulator_register(&client->dev, &tps->desc, &config); + if (IS_ERR(rdev)) { + dev_err(tps->dev, + "%s(): regulator register failed with err %s\n", + __func__, id->name); + return PTR_ERR(rdev); + } + + tps->rdev = rdev; + return 0; +} + +static void tps62360_shutdown(struct i2c_client *client) +{ + struct tps62360_chip *tps = i2c_get_clientdata(client); + int st; + + if (!tps->en_discharge) + return; + + /* Configure the output discharge path */ + st = regmap_update_bits(tps->regmap, REG_RAMPCTRL, BIT(2), BIT(2)); + if (st < 0) + dev_err(tps->dev, + "%s(): register %d update failed with err %d\n", + __func__, REG_RAMPCTRL, st); +} + +static const struct i2c_device_id tps62360_id[] = { + {.name = "tps62360", .driver_data = TPS62360}, + {.name = "tps62361", .driver_data = TPS62361}, + {.name = "tps62362", .driver_data = TPS62362}, + {.name = "tps62363", .driver_data = TPS62363}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, tps62360_id); + +static struct i2c_driver tps62360_i2c_driver = { + .driver = { + .name = "tps62360", + .of_match_table = of_match_ptr(tps62360_of_match), + }, + .probe = tps62360_probe, + .shutdown = tps62360_shutdown, + .id_table = tps62360_id, +}; + +static int __init tps62360_init(void) +{ + return i2c_add_driver(&tps62360_i2c_driver); +} +subsys_initcall(tps62360_init); + +static void __exit tps62360_cleanup(void) +{ + i2c_del_driver(&tps62360_i2c_driver); +} +module_exit(tps62360_cleanup); + +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_DESCRIPTION("TPS6236x voltage regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/tps65023-regulator.c b/drivers/regulator/tps65023-regulator.c new file mode 100644 index 000000000..5ca6d2130 --- /dev/null +++ b/drivers/regulator/tps65023-regulator.c @@ -0,0 +1,365 @@ +/* + * tps65023-regulator.c + * + * Supports TPS65023 Regulator + * + * 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/i2c.h> +#include <linux/slab.h> +#include <linux/regmap.h> + +/* Register definitions */ +#define TPS65023_REG_VERSION 0 +#define TPS65023_REG_PGOODZ 1 +#define TPS65023_REG_MASK 2 +#define TPS65023_REG_REG_CTRL 3 +#define TPS65023_REG_CON_CTRL 4 +#define TPS65023_REG_CON_CTRL2 5 +#define TPS65023_REG_DEF_CORE 6 +#define TPS65023_REG_DEFSLEW 7 +#define TPS65023_REG_LDO_CTRL 8 + +/* PGOODZ bitfields */ +#define TPS65023_PGOODZ_PWRFAILZ BIT(7) +#define TPS65023_PGOODZ_LOWBATTZ BIT(6) +#define TPS65023_PGOODZ_VDCDC1 BIT(5) +#define TPS65023_PGOODZ_VDCDC2 BIT(4) +#define TPS65023_PGOODZ_VDCDC3 BIT(3) +#define TPS65023_PGOODZ_LDO2 BIT(2) +#define TPS65023_PGOODZ_LDO1 BIT(1) + +/* MASK bitfields */ +#define TPS65023_MASK_PWRFAILZ BIT(7) +#define TPS65023_MASK_LOWBATTZ BIT(6) +#define TPS65023_MASK_VDCDC1 BIT(5) +#define TPS65023_MASK_VDCDC2 BIT(4) +#define TPS65023_MASK_VDCDC3 BIT(3) +#define TPS65023_MASK_LDO2 BIT(2) +#define TPS65023_MASK_LDO1 BIT(1) + +/* REG_CTRL bitfields */ +#define TPS65023_REG_CTRL_VDCDC1_EN BIT(5) +#define TPS65023_REG_CTRL_VDCDC2_EN BIT(4) +#define TPS65023_REG_CTRL_VDCDC3_EN BIT(3) +#define TPS65023_REG_CTRL_LDO2_EN BIT(2) +#define TPS65023_REG_CTRL_LDO1_EN BIT(1) + +/* REG_CTRL2 bitfields */ +#define TPS65023_REG_CTRL2_GO BIT(7) +#define TPS65023_REG_CTRL2_CORE_ADJ BIT(6) +#define TPS65023_REG_CTRL2_DCDC2 BIT(2) +#define TPS65023_REG_CTRL2_DCDC1 BIT(1) +#define TPS65023_REG_CTRL2_DCDC3 BIT(0) + +/* Number of step-down converters available */ +#define TPS65023_NUM_DCDC 3 +/* Number of LDO voltage regulators available */ +#define TPS65023_NUM_LDO 2 +/* Number of total regulators available */ +#define TPS65023_NUM_REGULATOR (TPS65023_NUM_DCDC + TPS65023_NUM_LDO) + +/* DCDCs */ +#define TPS65023_DCDC_1 0 +#define TPS65023_DCDC_2 1 +#define TPS65023_DCDC_3 2 +/* LDOs */ +#define TPS65023_LDO_1 3 +#define TPS65023_LDO_2 4 + +#define TPS65023_MAX_REG_ID TPS65023_LDO_2 + +#define TPS65023_REGULATOR_DCDC(_num, _t, _em) \ + { \ + .name = "VDCDC"#_num, \ + .of_match = of_match_ptr("VDCDC"#_num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = TPS65023_DCDC_##_num, \ + .n_voltages = ARRAY_SIZE(_t), \ + .ops = &tps65023_dcdc_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .volt_table = _t, \ + .vsel_reg = TPS65023_REG_DEF_CORE, \ + .vsel_mask = ARRAY_SIZE(_t) - 1, \ + .enable_mask = _em, \ + .enable_reg = TPS65023_REG_REG_CTRL, \ + .apply_reg = TPS65023_REG_CON_CTRL2, \ + .apply_bit = TPS65023_REG_CTRL2_GO, \ + } \ + +#define TPS65023_REGULATOR_LDO(_num, _t, _vm) \ + { \ + .name = "LDO"#_num, \ + .of_match = of_match_ptr("LDO"#_num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = TPS65023_LDO_##_num, \ + .n_voltages = ARRAY_SIZE(_t), \ + .ops = &tps65023_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .volt_table = _t, \ + .vsel_reg = TPS65023_REG_LDO_CTRL, \ + .vsel_mask = _vm, \ + .enable_mask = 1 << (_num), \ + .enable_reg = TPS65023_REG_REG_CTRL, \ + } \ + +/* Supported voltage values for regulators */ +static const unsigned int VCORE_VSEL_table[] = { + 800000, 825000, 850000, 875000, + 900000, 925000, 950000, 975000, + 1000000, 1025000, 1050000, 1075000, + 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, + 1300000, 1325000, 1350000, 1375000, + 1400000, 1425000, 1450000, 1475000, + 1500000, 1525000, 1550000, 1600000, +}; + +static const unsigned int DCDC_FIXED_3300000_VSEL_table[] = { + 3300000, +}; + +static const unsigned int DCDC_FIXED_1800000_VSEL_table[] = { + 1800000, +}; + +/* Supported voltage values for LDO regulators for tps65020 */ +static const unsigned int TPS65020_LDO_VSEL_table[] = { + 1000000, 1050000, 1100000, 1300000, + 1800000, 2500000, 3000000, 3300000, +}; + +/* Supported voltage values for LDO regulators + * for tps65021 and tps65023 */ +static const unsigned int TPS65023_LDO1_VSEL_table[] = { + 1000000, 1100000, 1300000, 1800000, + 2200000, 2600000, 2800000, 3150000, +}; + +static const unsigned int TPS65023_LDO2_VSEL_table[] = { + 1050000, 1200000, 1300000, 1800000, + 2500000, 2800000, 3000000, 3300000, +}; + +/* PMIC details */ +struct tps_pmic { + struct regulator_dev *rdev[TPS65023_NUM_REGULATOR]; + const struct tps_driver_data *driver_data; + struct regmap *regmap; +}; + +/* Struct passed as driver data */ +struct tps_driver_data { + const struct regulator_desc *desc; + u8 core_regulator; +}; + +static int tps65023_dcdc_get_voltage_sel(struct regulator_dev *dev) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int dcdc = rdev_get_id(dev); + + if (dcdc < TPS65023_DCDC_1 || dcdc > TPS65023_DCDC_3) + return -EINVAL; + + if (dcdc != tps->driver_data->core_regulator) + return 0; + + return regulator_get_voltage_sel_regmap(dev); +} + +static int tps65023_dcdc_set_voltage_sel(struct regulator_dev *dev, + unsigned selector) +{ + struct tps_pmic *tps = rdev_get_drvdata(dev); + int dcdc = rdev_get_id(dev); + + if (dcdc != tps->driver_data->core_regulator) + return -EINVAL; + + return regulator_set_voltage_sel_regmap(dev, selector); +} + +/* Operations permitted on VDCDCx */ +static const struct regulator_ops tps65023_dcdc_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = tps65023_dcdc_get_voltage_sel, + .set_voltage_sel = tps65023_dcdc_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, +}; + +/* Operations permitted on LDOx */ +static const struct regulator_ops tps65023_ldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, +}; + +static const struct regmap_config tps65023_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct regulator_desc tps65020_regulators[] = { + TPS65023_REGULATOR_DCDC(1, DCDC_FIXED_3300000_VSEL_table, 0x20), + TPS65023_REGULATOR_DCDC(2, DCDC_FIXED_1800000_VSEL_table, 0x10), + TPS65023_REGULATOR_DCDC(3, VCORE_VSEL_table, 0x08), + TPS65023_REGULATOR_LDO(1, TPS65020_LDO_VSEL_table, 0x07), + TPS65023_REGULATOR_LDO(2, TPS65020_LDO_VSEL_table, 0x70), +}; + +static const struct regulator_desc tps65021_regulators[] = { + TPS65023_REGULATOR_DCDC(1, DCDC_FIXED_3300000_VSEL_table, 0x20), + TPS65023_REGULATOR_DCDC(2, DCDC_FIXED_1800000_VSEL_table, 0x10), + TPS65023_REGULATOR_DCDC(3, VCORE_VSEL_table, 0x08), + TPS65023_REGULATOR_LDO(1, TPS65023_LDO1_VSEL_table, 0x07), + TPS65023_REGULATOR_LDO(2, TPS65023_LDO2_VSEL_table, 0x70), +}; + +static const struct regulator_desc tps65023_regulators[] = { + TPS65023_REGULATOR_DCDC(1, VCORE_VSEL_table, 0x20), + TPS65023_REGULATOR_DCDC(2, DCDC_FIXED_3300000_VSEL_table, 0x10), + TPS65023_REGULATOR_DCDC(3, DCDC_FIXED_1800000_VSEL_table, 0x08), + TPS65023_REGULATOR_LDO(1, TPS65023_LDO1_VSEL_table, 0x07), + TPS65023_REGULATOR_LDO(2, TPS65023_LDO2_VSEL_table, 0x70), +}; + +static struct tps_driver_data tps65020_drv_data = { + .desc = tps65020_regulators, + .core_regulator = TPS65023_DCDC_3, +}; + +static struct tps_driver_data tps65021_drv_data = { + .desc = tps65021_regulators, + .core_regulator = TPS65023_DCDC_3, +}; + +static struct tps_driver_data tps65023_drv_data = { + .desc = tps65023_regulators, + .core_regulator = TPS65023_DCDC_1, +}; + +static int tps_65023_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regulator_init_data *init_data = dev_get_platdata(&client->dev); + struct regulator_config config = { }; + struct tps_pmic *tps; + int i; + int error; + + tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); + if (!tps) + return -ENOMEM; + + tps->driver_data = (struct tps_driver_data *)id->driver_data; + + tps->regmap = devm_regmap_init_i2c(client, &tps65023_regmap_config); + if (IS_ERR(tps->regmap)) { + error = PTR_ERR(tps->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + error); + return error; + } + + /* common for all regulators */ + config.dev = &client->dev; + config.driver_data = tps; + config.regmap = tps->regmap; + + for (i = 0; i < TPS65023_NUM_REGULATOR; i++) { + if (init_data) + config.init_data = &init_data[i]; + + /* Register the regulators */ + tps->rdev[i] = devm_regulator_register(&client->dev, + &tps->driver_data->desc[i], &config); + if (IS_ERR(tps->rdev[i])) { + dev_err(&client->dev, "failed to register %s\n", + id->name); + return PTR_ERR(tps->rdev[i]); + } + } + + i2c_set_clientdata(client, tps); + + /* Enable setting output voltage by I2C */ + regmap_update_bits(tps->regmap, TPS65023_REG_CON_CTRL2, + TPS65023_REG_CTRL2_CORE_ADJ, 0); + + return 0; +} + +static const struct of_device_id tps65023_of_match[] = { + { .compatible = "ti,tps65020", .data = &tps65020_drv_data}, + { .compatible = "ti,tps65021", .data = &tps65021_drv_data}, + { .compatible = "ti,tps65023", .data = &tps65023_drv_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, tps65023_of_match); + +static const struct i2c_device_id tps_65023_id[] = { + { + .name = "tps65023", + .driver_data = (kernel_ulong_t)&tps65023_drv_data + }, { + .name = "tps65021", + .driver_data = (kernel_ulong_t)&tps65021_drv_data + }, { + .name = "tps65020", + .driver_data = (kernel_ulong_t)&tps65020_drv_data + }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, tps_65023_id); + +static struct i2c_driver tps_65023_i2c_driver = { + .driver = { + .name = "tps65023", + .of_match_table = of_match_ptr(tps65023_of_match), + }, + .probe = tps_65023_probe, + .id_table = tps_65023_id, +}; + +static int __init tps_65023_init(void) +{ + return i2c_add_driver(&tps_65023_i2c_driver); +} +subsys_initcall(tps_65023_init); + +static void __exit tps_65023_cleanup(void) +{ + i2c_del_driver(&tps_65023_i2c_driver); +} +module_exit(tps_65023_cleanup); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("TPS65023 voltage regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/tps6507x-regulator.c b/drivers/regulator/tps6507x-regulator.c new file mode 100644 index 000000000..c179a3a22 --- /dev/null +++ b/drivers/regulator/tps6507x-regulator.c @@ -0,0 +1,541 @@ +/* + * 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/regulator/tps6507x.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/mfd/tps6507x.h> +#include <linux/regulator/of_regulator.h> + +/* DCDC's */ +#define TPS6507X_DCDC_1 0 +#define TPS6507X_DCDC_2 1 +#define TPS6507X_DCDC_3 2 +/* LDOs */ +#define TPS6507X_LDO_1 3 +#define TPS6507X_LDO_2 4 + +#define TPS6507X_MAX_REG_ID TPS6507X_LDO_2 + +/* Number of step-down converters available */ +#define TPS6507X_NUM_DCDC 3 +/* Number of LDO voltage regulators available */ +#define TPS6507X_NUM_LDO 2 +/* Number of total regulators available */ +#define TPS6507X_NUM_REGULATOR (TPS6507X_NUM_DCDC + TPS6507X_NUM_LDO) + +/* Supported voltage values for regulators (in microVolts) */ +static const unsigned int VDCDCx_VSEL_table[] = { + 725000, 750000, 775000, 800000, + 825000, 850000, 875000, 900000, + 925000, 950000, 975000, 1000000, + 1025000, 1050000, 1075000, 1100000, + 1125000, 1150000, 1175000, 1200000, + 1225000, 1250000, 1275000, 1300000, + 1325000, 1350000, 1375000, 1400000, + 1425000, 1450000, 1475000, 1500000, + 1550000, 1600000, 1650000, 1700000, + 1750000, 1800000, 1850000, 1900000, + 1950000, 2000000, 2050000, 2100000, + 2150000, 2200000, 2250000, 2300000, + 2350000, 2400000, 2450000, 2500000, + 2550000, 2600000, 2650000, 2700000, + 2750000, 2800000, 2850000, 2900000, + 3000000, 3100000, 3200000, 3300000, +}; + +static const unsigned int LDO1_VSEL_table[] = { + 1000000, 1100000, 1200000, 1250000, + 1300000, 1350000, 1400000, 1500000, + 1600000, 1800000, 2500000, 2750000, + 2800000, 3000000, 3100000, 3300000, +}; + +/* The voltage mapping table for LDO2 is the same as VDCDCx */ +#define LDO2_VSEL_table VDCDCx_VSEL_table + +struct tps_info { + const char *name; + u8 table_len; + const unsigned int *table; + + /* Does DCDC high or the low register defines output voltage? */ + bool defdcdc_default; +}; + +static struct tps_info tps6507x_pmic_regs[] = { + { + .name = "VDCDC1", + .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), + .table = VDCDCx_VSEL_table, + }, + { + .name = "VDCDC2", + .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), + .table = VDCDCx_VSEL_table, + }, + { + .name = "VDCDC3", + .table_len = ARRAY_SIZE(VDCDCx_VSEL_table), + .table = VDCDCx_VSEL_table, + }, + { + .name = "LDO1", + .table_len = ARRAY_SIZE(LDO1_VSEL_table), + .table = LDO1_VSEL_table, + }, + { + .name = "LDO2", + .table_len = ARRAY_SIZE(LDO2_VSEL_table), + .table = LDO2_VSEL_table, + }, +}; + +struct tps6507x_pmic { + struct regulator_desc desc[TPS6507X_NUM_REGULATOR]; + struct tps6507x_dev *mfd; + struct regulator_dev *rdev[TPS6507X_NUM_REGULATOR]; + struct tps_info *info[TPS6507X_NUM_REGULATOR]; + struct mutex io_lock; +}; +static inline int tps6507x_pmic_read(struct tps6507x_pmic *tps, u8 reg) +{ + u8 val; + int err; + + err = tps->mfd->read_dev(tps->mfd, reg, 1, &val); + + if (err) + return err; + + return val; +} + +static inline int tps6507x_pmic_write(struct tps6507x_pmic *tps, u8 reg, u8 val) +{ + return tps->mfd->write_dev(tps->mfd, reg, 1, &val); +} + +static int tps6507x_pmic_set_bits(struct tps6507x_pmic *tps, u8 reg, u8 mask) +{ + int err, data; + + mutex_lock(&tps->io_lock); + + data = tps6507x_pmic_read(tps, reg); + if (data < 0) { + dev_err(tps->mfd->dev, "Read from reg 0x%x failed\n", reg); + err = data; + goto out; + } + + data |= mask; + err = tps6507x_pmic_write(tps, reg, data); + if (err) + dev_err(tps->mfd->dev, "Write for reg 0x%x failed\n", reg); + +out: + mutex_unlock(&tps->io_lock); + return err; +} + +static int tps6507x_pmic_clear_bits(struct tps6507x_pmic *tps, u8 reg, u8 mask) +{ + int err, data; + + mutex_lock(&tps->io_lock); + + data = tps6507x_pmic_read(tps, reg); + if (data < 0) { + dev_err(tps->mfd->dev, "Read from reg 0x%x failed\n", reg); + err = data; + goto out; + } + + data &= ~mask; + err = tps6507x_pmic_write(tps, reg, data); + if (err) + dev_err(tps->mfd->dev, "Write for reg 0x%x failed\n", reg); + +out: + mutex_unlock(&tps->io_lock); + return err; +} + +static int tps6507x_pmic_reg_read(struct tps6507x_pmic *tps, u8 reg) +{ + int data; + + mutex_lock(&tps->io_lock); + + data = tps6507x_pmic_read(tps, reg); + if (data < 0) + dev_err(tps->mfd->dev, "Read from reg 0x%x failed\n", reg); + + mutex_unlock(&tps->io_lock); + return data; +} + +static int tps6507x_pmic_reg_write(struct tps6507x_pmic *tps, u8 reg, u8 val) +{ + int err; + + mutex_lock(&tps->io_lock); + + err = tps6507x_pmic_write(tps, reg, val); + if (err < 0) + dev_err(tps->mfd->dev, "Write for reg 0x%x failed\n", reg); + + mutex_unlock(&tps->io_lock); + return err; +} + +static int tps6507x_pmic_is_enabled(struct regulator_dev *dev) +{ + struct tps6507x_pmic *tps = rdev_get_drvdata(dev); + int data, rid = rdev_get_id(dev); + u8 shift; + + if (rid < TPS6507X_DCDC_1 || rid > TPS6507X_LDO_2) + return -EINVAL; + + shift = TPS6507X_MAX_REG_ID - rid; + data = tps6507x_pmic_reg_read(tps, TPS6507X_REG_CON_CTRL1); + + if (data < 0) + return data; + else + return (data & 1<<shift) ? 1 : 0; +} + +static int tps6507x_pmic_enable(struct regulator_dev *dev) +{ + struct tps6507x_pmic *tps = rdev_get_drvdata(dev); + int rid = rdev_get_id(dev); + u8 shift; + + if (rid < TPS6507X_DCDC_1 || rid > TPS6507X_LDO_2) + return -EINVAL; + + shift = TPS6507X_MAX_REG_ID - rid; + return tps6507x_pmic_set_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift); +} + +static int tps6507x_pmic_disable(struct regulator_dev *dev) +{ + struct tps6507x_pmic *tps = rdev_get_drvdata(dev); + int rid = rdev_get_id(dev); + u8 shift; + + if (rid < TPS6507X_DCDC_1 || rid > TPS6507X_LDO_2) + return -EINVAL; + + shift = TPS6507X_MAX_REG_ID - rid; + return tps6507x_pmic_clear_bits(tps, TPS6507X_REG_CON_CTRL1, + 1 << shift); +} + +static int tps6507x_pmic_get_voltage_sel(struct regulator_dev *dev) +{ + struct tps6507x_pmic *tps = rdev_get_drvdata(dev); + int data, rid = rdev_get_id(dev); + u8 reg, mask; + + switch (rid) { + case TPS6507X_DCDC_1: + reg = TPS6507X_REG_DEFDCDC1; + mask = TPS6507X_DEFDCDCX_DCDC_MASK; + break; + case TPS6507X_DCDC_2: + if (tps->info[rid]->defdcdc_default) + reg = TPS6507X_REG_DEFDCDC2_HIGH; + else + reg = TPS6507X_REG_DEFDCDC2_LOW; + mask = TPS6507X_DEFDCDCX_DCDC_MASK; + break; + case TPS6507X_DCDC_3: + if (tps->info[rid]->defdcdc_default) + reg = TPS6507X_REG_DEFDCDC3_HIGH; + else + reg = TPS6507X_REG_DEFDCDC3_LOW; + mask = TPS6507X_DEFDCDCX_DCDC_MASK; + break; + case TPS6507X_LDO_1: + reg = TPS6507X_REG_LDO_CTRL1; + mask = TPS6507X_REG_LDO_CTRL1_LDO1_MASK; + break; + case TPS6507X_LDO_2: + reg = TPS6507X_REG_DEFLDO2; + mask = TPS6507X_REG_DEFLDO2_LDO2_MASK; + break; + default: + return -EINVAL; + } + + data = tps6507x_pmic_reg_read(tps, reg); + if (data < 0) + return data; + + data &= mask; + return data; +} + +static int tps6507x_pmic_set_voltage_sel(struct regulator_dev *dev, + unsigned selector) +{ + struct tps6507x_pmic *tps = rdev_get_drvdata(dev); + int data, rid = rdev_get_id(dev); + u8 reg, mask; + + switch (rid) { + case TPS6507X_DCDC_1: + reg = TPS6507X_REG_DEFDCDC1; + mask = TPS6507X_DEFDCDCX_DCDC_MASK; + break; + case TPS6507X_DCDC_2: + if (tps->info[rid]->defdcdc_default) + reg = TPS6507X_REG_DEFDCDC2_HIGH; + else + reg = TPS6507X_REG_DEFDCDC2_LOW; + mask = TPS6507X_DEFDCDCX_DCDC_MASK; + break; + case TPS6507X_DCDC_3: + if (tps->info[rid]->defdcdc_default) + reg = TPS6507X_REG_DEFDCDC3_HIGH; + else + reg = TPS6507X_REG_DEFDCDC3_LOW; + mask = TPS6507X_DEFDCDCX_DCDC_MASK; + break; + case TPS6507X_LDO_1: + reg = TPS6507X_REG_LDO_CTRL1; + mask = TPS6507X_REG_LDO_CTRL1_LDO1_MASK; + break; + case TPS6507X_LDO_2: + reg = TPS6507X_REG_DEFLDO2; + mask = TPS6507X_REG_DEFLDO2_LDO2_MASK; + break; + default: + return -EINVAL; + } + + data = tps6507x_pmic_reg_read(tps, reg); + if (data < 0) + return data; + + data &= ~mask; + data |= selector; + + return tps6507x_pmic_reg_write(tps, reg, data); +} + +static struct regulator_ops tps6507x_pmic_ops = { + .is_enabled = tps6507x_pmic_is_enabled, + .enable = tps6507x_pmic_enable, + .disable = tps6507x_pmic_disable, + .get_voltage_sel = tps6507x_pmic_get_voltage_sel, + .set_voltage_sel = tps6507x_pmic_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, +}; + +static struct of_regulator_match tps6507x_matches[] = { + { .name = "VDCDC1"}, + { .name = "VDCDC2"}, + { .name = "VDCDC3"}, + { .name = "LDO1"}, + { .name = "LDO2"}, +}; + +static struct tps6507x_board *tps6507x_parse_dt_reg_data( + struct platform_device *pdev, + struct of_regulator_match **tps6507x_reg_matches) +{ + struct tps6507x_board *tps_board; + struct device_node *np = pdev->dev.parent->of_node; + struct device_node *regulators; + struct of_regulator_match *matches; + struct regulator_init_data *reg_data; + int idx = 0, count, ret; + + tps_board = devm_kzalloc(&pdev->dev, sizeof(*tps_board), + GFP_KERNEL); + if (!tps_board) + return NULL; + + regulators = of_get_child_by_name(np, "regulators"); + if (!regulators) { + dev_err(&pdev->dev, "regulator node not found\n"); + return NULL; + } + + count = ARRAY_SIZE(tps6507x_matches); + matches = tps6507x_matches; + + ret = of_regulator_match(&pdev->dev, regulators, matches, count); + of_node_put(regulators); + if (ret < 0) { + dev_err(&pdev->dev, "Error parsing regulator init data: %d\n", + ret); + return NULL; + } + + *tps6507x_reg_matches = matches; + + reg_data = devm_kzalloc(&pdev->dev, (sizeof(struct regulator_init_data) + * TPS6507X_NUM_REGULATOR), GFP_KERNEL); + if (!reg_data) + return NULL; + + tps_board->tps6507x_pmic_init_data = reg_data; + + for (idx = 0; idx < count; idx++) { + if (!matches[idx].init_data || !matches[idx].of_node) + continue; + + memcpy(®_data[idx], matches[idx].init_data, + sizeof(struct regulator_init_data)); + + } + + return tps_board; +} + +static int tps6507x_pmic_probe(struct platform_device *pdev) +{ + struct tps6507x_dev *tps6507x_dev = dev_get_drvdata(pdev->dev.parent); + struct tps_info *info = &tps6507x_pmic_regs[0]; + struct regulator_config config = { }; + struct regulator_init_data *init_data; + struct regulator_dev *rdev; + struct tps6507x_pmic *tps; + struct tps6507x_board *tps_board; + struct of_regulator_match *tps6507x_reg_matches = NULL; + int i; + int error; + unsigned int prop; + + /** + * tps_board points to pmic related constants + * coming from the board-evm file. + */ + + tps_board = dev_get_platdata(tps6507x_dev->dev); + if (IS_ENABLED(CONFIG_OF) && !tps_board && + tps6507x_dev->dev->of_node) + tps_board = tps6507x_parse_dt_reg_data(pdev, + &tps6507x_reg_matches); + if (!tps_board) + return -EINVAL; + + /** + * init_data points to array of regulator_init structures + * coming from the board-evm file. + */ + init_data = tps_board->tps6507x_pmic_init_data; + if (!init_data) + return -EINVAL; + + tps = devm_kzalloc(&pdev->dev, sizeof(*tps), GFP_KERNEL); + if (!tps) + return -ENOMEM; + + mutex_init(&tps->io_lock); + + /* common for all regulators */ + tps->mfd = tps6507x_dev; + + for (i = 0; i < TPS6507X_NUM_REGULATOR; i++, info++, init_data++) { + /* Register the regulators */ + tps->info[i] = info; + if (init_data->driver_data) { + struct tps6507x_reg_platform_data *data = + init_data->driver_data; + tps->info[i]->defdcdc_default = data->defdcdc_default; + } + + tps->desc[i].name = info->name; + tps->desc[i].id = i; + tps->desc[i].n_voltages = info->table_len; + tps->desc[i].volt_table = info->table; + tps->desc[i].ops = &tps6507x_pmic_ops; + tps->desc[i].type = REGULATOR_VOLTAGE; + tps->desc[i].owner = THIS_MODULE; + + config.dev = tps6507x_dev->dev; + config.init_data = init_data; + config.driver_data = tps; + + if (tps6507x_reg_matches) { + error = of_property_read_u32( + tps6507x_reg_matches[i].of_node, + "ti,defdcdc_default", &prop); + + if (!error) + tps->info[i]->defdcdc_default = prop; + + config.of_node = tps6507x_reg_matches[i].of_node; + } + + rdev = devm_regulator_register(&pdev->dev, &tps->desc[i], + &config); + if (IS_ERR(rdev)) { + dev_err(tps6507x_dev->dev, + "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + + /* Save regulator for cleanup */ + tps->rdev[i] = rdev; + } + + tps6507x_dev->pmic = tps; + platform_set_drvdata(pdev, tps6507x_dev); + + return 0; +} + +static struct platform_driver tps6507x_pmic_driver = { + .driver = { + .name = "tps6507x-pmic", + }, + .probe = tps6507x_pmic_probe, +}; + +static int __init tps6507x_pmic_init(void) +{ + return platform_driver_register(&tps6507x_pmic_driver); +} +subsys_initcall(tps6507x_pmic_init); + +static void __exit tps6507x_pmic_cleanup(void) +{ + platform_driver_unregister(&tps6507x_pmic_driver); +} +module_exit(tps6507x_pmic_cleanup); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("TPS6507x voltage regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps6507x-pmic"); diff --git a/drivers/regulator/tps65086-regulator.c b/drivers/regulator/tps65086-regulator.c new file mode 100644 index 000000000..5a5e9b5bf --- /dev/null +++ b/drivers/regulator/tps65086-regulator.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ + * + * Author: Andrew F. Davis <afd@ti.com> + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether expressed or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License version 2 for more details. + * + * Based on the TPS65912 driver + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> + +#include <linux/mfd/tps65086.h> + +enum tps65086_regulators { BUCK1, BUCK2, BUCK3, BUCK4, BUCK5, BUCK6, LDOA1, + LDOA2, LDOA3, SWA1, SWB1, SWB2, VTT }; + +#define TPS65086_REGULATOR(_name, _of, _id, _nv, _vr, _vm, _er, _em, _lr, _dr, _dm) \ + [_id] = { \ + .desc = { \ + .name = _name, \ + .of_match = of_match_ptr(_of), \ + .regulators_node = "regulators", \ + .of_parse_cb = tps65086_of_parse_cb, \ + .id = _id, \ + .ops = ®_ops, \ + .n_voltages = _nv, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .vsel_reg = _vr, \ + .vsel_mask = _vm, \ + .enable_reg = _er, \ + .enable_mask = _em, \ + .volt_table = NULL, \ + .linear_ranges = _lr, \ + .n_linear_ranges = ARRAY_SIZE(_lr), \ + }, \ + .decay_reg = _dr, \ + .decay_mask = _dm, \ + } + +#define TPS65086_SWITCH(_name, _of, _id, _er, _em) \ + [_id] = { \ + .desc = { \ + .name = _name, \ + .of_match = of_match_ptr(_of), \ + .regulators_node = "regulators", \ + .of_parse_cb = tps65086_of_parse_cb, \ + .id = _id, \ + .ops = &switch_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .enable_reg = _er, \ + .enable_mask = _em, \ + }, \ + } + +struct tps65086_regulator { + struct regulator_desc desc; + unsigned int decay_reg; + unsigned int decay_mask; +}; + +static const struct regulator_linear_range tps65086_10mv_ranges[] = { + REGULATOR_LINEAR_RANGE(0, 0x0, 0x0, 0), + REGULATOR_LINEAR_RANGE(410000, 0x1, 0x7F, 10000), +}; + +static const struct regulator_linear_range tps65086_buck126_25mv_ranges[] = { + REGULATOR_LINEAR_RANGE(0, 0x0, 0x0, 0), + REGULATOR_LINEAR_RANGE(1000000, 0x1, 0x18, 0), + REGULATOR_LINEAR_RANGE(1025000, 0x19, 0x7F, 25000), +}; + +static const struct regulator_linear_range tps65086_buck345_25mv_ranges[] = { + REGULATOR_LINEAR_RANGE(0, 0x0, 0x0, 0), + REGULATOR_LINEAR_RANGE(425000, 0x1, 0x7F, 25000), +}; + +static const struct regulator_linear_range tps65086_ldoa1_ranges[] = { + REGULATOR_LINEAR_RANGE(1350000, 0x0, 0x0, 0), + REGULATOR_LINEAR_RANGE(1500000, 0x1, 0x7, 100000), + REGULATOR_LINEAR_RANGE(2300000, 0x8, 0xB, 100000), + REGULATOR_LINEAR_RANGE(2850000, 0xC, 0xD, 150000), + REGULATOR_LINEAR_RANGE(3300000, 0xE, 0xE, 0), +}; + +static const struct regulator_linear_range tps65086_ldoa23_ranges[] = { + REGULATOR_LINEAR_RANGE(700000, 0x0, 0xD, 50000), + REGULATOR_LINEAR_RANGE(1400000, 0xE, 0xF, 100000), +}; + +/* Operations permitted on regulators */ +static struct regulator_ops reg_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .map_voltage = regulator_map_voltage_linear_range, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, +}; + +/* Operations permitted on load switches */ +static struct regulator_ops switch_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static int tps65086_of_parse_cb(struct device_node *dev, + const struct regulator_desc *desc, + struct regulator_config *config); + +static struct tps65086_regulator regulators[] = { + TPS65086_REGULATOR("BUCK1", "buck1", BUCK1, 0x80, TPS65086_BUCK1CTRL, + BUCK_VID_MASK, TPS65086_BUCK123CTRL, BIT(0), + tps65086_10mv_ranges, TPS65086_BUCK1CTRL, + BIT(0)), + TPS65086_REGULATOR("BUCK2", "buck2", BUCK2, 0x80, TPS65086_BUCK2CTRL, + BUCK_VID_MASK, TPS65086_BUCK123CTRL, BIT(1), + tps65086_10mv_ranges, TPS65086_BUCK2CTRL, + BIT(0)), + TPS65086_REGULATOR("BUCK3", "buck3", BUCK3, 0x80, TPS65086_BUCK3VID, + BUCK_VID_MASK, TPS65086_BUCK123CTRL, BIT(2), + tps65086_10mv_ranges, TPS65086_BUCK3DECAY, + BIT(0)), + TPS65086_REGULATOR("BUCK4", "buck4", BUCK4, 0x80, TPS65086_BUCK4VID, + BUCK_VID_MASK, TPS65086_BUCK4CTRL, BIT(0), + tps65086_10mv_ranges, TPS65086_BUCK4VID, + BIT(0)), + TPS65086_REGULATOR("BUCK5", "buck5", BUCK5, 0x80, TPS65086_BUCK5VID, + BUCK_VID_MASK, TPS65086_BUCK5CTRL, BIT(0), + tps65086_10mv_ranges, TPS65086_BUCK5CTRL, + BIT(0)), + TPS65086_REGULATOR("BUCK6", "buck6", BUCK6, 0x80, TPS65086_BUCK6VID, + BUCK_VID_MASK, TPS65086_BUCK6CTRL, BIT(0), + tps65086_10mv_ranges, TPS65086_BUCK6CTRL, + BIT(0)), + TPS65086_REGULATOR("LDOA1", "ldoa1", LDOA1, 0xF, TPS65086_LDOA1CTRL, + VDOA1_VID_MASK, TPS65086_LDOA1CTRL, BIT(0), + tps65086_ldoa1_ranges, 0, 0), + TPS65086_REGULATOR("LDOA2", "ldoa2", LDOA2, 0x10, TPS65086_LDOA2VID, + VDOA23_VID_MASK, TPS65086_LDOA2CTRL, BIT(0), + tps65086_ldoa23_ranges, 0, 0), + TPS65086_REGULATOR("LDOA3", "ldoa3", LDOA3, 0x10, TPS65086_LDOA3VID, + VDOA23_VID_MASK, TPS65086_LDOA3CTRL, BIT(0), + tps65086_ldoa23_ranges, 0, 0), + TPS65086_SWITCH("SWA1", "swa1", SWA1, TPS65086_SWVTT_EN, BIT(5)), + TPS65086_SWITCH("SWB1", "swb1", SWB1, TPS65086_SWVTT_EN, BIT(6)), + TPS65086_SWITCH("SWB2", "swb2", SWB2, TPS65086_SWVTT_EN, BIT(7)), + TPS65086_SWITCH("VTT", "vtt", VTT, TPS65086_SWVTT_EN, BIT(4)), +}; + +static int tps65086_of_parse_cb(struct device_node *node, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + int ret; + + /* Check for 25mV step mode */ + if (of_property_read_bool(node, "ti,regulator-step-size-25mv")) { + switch (desc->id) { + case BUCK1: + case BUCK2: + case BUCK6: + regulators[desc->id].desc.linear_ranges = + tps65086_buck126_25mv_ranges; + regulators[desc->id].desc.n_linear_ranges = + ARRAY_SIZE(tps65086_buck126_25mv_ranges); + break; + case BUCK3: + case BUCK4: + case BUCK5: + regulators[desc->id].desc.linear_ranges = + tps65086_buck345_25mv_ranges; + regulators[desc->id].desc.n_linear_ranges = + ARRAY_SIZE(tps65086_buck345_25mv_ranges); + break; + default: + dev_warn(config->dev, "25mV step mode only valid for BUCK regulators\n"); + } + } + + /* Check for decay mode */ + if (desc->id <= BUCK6 && of_property_read_bool(node, "ti,regulator-decay")) { + ret = regmap_write_bits(config->regmap, + regulators[desc->id].decay_reg, + regulators[desc->id].decay_mask, + regulators[desc->id].decay_mask); + if (ret) { + dev_err(config->dev, "Error setting decay\n"); + return ret; + } + } + + return 0; +} + +static int tps65086_regulator_probe(struct platform_device *pdev) +{ + struct tps65086 *tps = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = { }; + struct regulator_dev *rdev; + int i; + + platform_set_drvdata(pdev, tps); + + config.dev = &pdev->dev; + config.dev->of_node = tps->dev->of_node; + config.driver_data = tps; + config.regmap = tps->regmap; + + for (i = 0; i < ARRAY_SIZE(regulators); i++) { + rdev = devm_regulator_register(&pdev->dev, ®ulators[i].desc, + &config); + if (IS_ERR(rdev)) { + dev_err(tps->dev, "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct platform_device_id tps65086_regulator_id_table[] = { + { "tps65086-regulator", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, tps65086_regulator_id_table); + +static struct platform_driver tps65086_regulator_driver = { + .driver = { + .name = "tps65086-regulator", + }, + .probe = tps65086_regulator_probe, + .id_table = tps65086_regulator_id_table, +}; +module_platform_driver(tps65086_regulator_driver); + +MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>"); +MODULE_DESCRIPTION("TPS65086 Regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/tps65090-regulator.c b/drivers/regulator/tps65090-regulator.c new file mode 100644 index 000000000..edaef9e4d --- /dev/null +++ b/drivers/regulator/tps65090-regulator.c @@ -0,0 +1,531 @@ +/* + * Regulator driver for tps65090 power management chip. + * + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/gpio/consumer.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/tps65090.h> + +#define MAX_CTRL_READ_TRIES 5 +#define MAX_FET_ENABLE_TRIES 1000 + +#define CTRL_EN_BIT 0 /* Regulator enable bit, active high */ +#define CTRL_WT_BIT 2 /* Regulator wait time 0 bit */ +#define CTRL_PG_BIT 4 /* Regulator power good bit, 1=good */ +#define CTRL_TO_BIT 7 /* Regulator timeout bit, 1=wait */ + +#define MAX_OVERCURRENT_WAIT 3 /* Overcurrent wait must be <= this */ + +/** + * struct tps65090_regulator - Per-regulator data for a tps65090 regulator + * + * @dev: Pointer to our device. + * @desc: The struct regulator_desc for the regulator. + * @rdev: The struct regulator_dev for the regulator. + * @overcurrent_wait_valid: True if overcurrent_wait is valid. + * @overcurrent_wait: For FETs, the value to put in the WTFET bitfield. + */ + +struct tps65090_regulator { + struct device *dev; + struct regulator_desc *desc; + struct regulator_dev *rdev; + bool overcurrent_wait_valid; + int overcurrent_wait; +}; + +static struct regulator_ops tps65090_ext_control_ops = { +}; + +/** + * tps65090_reg_set_overcurrent_wait - Setup overcurrent wait + * + * This will set the overcurrent wait time based on what's in the regulator + * info. + * + * @ri: Overall regulator data + * @rdev: Regulator device + * + * Return: 0 if no error, non-zero if there was an error writing the register. + */ +static int tps65090_reg_set_overcurrent_wait(struct tps65090_regulator *ri, + struct regulator_dev *rdev) +{ + int ret; + + ret = regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + MAX_OVERCURRENT_WAIT << CTRL_WT_BIT, + ri->overcurrent_wait << CTRL_WT_BIT); + if (ret) { + dev_err(&rdev->dev, "Error updating overcurrent wait %#x\n", + rdev->desc->enable_reg); + } + + return ret; +} + +/** + * tps65090_try_enable_fet - Try to enable a FET + * + * @rdev: Regulator device + * + * Return: 0 if ok, -ENOTRECOVERABLE if the FET power good bit did not get + * set, or some other -ve value if another error occurred (e.g. i2c error) + */ +static int tps65090_try_enable_fet(struct regulator_dev *rdev) +{ + unsigned int control; + int ret, i; + + ret = regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, + rdev->desc->enable_mask); + if (ret < 0) { + dev_err(&rdev->dev, "Error in updating reg %#x\n", + rdev->desc->enable_reg); + return ret; + } + + for (i = 0; i < MAX_CTRL_READ_TRIES; i++) { + ret = regmap_read(rdev->regmap, rdev->desc->enable_reg, + &control); + if (ret < 0) + return ret; + + if (!(control & BIT(CTRL_TO_BIT))) + break; + + usleep_range(1000, 1500); + } + if (!(control & BIT(CTRL_PG_BIT))) + return -ENOTRECOVERABLE; + + return 0; +} + +/** + * tps65090_fet_enable - Enable a FET, trying a few times if it fails + * + * Some versions of the tps65090 have issues when turning on the FETs. + * This function goes through several steps to ensure the best chance of the + * FET going on. Specifically: + * - We'll make sure that we bump the "overcurrent wait" to the maximum, which + * increases the chances that we'll turn on properly. + * - We'll retry turning the FET on multiple times (turning off in between). + * + * @rdev: Regulator device + * + * Return: 0 if ok, non-zero if it fails. + */ +static int tps65090_fet_enable(struct regulator_dev *rdev) +{ + int ret, tries; + + /* + * Try enabling multiple times until we succeed since sometimes the + * first try times out. + */ + tries = 0; + while (true) { + ret = tps65090_try_enable_fet(rdev); + if (!ret) + break; + if (ret != -ENOTRECOVERABLE || tries == MAX_FET_ENABLE_TRIES) + goto err; + + /* Try turning the FET off (and then on again) */ + ret = regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, 0); + if (ret) + goto err; + + tries++; + } + + if (tries) + dev_warn(&rdev->dev, "reg %#x enable ok after %d tries\n", + rdev->desc->enable_reg, tries); + + return 0; +err: + dev_warn(&rdev->dev, "reg %#x enable failed\n", rdev->desc->enable_reg); + WARN_ON(1); + + return ret; +} + +static struct regulator_ops tps65090_reg_control_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static struct regulator_ops tps65090_fet_control_ops = { + .enable = tps65090_fet_enable, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static struct regulator_ops tps65090_ldo_ops = { +}; + +#define tps65090_REG_DESC(_id, _sname, _en_reg, _en_bits, _nvolt, _volt, _ops) \ +{ \ + .name = "TPS65090_RAILS"#_id, \ + .supply_name = _sname, \ + .id = TPS65090_REGULATOR_##_id, \ + .n_voltages = _nvolt, \ + .ops = &_ops, \ + .fixed_uV = _volt, \ + .enable_reg = _en_reg, \ + .enable_val = _en_bits, \ + .enable_mask = _en_bits, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ +} + +#define tps65090_REG_FIXEDV(_id, _sname, en_reg, _en_bits, _volt, _ops) \ + tps65090_REG_DESC(_id, _sname, en_reg, _en_bits, 1, _volt, _ops) + +#define tps65090_REG_SWITCH(_id, _sname, en_reg, _en_bits, _ops) \ + tps65090_REG_DESC(_id, _sname, en_reg, _en_bits, 0, 0, _ops) + +static struct regulator_desc tps65090_regulator_desc[] = { + tps65090_REG_FIXEDV(DCDC1, "vsys1", 0x0C, BIT(CTRL_EN_BIT), 5000000, + tps65090_reg_control_ops), + tps65090_REG_FIXEDV(DCDC2, "vsys2", 0x0D, BIT(CTRL_EN_BIT), 3300000, + tps65090_reg_control_ops), + tps65090_REG_SWITCH(DCDC3, "vsys3", 0x0E, BIT(CTRL_EN_BIT), + tps65090_reg_control_ops), + + tps65090_REG_SWITCH(FET1, "infet1", 0x0F, + BIT(CTRL_EN_BIT) | BIT(CTRL_PG_BIT), + tps65090_fet_control_ops), + tps65090_REG_SWITCH(FET2, "infet2", 0x10, + BIT(CTRL_EN_BIT) | BIT(CTRL_PG_BIT), + tps65090_fet_control_ops), + tps65090_REG_SWITCH(FET3, "infet3", 0x11, + BIT(CTRL_EN_BIT) | BIT(CTRL_PG_BIT), + tps65090_fet_control_ops), + tps65090_REG_SWITCH(FET4, "infet4", 0x12, + BIT(CTRL_EN_BIT) | BIT(CTRL_PG_BIT), + tps65090_fet_control_ops), + tps65090_REG_SWITCH(FET5, "infet5", 0x13, + BIT(CTRL_EN_BIT) | BIT(CTRL_PG_BIT), + tps65090_fet_control_ops), + tps65090_REG_SWITCH(FET6, "infet6", 0x14, + BIT(CTRL_EN_BIT) | BIT(CTRL_PG_BIT), + tps65090_fet_control_ops), + tps65090_REG_SWITCH(FET7, "infet7", 0x15, + BIT(CTRL_EN_BIT) | BIT(CTRL_PG_BIT), + tps65090_fet_control_ops), + + tps65090_REG_FIXEDV(LDO1, "vsys-l1", 0, 0, 5000000, + tps65090_ldo_ops), + tps65090_REG_FIXEDV(LDO2, "vsys-l2", 0, 0, 3300000, + tps65090_ldo_ops), +}; + +static inline bool is_dcdc(int id) +{ + switch (id) { + case TPS65090_REGULATOR_DCDC1: + case TPS65090_REGULATOR_DCDC2: + case TPS65090_REGULATOR_DCDC3: + return true; + default: + return false; + } +} + +static int tps65090_config_ext_control( + struct tps65090_regulator *ri, bool enable) +{ + int ret; + struct device *parent = ri->dev->parent; + unsigned int reg_en_reg = ri->desc->enable_reg; + + if (enable) + ret = tps65090_set_bits(parent, reg_en_reg, 1); + else + ret = tps65090_clr_bits(parent, reg_en_reg, 1); + if (ret < 0) + dev_err(ri->dev, "Error in updating reg 0x%x\n", reg_en_reg); + return ret; +} + +static int tps65090_regulator_disable_ext_control( + struct tps65090_regulator *ri, + struct tps65090_regulator_plat_data *tps_pdata) +{ + int ret = 0; + struct device *parent = ri->dev->parent; + unsigned int reg_en_reg = ri->desc->enable_reg; + + /* + * First enable output for internal control if require. + * And then disable external control. + */ + if (tps_pdata->reg_init_data->constraints.always_on || + tps_pdata->reg_init_data->constraints.boot_on) { + ret = tps65090_set_bits(parent, reg_en_reg, 0); + if (ret < 0) { + dev_err(ri->dev, "Error in set reg 0x%x\n", reg_en_reg); + return ret; + } + } + return tps65090_config_ext_control(ri, false); +} + +#ifdef CONFIG_OF +static struct of_regulator_match tps65090_matches[] = { + { .name = "dcdc1", }, + { .name = "dcdc2", }, + { .name = "dcdc3", }, + { .name = "fet1", }, + { .name = "fet2", }, + { .name = "fet3", }, + { .name = "fet4", }, + { .name = "fet5", }, + { .name = "fet6", }, + { .name = "fet7", }, + { .name = "ldo1", }, + { .name = "ldo2", }, +}; + +static struct tps65090_platform_data *tps65090_parse_dt_reg_data( + struct platform_device *pdev, + struct of_regulator_match **tps65090_reg_matches) +{ + struct tps65090_platform_data *tps65090_pdata; + struct device_node *np = pdev->dev.parent->of_node; + struct device_node *regulators; + int idx = 0, ret; + struct tps65090_regulator_plat_data *reg_pdata; + + tps65090_pdata = devm_kzalloc(&pdev->dev, sizeof(*tps65090_pdata), + GFP_KERNEL); + if (!tps65090_pdata) + return ERR_PTR(-ENOMEM); + + reg_pdata = devm_kcalloc(&pdev->dev, + TPS65090_REGULATOR_MAX, sizeof(*reg_pdata), + GFP_KERNEL); + if (!reg_pdata) + return ERR_PTR(-ENOMEM); + + regulators = of_get_child_by_name(np, "regulators"); + if (!regulators) { + dev_err(&pdev->dev, "regulator node not found\n"); + return ERR_PTR(-ENODEV); + } + + ret = of_regulator_match(&pdev->dev, regulators, tps65090_matches, + ARRAY_SIZE(tps65090_matches)); + of_node_put(regulators); + if (ret < 0) { + dev_err(&pdev->dev, + "Error parsing regulator init data: %d\n", ret); + return ERR_PTR(ret); + } + + *tps65090_reg_matches = tps65090_matches; + for (idx = 0; idx < ARRAY_SIZE(tps65090_matches); idx++) { + struct regulator_init_data *ri_data; + struct tps65090_regulator_plat_data *rpdata; + + rpdata = ®_pdata[idx]; + ri_data = tps65090_matches[idx].init_data; + if (!ri_data || !tps65090_matches[idx].of_node) + continue; + + rpdata->reg_init_data = ri_data; + rpdata->enable_ext_control = of_property_read_bool( + tps65090_matches[idx].of_node, + "ti,enable-ext-control"); + if (rpdata->enable_ext_control) { + enum gpiod_flags gflags; + + if (ri_data->constraints.always_on || + ri_data->constraints.boot_on) + gflags = GPIOD_OUT_HIGH; + else + gflags = GPIOD_OUT_LOW; + + rpdata->gpiod = devm_gpiod_get_from_of_node(&pdev->dev, + tps65090_matches[idx].of_node, + "dcdc-ext-control-gpios", 0, + gflags, + "tps65090"); + if (IS_ERR(rpdata->gpiod)) + return ERR_CAST(rpdata->gpiod); + if (!rpdata->gpiod) + dev_err(&pdev->dev, + "could not find DCDC external control GPIO\n"); + } + + if (of_property_read_u32(tps65090_matches[idx].of_node, + "ti,overcurrent-wait", + &rpdata->overcurrent_wait) == 0) + rpdata->overcurrent_wait_valid = true; + + tps65090_pdata->reg_pdata[idx] = rpdata; + } + return tps65090_pdata; +} +#else +static inline struct tps65090_platform_data *tps65090_parse_dt_reg_data( + struct platform_device *pdev, + struct of_regulator_match **tps65090_reg_matches) +{ + *tps65090_reg_matches = NULL; + return NULL; +} +#endif + +static int tps65090_regulator_probe(struct platform_device *pdev) +{ + struct tps65090 *tps65090_mfd = dev_get_drvdata(pdev->dev.parent); + struct tps65090_regulator *ri = NULL; + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct tps65090_regulator_plat_data *tps_pdata; + struct tps65090_regulator *pmic; + struct tps65090_platform_data *tps65090_pdata; + struct of_regulator_match *tps65090_reg_matches = NULL; + int num; + int ret; + + dev_dbg(&pdev->dev, "Probing regulator\n"); + + tps65090_pdata = dev_get_platdata(pdev->dev.parent); + if (!tps65090_pdata && tps65090_mfd->dev->of_node) + tps65090_pdata = tps65090_parse_dt_reg_data(pdev, + &tps65090_reg_matches); + if (IS_ERR_OR_NULL(tps65090_pdata)) { + dev_err(&pdev->dev, "Platform data missing\n"); + return tps65090_pdata ? PTR_ERR(tps65090_pdata) : -EINVAL; + } + + pmic = devm_kcalloc(&pdev->dev, + TPS65090_REGULATOR_MAX, sizeof(*pmic), + GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + for (num = 0; num < TPS65090_REGULATOR_MAX; num++) { + tps_pdata = tps65090_pdata->reg_pdata[num]; + + ri = &pmic[num]; + ri->dev = &pdev->dev; + ri->desc = &tps65090_regulator_desc[num]; + if (tps_pdata) { + ri->overcurrent_wait_valid = + tps_pdata->overcurrent_wait_valid; + ri->overcurrent_wait = tps_pdata->overcurrent_wait; + } + + /* + * TPS5090 DCDC support the control from external digital input. + * Configure it as per platform data. + */ + if (tps_pdata && is_dcdc(num) && tps_pdata->reg_init_data) { + if (tps_pdata->enable_ext_control) { + config.ena_gpiod = tps_pdata->gpiod; + ri->desc->ops = &tps65090_ext_control_ops; + } else { + ret = tps65090_regulator_disable_ext_control( + ri, tps_pdata); + if (ret < 0) { + dev_err(&pdev->dev, + "failed disable ext control\n"); + return ret; + } + } + } + + config.dev = pdev->dev.parent; + config.driver_data = ri; + config.regmap = tps65090_mfd->rmap; + if (tps_pdata) + config.init_data = tps_pdata->reg_init_data; + else + config.init_data = NULL; + if (tps65090_reg_matches) + config.of_node = tps65090_reg_matches[num].of_node; + else + config.of_node = NULL; + + rdev = devm_regulator_register(&pdev->dev, ri->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + ri->desc->name); + return PTR_ERR(rdev); + } + ri->rdev = rdev; + + if (ri->overcurrent_wait_valid) { + ret = tps65090_reg_set_overcurrent_wait(ri, rdev); + if (ret < 0) + return ret; + } + + /* Enable external control if it is require */ + if (tps_pdata && is_dcdc(num) && tps_pdata->reg_init_data && + tps_pdata->enable_ext_control) { + ret = tps65090_config_ext_control(ri, true); + if (ret < 0) + return ret; + } + } + + platform_set_drvdata(pdev, pmic); + return 0; +} + +static struct platform_driver tps65090_regulator_driver = { + .driver = { + .name = "tps65090-pmic", + }, + .probe = tps65090_regulator_probe, +}; + +static int __init tps65090_regulator_init(void) +{ + return platform_driver_register(&tps65090_regulator_driver); +} +subsys_initcall(tps65090_regulator_init); + +static void __exit tps65090_regulator_exit(void) +{ + platform_driver_unregister(&tps65090_regulator_driver); +} +module_exit(tps65090_regulator_exit); + +MODULE_DESCRIPTION("tps65090 regulator driver"); +MODULE_AUTHOR("Venu Byravarasu <vbyravarasu@nvidia.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps65090-pmic"); diff --git a/drivers/regulator/tps65132-regulator.c b/drivers/regulator/tps65132-regulator.c new file mode 100644 index 000000000..73978dd44 --- /dev/null +++ b/drivers/regulator/tps65132-regulator.c @@ -0,0 +1,284 @@ +/* + * TI TPS65132 Regulator driver + * + * Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved. + * + * Author: Venkat Reddy Talla <vreddytalla@nvidia.com> + * Laxman Dewangan <ldewangan@nvidia.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; either version 2 of the + * License, or (at your option) any later version. + * + * 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/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> + +#define TPS65132_REG_VPOS 0x00 +#define TPS65132_REG_VNEG 0x01 +#define TPS65132_REG_APPS_DISP_DISN 0x03 +#define TPS65132_REG_CONTROL 0x0FF + +#define TPS65132_VOUT_MASK 0x1F +#define TPS65132_VOUT_N_VOLTAGE 0x15 +#define TPS65132_VOUT_VMIN 4000000 +#define TPS65132_VOUT_VMAX 6000000 +#define TPS65132_VOUT_STEP 100000 + +#define TPS65132_REG_APPS_DIS_VPOS BIT(0) +#define TPS65132_REG_APPS_DIS_VNEG BIT(1) + +#define TPS65132_REGULATOR_ID_VPOS 0 +#define TPS65132_REGULATOR_ID_VNEG 1 +#define TPS65132_MAX_REGULATORS 2 + +#define TPS65132_ACT_DIS_TIME_SLACK 1000 + +struct tps65132_reg_pdata { + struct gpio_desc *en_gpiod; + struct gpio_desc *act_dis_gpiod; + unsigned int act_dis_time_us; + int ena_gpio_state; +}; + +struct tps65132_regulator { + struct device *dev; + struct regmap *rmap; + struct regulator_desc *rdesc[TPS65132_MAX_REGULATORS]; + struct tps65132_reg_pdata reg_pdata[TPS65132_MAX_REGULATORS]; + struct regulator_dev *rdev[TPS65132_MAX_REGULATORS]; +}; + +static int tps65132_regulator_enable(struct regulator_dev *rdev) +{ + struct tps65132_regulator *tps = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + struct tps65132_reg_pdata *rpdata = &tps->reg_pdata[id]; + int ret; + + if (!IS_ERR(rpdata->en_gpiod)) { + gpiod_set_value_cansleep(rpdata->en_gpiod, 1); + rpdata->ena_gpio_state = 1; + } + + /* Hardware automatically enable discharge bit in enable */ + if (rdev->constraints->active_discharge == + REGULATOR_ACTIVE_DISCHARGE_DISABLE) { + ret = regulator_set_active_discharge_regmap(rdev, false); + if (ret < 0) { + dev_err(tps->dev, "Failed to disable active discharge: %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int tps65132_regulator_disable(struct regulator_dev *rdev) +{ + struct tps65132_regulator *tps = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + struct tps65132_reg_pdata *rpdata = &tps->reg_pdata[id]; + + if (!IS_ERR(rpdata->en_gpiod)) { + gpiod_set_value_cansleep(rpdata->en_gpiod, 0); + rpdata->ena_gpio_state = 0; + } + + if (!IS_ERR(rpdata->act_dis_gpiod)) { + gpiod_set_value_cansleep(rpdata->act_dis_gpiod, 1); + usleep_range(rpdata->act_dis_time_us, rpdata->act_dis_time_us + + TPS65132_ACT_DIS_TIME_SLACK); + gpiod_set_value_cansleep(rpdata->act_dis_gpiod, 0); + } + + return 0; +} + +static int tps65132_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct tps65132_regulator *tps = rdev_get_drvdata(rdev); + int id = rdev_get_id(rdev); + struct tps65132_reg_pdata *rpdata = &tps->reg_pdata[id]; + + if (!IS_ERR(rpdata->en_gpiod)) + return rpdata->ena_gpio_state; + + return 1; +} + +static struct regulator_ops tps65132_regulator_ops = { + .enable = tps65132_regulator_enable, + .disable = tps65132_regulator_disable, + .is_enabled = tps65132_regulator_is_enabled, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_active_discharge = regulator_set_active_discharge_regmap, +}; + +static int tps65132_of_parse_cb(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct tps65132_regulator *tps = config->driver_data; + struct tps65132_reg_pdata *rpdata = &tps->reg_pdata[desc->id]; + int ret; + + rpdata->en_gpiod = devm_fwnode_get_index_gpiod_from_child(tps->dev, + "enable", 0, &np->fwnode, 0, "enable"); + if (IS_ERR(rpdata->en_gpiod)) { + ret = PTR_ERR(rpdata->en_gpiod); + + /* Ignore the error other than probe defer */ + if (ret == -EPROBE_DEFER) + return ret; + return 0; + } + + rpdata->act_dis_gpiod = devm_fwnode_get_index_gpiod_from_child( + tps->dev, "active-discharge", 0, + &np->fwnode, 0, "active-discharge"); + if (IS_ERR(rpdata->act_dis_gpiod)) { + ret = PTR_ERR(rpdata->act_dis_gpiod); + + /* Ignore the error other than probe defer */ + if (ret == -EPROBE_DEFER) + return ret; + + return 0; + } + + ret = of_property_read_u32(np, "ti,active-discharge-time-us", + &rpdata->act_dis_time_us); + if (ret < 0) { + dev_err(tps->dev, "Failed to read active discharge time:%d\n", + ret); + return ret; + } + + return 0; +} + +#define TPS65132_REGULATOR_DESC(_id, _name) \ + [TPS65132_REGULATOR_ID_##_id] = { \ + .name = "tps65132-"#_name, \ + .supply_name = "vin", \ + .id = TPS65132_REGULATOR_ID_##_id, \ + .of_match = of_match_ptr(#_name), \ + .of_parse_cb = tps65132_of_parse_cb, \ + .ops = &tps65132_regulator_ops, \ + .n_voltages = TPS65132_VOUT_N_VOLTAGE, \ + .min_uV = TPS65132_VOUT_VMIN, \ + .uV_step = TPS65132_VOUT_STEP, \ + .enable_time = 500, \ + .vsel_mask = TPS65132_VOUT_MASK, \ + .vsel_reg = TPS65132_REG_##_id, \ + .active_discharge_off = 0, \ + .active_discharge_on = TPS65132_REG_APPS_DIS_##_id, \ + .active_discharge_mask = TPS65132_REG_APPS_DIS_##_id, \ + .active_discharge_reg = TPS65132_REG_APPS_DISP_DISN, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +static struct regulator_desc tps_regs_desc[TPS65132_MAX_REGULATORS] = { + TPS65132_REGULATOR_DESC(VPOS, outp), + TPS65132_REGULATOR_DESC(VNEG, outn), +}; + +static const struct regmap_range tps65132_no_reg_ranges[] = { + regmap_reg_range(TPS65132_REG_APPS_DISP_DISN + 1, + TPS65132_REG_CONTROL - 1), +}; + +static const struct regmap_access_table tps65132_no_reg_table = { + .no_ranges = tps65132_no_reg_ranges, + .n_no_ranges = ARRAY_SIZE(tps65132_no_reg_ranges), +}; + +static const struct regmap_config tps65132_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TPS65132_REG_CONTROL, + .cache_type = REGCACHE_NONE, + .rd_table = &tps65132_no_reg_table, + .wr_table = &tps65132_no_reg_table, +}; + +static int tps65132_probe(struct i2c_client *client, + const struct i2c_device_id *client_id) +{ + struct device *dev = &client->dev; + struct tps65132_regulator *tps; + struct regulator_config config = { }; + int id; + int ret; + + tps = devm_kzalloc(dev, sizeof(*tps), GFP_KERNEL); + if (!tps) + return -ENOMEM; + + tps->rmap = devm_regmap_init_i2c(client, &tps65132_regmap_config); + if (IS_ERR(tps->rmap)) { + ret = PTR_ERR(tps->rmap); + dev_err(dev, "regmap init failed: %d\n", ret); + return ret; + } + + i2c_set_clientdata(client, tps); + tps->dev = dev; + + for (id = 0; id < TPS65132_MAX_REGULATORS; ++id) { + tps->rdesc[id] = &tps_regs_desc[id]; + + config.regmap = tps->rmap; + config.dev = dev; + config.driver_data = tps; + + tps->rdev[id] = devm_regulator_register(dev, + tps->rdesc[id], &config); + if (IS_ERR(tps->rdev[id])) { + ret = PTR_ERR(tps->rdev[id]); + dev_err(dev, "regulator %s register failed: %d\n", + tps->rdesc[id]->name, ret); + return ret; + } + } + return 0; +} + +static const struct i2c_device_id tps65132_id[] = { + {.name = "tps65132",}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, tps65132_id); + +static struct i2c_driver tps65132_i2c_driver = { + .driver = { + .name = "tps65132", + }, + .probe = tps65132_probe, + .id_table = tps65132_id, +}; + +module_i2c_driver(tps65132_i2c_driver); + +MODULE_DESCRIPTION("tps65132 regulator driver"); +MODULE_AUTHOR("Venkat Reddy Talla <vreddytalla@nvidia.com>"); +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/tps65217-regulator.c b/drivers/regulator/tps65217-regulator.c new file mode 100644 index 000000000..d84fab616 --- /dev/null +++ b/drivers/regulator/tps65217-regulator.c @@ -0,0 +1,286 @@ +/* + * tps65217-regulator.c + * + * Regulator driver for TPS65217 PMIC + * + * Copyright (C) 2011 Texas Instruments 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/device.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> + +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/tps65217.h> + +#define TPS65217_REGULATOR(_name, _id, _of_match, _ops, _n, _vr, _vm, _em, \ + _t, _lr, _nlr, _sr, _sm) \ + { \ + .name = _name, \ + .id = _id, \ + .of_match = of_match_ptr(_of_match), \ + .regulators_node= of_match_ptr("regulators"), \ + .ops = &_ops, \ + .n_voltages = _n, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .vsel_reg = _vr, \ + .vsel_mask = _vm, \ + .enable_reg = TPS65217_REG_ENABLE, \ + .enable_mask = _em, \ + .volt_table = _t, \ + .linear_ranges = _lr, \ + .n_linear_ranges = _nlr, \ + .bypass_reg = _sr, \ + .bypass_mask = _sm, \ + } \ + +static const unsigned int LDO1_VSEL_table[] = { + 1000000, 1100000, 1200000, 1250000, + 1300000, 1350000, 1400000, 1500000, + 1600000, 1800000, 2500000, 2750000, + 2800000, 3000000, 3100000, 3300000, +}; + +static const struct regulator_linear_range tps65217_uv1_ranges[] = { + REGULATOR_LINEAR_RANGE(900000, 0, 24, 25000), + REGULATOR_LINEAR_RANGE(1550000, 25, 30, 50000), + REGULATOR_LINEAR_RANGE(1850000, 31, 52, 50000), + REGULATOR_LINEAR_RANGE(3000000, 53, 55, 100000), + REGULATOR_LINEAR_RANGE(3300000, 56, 62, 0), +}; + +static const struct regulator_linear_range tps65217_uv2_ranges[] = { + REGULATOR_LINEAR_RANGE(1500000, 0, 8, 50000), + REGULATOR_LINEAR_RANGE(2000000, 9, 13, 100000), + REGULATOR_LINEAR_RANGE(2450000, 14, 31, 50000), +}; + +static int tps65217_pmic_enable(struct regulator_dev *dev) +{ + struct tps65217 *tps = rdev_get_drvdata(dev); + int rid = rdev_get_id(dev); + + if (rid < TPS65217_DCDC_1 || rid > TPS65217_LDO_4) + return -EINVAL; + + /* Enable the regulator and password protection is level 1 */ + return tps65217_set_bits(tps, TPS65217_REG_ENABLE, + dev->desc->enable_mask, dev->desc->enable_mask, + TPS65217_PROTECT_L1); +} + +static int tps65217_pmic_disable(struct regulator_dev *dev) +{ + struct tps65217 *tps = rdev_get_drvdata(dev); + int rid = rdev_get_id(dev); + + if (rid < TPS65217_DCDC_1 || rid > TPS65217_LDO_4) + return -EINVAL; + + /* Disable the regulator and password protection is level 1 */ + return tps65217_clear_bits(tps, TPS65217_REG_ENABLE, + dev->desc->enable_mask, TPS65217_PROTECT_L1); +} + +static int tps65217_pmic_set_voltage_sel(struct regulator_dev *dev, + unsigned selector) +{ + int ret; + struct tps65217 *tps = rdev_get_drvdata(dev); + unsigned int rid = rdev_get_id(dev); + + /* Set the voltage based on vsel value and write protect level is 2 */ + ret = tps65217_set_bits(tps, dev->desc->vsel_reg, dev->desc->vsel_mask, + selector, TPS65217_PROTECT_L2); + + /* Set GO bit for DCDCx to initiate voltage transistion */ + switch (rid) { + case TPS65217_DCDC_1 ... TPS65217_DCDC_3: + ret = tps65217_set_bits(tps, TPS65217_REG_DEFSLEW, + TPS65217_DEFSLEW_GO, TPS65217_DEFSLEW_GO, + TPS65217_PROTECT_L2); + break; + } + + return ret; +} + +static int tps65217_pmic_set_suspend_enable(struct regulator_dev *dev) +{ + struct tps65217 *tps = rdev_get_drvdata(dev); + unsigned int rid = rdev_get_id(dev); + + if (rid < TPS65217_DCDC_1 || rid > TPS65217_LDO_4) + return -EINVAL; + + return tps65217_clear_bits(tps, dev->desc->bypass_reg, + dev->desc->bypass_mask, + TPS65217_PROTECT_L1); +} + +static int tps65217_pmic_set_suspend_disable(struct regulator_dev *dev) +{ + struct tps65217 *tps = rdev_get_drvdata(dev); + unsigned int rid = rdev_get_id(dev); + + if (rid < TPS65217_DCDC_1 || rid > TPS65217_LDO_4) + return -EINVAL; + + if (!tps->strobes[rid]) + return -EINVAL; + + return tps65217_set_bits(tps, dev->desc->bypass_reg, + dev->desc->bypass_mask, + tps->strobes[rid], TPS65217_PROTECT_L1); +} + +/* Operations permitted on DCDCx, LDO2, LDO3 and LDO4 */ +static struct regulator_ops tps65217_pmic_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = tps65217_pmic_enable, + .disable = tps65217_pmic_disable, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = tps65217_pmic_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_suspend_enable = tps65217_pmic_set_suspend_enable, + .set_suspend_disable = tps65217_pmic_set_suspend_disable, +}; + +/* Operations permitted on LDO1 */ +static struct regulator_ops tps65217_pmic_ldo1_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = tps65217_pmic_enable, + .disable = tps65217_pmic_disable, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = tps65217_pmic_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_suspend_enable = tps65217_pmic_set_suspend_enable, + .set_suspend_disable = tps65217_pmic_set_suspend_disable, +}; + +static const struct regulator_desc regulators[] = { + TPS65217_REGULATOR("DCDC1", TPS65217_DCDC_1, "dcdc1", + tps65217_pmic_ops, 64, TPS65217_REG_DEFDCDC1, + TPS65217_DEFDCDCX_DCDC_MASK, TPS65217_ENABLE_DC1_EN, + NULL, tps65217_uv1_ranges, + ARRAY_SIZE(tps65217_uv1_ranges), TPS65217_REG_SEQ1, + TPS65217_SEQ1_DC1_SEQ_MASK), + TPS65217_REGULATOR("DCDC2", TPS65217_DCDC_2, "dcdc2", + tps65217_pmic_ops, 64, TPS65217_REG_DEFDCDC2, + TPS65217_DEFDCDCX_DCDC_MASK, TPS65217_ENABLE_DC2_EN, + NULL, tps65217_uv1_ranges, + ARRAY_SIZE(tps65217_uv1_ranges), TPS65217_REG_SEQ1, + TPS65217_SEQ1_DC2_SEQ_MASK), + TPS65217_REGULATOR("DCDC3", TPS65217_DCDC_3, "dcdc3", + tps65217_pmic_ops, 64, TPS65217_REG_DEFDCDC3, + TPS65217_DEFDCDCX_DCDC_MASK, TPS65217_ENABLE_DC3_EN, + NULL, tps65217_uv1_ranges, + ARRAY_SIZE(tps65217_uv1_ranges), TPS65217_REG_SEQ2, + TPS65217_SEQ2_DC3_SEQ_MASK), + TPS65217_REGULATOR("LDO1", TPS65217_LDO_1, "ldo1", + tps65217_pmic_ldo1_ops, 16, TPS65217_REG_DEFLDO1, + TPS65217_DEFLDO1_LDO1_MASK, TPS65217_ENABLE_LDO1_EN, + LDO1_VSEL_table, NULL, 0, TPS65217_REG_SEQ2, + TPS65217_SEQ2_LDO1_SEQ_MASK), + TPS65217_REGULATOR("LDO2", TPS65217_LDO_2, "ldo2", tps65217_pmic_ops, + 64, TPS65217_REG_DEFLDO2, + TPS65217_DEFLDO2_LDO2_MASK, TPS65217_ENABLE_LDO2_EN, + NULL, tps65217_uv1_ranges, + ARRAY_SIZE(tps65217_uv1_ranges), TPS65217_REG_SEQ3, + TPS65217_SEQ3_LDO2_SEQ_MASK), + TPS65217_REGULATOR("LDO3", TPS65217_LDO_3, "ldo3", tps65217_pmic_ops, + 32, TPS65217_REG_DEFLS1, TPS65217_DEFLDO3_LDO3_MASK, + TPS65217_ENABLE_LS1_EN | TPS65217_DEFLDO3_LDO3_EN, + NULL, tps65217_uv2_ranges, + ARRAY_SIZE(tps65217_uv2_ranges), TPS65217_REG_SEQ3, + TPS65217_SEQ3_LDO3_SEQ_MASK), + TPS65217_REGULATOR("LDO4", TPS65217_LDO_4, "ldo4", tps65217_pmic_ops, + 32, TPS65217_REG_DEFLS2, TPS65217_DEFLDO4_LDO4_MASK, + TPS65217_ENABLE_LS2_EN | TPS65217_DEFLDO4_LDO4_EN, + NULL, tps65217_uv2_ranges, + ARRAY_SIZE(tps65217_uv2_ranges), TPS65217_REG_SEQ4, + TPS65217_SEQ4_LDO4_SEQ_MASK), +}; + +static int tps65217_regulator_probe(struct platform_device *pdev) +{ + struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent); + struct tps65217_board *pdata = dev_get_platdata(tps->dev); + struct regulator_dev *rdev; + struct regulator_config config = { }; + int i, ret; + unsigned int val; + + /* Allocate memory for strobes */ + tps->strobes = devm_kcalloc(&pdev->dev, + TPS65217_NUM_REGULATOR, sizeof(u8), + GFP_KERNEL); + if (!tps->strobes) + return -ENOMEM; + + platform_set_drvdata(pdev, tps); + + for (i = 0; i < TPS65217_NUM_REGULATOR; i++) { + /* Register the regulators */ + config.dev = tps->dev; + if (pdata) + config.init_data = pdata->tps65217_init_data[i]; + config.driver_data = tps; + config.regmap = tps->regmap; + + rdev = devm_regulator_register(&pdev->dev, ®ulators[i], + &config); + if (IS_ERR(rdev)) { + dev_err(tps->dev, "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + + /* Store default strobe info */ + ret = tps65217_reg_read(tps, regulators[i].bypass_reg, &val); + tps->strobes[i] = val & regulators[i].bypass_mask; + } + + return 0; +} + +static struct platform_driver tps65217_regulator_driver = { + .driver = { + .name = "tps65217-pmic", + }, + .probe = tps65217_regulator_probe, +}; + +static int __init tps65217_regulator_init(void) +{ + return platform_driver_register(&tps65217_regulator_driver); +} +subsys_initcall(tps65217_regulator_init); + +static void __exit tps65217_regulator_exit(void) +{ + platform_driver_unregister(&tps65217_regulator_driver); +} +module_exit(tps65217_regulator_exit); + +MODULE_AUTHOR("AnilKumar Ch <anilkumar@ti.com>"); +MODULE_DESCRIPTION("TPS65217 voltage regulator driver"); +MODULE_ALIAS("platform:tps65217-pmic"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/tps65218-regulator.c b/drivers/regulator/tps65218-regulator.c new file mode 100644 index 000000000..6209beee1 --- /dev/null +++ b/drivers/regulator/tps65218-regulator.c @@ -0,0 +1,371 @@ +/* + * tps65218-regulator.c + * + * Regulator driver for TPS65218 PMIC + * + * Copyright (C) 2014 Texas Instruments 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 version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether expressed or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License version 2 for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/tps65218.h> + +#define TPS65218_REGULATOR(_name, _of, _id, _type, _ops, _n, _vr, _vm, _er, \ + _em, _cr, _cm, _lr, _nlr, _delay, _fuv, _sr, _sm) \ + { \ + .name = _name, \ + .of_match = _of, \ + .id = _id, \ + .ops = &_ops, \ + .n_voltages = _n, \ + .type = _type, \ + .owner = THIS_MODULE, \ + .vsel_reg = _vr, \ + .vsel_mask = _vm, \ + .csel_reg = _cr, \ + .csel_mask = _cm, \ + .enable_reg = _er, \ + .enable_mask = _em, \ + .volt_table = NULL, \ + .linear_ranges = _lr, \ + .n_linear_ranges = _nlr, \ + .ramp_delay = _delay, \ + .fixed_uV = _fuv, \ + .bypass_reg = _sr, \ + .bypass_mask = _sm, \ + } \ + +static const struct regulator_linear_range dcdc1_dcdc2_ranges[] = { + REGULATOR_LINEAR_RANGE(850000, 0x0, 0x32, 10000), + REGULATOR_LINEAR_RANGE(1375000, 0x33, 0x3f, 25000), +}; + +static const struct regulator_linear_range ldo1_dcdc3_ranges[] = { + REGULATOR_LINEAR_RANGE(900000, 0x0, 0x1a, 25000), + REGULATOR_LINEAR_RANGE(1600000, 0x1b, 0x3f, 50000), +}; + +static const struct regulator_linear_range dcdc4_ranges[] = { + REGULATOR_LINEAR_RANGE(1175000, 0x0, 0xf, 25000), + REGULATOR_LINEAR_RANGE(1600000, 0x10, 0x34, 50000), +}; + +static int tps65218_pmic_set_voltage_sel(struct regulator_dev *dev, + unsigned selector) +{ + int ret; + struct tps65218 *tps = rdev_get_drvdata(dev); + unsigned int rid = rdev_get_id(dev); + + /* Set the voltage based on vsel value and write protect level is 2 */ + ret = tps65218_set_bits(tps, dev->desc->vsel_reg, dev->desc->vsel_mask, + selector, TPS65218_PROTECT_L1); + + /* Set GO bit for DCDC1/2 to initiate voltage transistion */ + switch (rid) { + case TPS65218_DCDC_1: + case TPS65218_DCDC_2: + ret = tps65218_set_bits(tps, TPS65218_REG_CONTRL_SLEW_RATE, + TPS65218_SLEW_RATE_GO, + TPS65218_SLEW_RATE_GO, + TPS65218_PROTECT_L1); + break; + } + + return ret; +} + +static int tps65218_pmic_enable(struct regulator_dev *dev) +{ + struct tps65218 *tps = rdev_get_drvdata(dev); + int rid = rdev_get_id(dev); + + if (rid < TPS65218_DCDC_1 || rid > TPS65218_LDO_1) + return -EINVAL; + + /* Enable the regulator and password protection is level 1 */ + return tps65218_set_bits(tps, dev->desc->enable_reg, + dev->desc->enable_mask, dev->desc->enable_mask, + TPS65218_PROTECT_L1); +} + +static int tps65218_pmic_disable(struct regulator_dev *dev) +{ + struct tps65218 *tps = rdev_get_drvdata(dev); + int rid = rdev_get_id(dev); + + if (rid < TPS65218_DCDC_1 || rid > TPS65218_LDO_1) + return -EINVAL; + + /* Disable the regulator and password protection is level 1 */ + return tps65218_clear_bits(tps, dev->desc->enable_reg, + dev->desc->enable_mask, TPS65218_PROTECT_L1); +} + +static int tps65218_pmic_set_suspend_enable(struct regulator_dev *dev) +{ + struct tps65218 *tps = rdev_get_drvdata(dev); + unsigned int rid = rdev_get_id(dev); + + if (rid < TPS65218_DCDC_1 || rid > TPS65218_LDO_1) + return -EINVAL; + + return tps65218_clear_bits(tps, dev->desc->bypass_reg, + dev->desc->bypass_mask, + TPS65218_PROTECT_L1); +} + +static int tps65218_pmic_set_suspend_disable(struct regulator_dev *dev) +{ + struct tps65218 *tps = rdev_get_drvdata(dev); + unsigned int rid = rdev_get_id(dev); + + if (rid < TPS65218_DCDC_1 || rid > TPS65218_LDO_1) + return -EINVAL; + + /* + * Certain revisions of TPS65218 will need to have DCDC3 regulator + * enabled always, otherwise an immediate system reboot will occur + * during poweroff. + */ + if (rid == TPS65218_DCDC_3 && tps->rev == TPS65218_REV_2_1) + return 0; + + if (!tps->strobes[rid]) { + if (rid == TPS65218_DCDC_3) + tps->strobes[rid] = 3; + else + return -EINVAL; + } + + return tps65218_set_bits(tps, dev->desc->bypass_reg, + dev->desc->bypass_mask, + tps->strobes[rid], TPS65218_PROTECT_L1); +} + +/* Operations permitted on DCDC1, DCDC2 */ +static struct regulator_ops tps65218_dcdc12_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = tps65218_pmic_enable, + .disable = tps65218_pmic_disable, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = tps65218_pmic_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_suspend_enable = tps65218_pmic_set_suspend_enable, + .set_suspend_disable = tps65218_pmic_set_suspend_disable, +}; + +/* Operations permitted on DCDC3, DCDC4 and LDO1 */ +static struct regulator_ops tps65218_ldo1_dcdc34_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = tps65218_pmic_enable, + .disable = tps65218_pmic_disable, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = tps65218_pmic_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_suspend_enable = tps65218_pmic_set_suspend_enable, + .set_suspend_disable = tps65218_pmic_set_suspend_disable, +}; + +static const int ls3_currents[] = { 100, 200, 500, 1000 }; + +static int tps65218_pmic_set_input_current_lim(struct regulator_dev *dev, + int lim_uA) +{ + unsigned int index = 0; + unsigned int num_currents = ARRAY_SIZE(ls3_currents); + struct tps65218 *tps = rdev_get_drvdata(dev); + + while (index < num_currents && ls3_currents[index] != lim_uA) + index++; + + if (index == num_currents) + return -EINVAL; + + return tps65218_set_bits(tps, dev->desc->csel_reg, dev->desc->csel_mask, + index << 2, TPS65218_PROTECT_L1); +} + +static int tps65218_pmic_set_current_limit(struct regulator_dev *dev, + int min_uA, int max_uA) +{ + int index = 0; + unsigned int num_currents = ARRAY_SIZE(ls3_currents); + struct tps65218 *tps = rdev_get_drvdata(dev); + + while (index < num_currents && ls3_currents[index] < max_uA) + index++; + + index--; + + if (index < 0 || ls3_currents[index] < min_uA) + return -EINVAL; + + return tps65218_set_bits(tps, dev->desc->csel_reg, dev->desc->csel_mask, + index << 2, TPS65218_PROTECT_L1); +} + +static int tps65218_pmic_get_current_limit(struct regulator_dev *dev) +{ + int retval; + unsigned int index; + struct tps65218 *tps = rdev_get_drvdata(dev); + + retval = regmap_read(tps->regmap, dev->desc->csel_reg, &index); + if (retval < 0) + return retval; + + index = (index & dev->desc->csel_mask) >> 2; + + return ls3_currents[index]; +} + +static struct regulator_ops tps65218_ls3_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = tps65218_pmic_enable, + .disable = tps65218_pmic_disable, + .set_input_current_limit = tps65218_pmic_set_input_current_lim, + .set_current_limit = tps65218_pmic_set_current_limit, + .get_current_limit = tps65218_pmic_get_current_limit, +}; + +/* Operations permitted on DCDC5, DCDC6 */ +static struct regulator_ops tps65218_dcdc56_pmic_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = tps65218_pmic_enable, + .disable = tps65218_pmic_disable, + .set_suspend_enable = tps65218_pmic_set_suspend_enable, + .set_suspend_disable = tps65218_pmic_set_suspend_disable, +}; + +static const struct regulator_desc regulators[] = { + TPS65218_REGULATOR("DCDC1", "regulator-dcdc1", TPS65218_DCDC_1, + REGULATOR_VOLTAGE, tps65218_dcdc12_ops, 64, + TPS65218_REG_CONTROL_DCDC1, + TPS65218_CONTROL_DCDC1_MASK, TPS65218_REG_ENABLE1, + TPS65218_ENABLE1_DC1_EN, 0, 0, dcdc1_dcdc2_ranges, + 2, 4000, 0, TPS65218_REG_SEQ3, + TPS65218_SEQ3_DC1_SEQ_MASK), + TPS65218_REGULATOR("DCDC2", "regulator-dcdc2", TPS65218_DCDC_2, + REGULATOR_VOLTAGE, tps65218_dcdc12_ops, 64, + TPS65218_REG_CONTROL_DCDC2, + TPS65218_CONTROL_DCDC2_MASK, TPS65218_REG_ENABLE1, + TPS65218_ENABLE1_DC2_EN, 0, 0, dcdc1_dcdc2_ranges, + 2, 4000, 0, TPS65218_REG_SEQ3, + TPS65218_SEQ3_DC2_SEQ_MASK), + TPS65218_REGULATOR("DCDC3", "regulator-dcdc3", TPS65218_DCDC_3, + REGULATOR_VOLTAGE, tps65218_ldo1_dcdc34_ops, 64, + TPS65218_REG_CONTROL_DCDC3, + TPS65218_CONTROL_DCDC3_MASK, TPS65218_REG_ENABLE1, + TPS65218_ENABLE1_DC3_EN, 0, 0, ldo1_dcdc3_ranges, 2, + 0, 0, TPS65218_REG_SEQ4, TPS65218_SEQ4_DC3_SEQ_MASK), + TPS65218_REGULATOR("DCDC4", "regulator-dcdc4", TPS65218_DCDC_4, + REGULATOR_VOLTAGE, tps65218_ldo1_dcdc34_ops, 53, + TPS65218_REG_CONTROL_DCDC4, + TPS65218_CONTROL_DCDC4_MASK, TPS65218_REG_ENABLE1, + TPS65218_ENABLE1_DC4_EN, 0, 0, dcdc4_ranges, 2, + 0, 0, TPS65218_REG_SEQ4, TPS65218_SEQ4_DC4_SEQ_MASK), + TPS65218_REGULATOR("DCDC5", "regulator-dcdc5", TPS65218_DCDC_5, + REGULATOR_VOLTAGE, tps65218_dcdc56_pmic_ops, 1, -1, + -1, TPS65218_REG_ENABLE1, TPS65218_ENABLE1_DC5_EN, 0, + 0, NULL, 0, 0, 1000000, TPS65218_REG_SEQ5, + TPS65218_SEQ5_DC5_SEQ_MASK), + TPS65218_REGULATOR("DCDC6", "regulator-dcdc6", TPS65218_DCDC_6, + REGULATOR_VOLTAGE, tps65218_dcdc56_pmic_ops, 1, -1, + -1, TPS65218_REG_ENABLE1, TPS65218_ENABLE1_DC6_EN, 0, + 0, NULL, 0, 0, 1800000, TPS65218_REG_SEQ5, + TPS65218_SEQ5_DC6_SEQ_MASK), + TPS65218_REGULATOR("LDO1", "regulator-ldo1", TPS65218_LDO_1, + REGULATOR_VOLTAGE, tps65218_ldo1_dcdc34_ops, 64, + TPS65218_REG_CONTROL_LDO1, + TPS65218_CONTROL_LDO1_MASK, TPS65218_REG_ENABLE2, + TPS65218_ENABLE2_LDO1_EN, 0, 0, ldo1_dcdc3_ranges, + 2, 0, 0, TPS65218_REG_SEQ6, + TPS65218_SEQ6_LDO1_SEQ_MASK), + TPS65218_REGULATOR("LS3", "regulator-ls3", TPS65218_LS_3, + REGULATOR_CURRENT, tps65218_ls3_ops, 0, 0, 0, + TPS65218_REG_ENABLE2, TPS65218_ENABLE2_LS3_EN, + TPS65218_REG_CONFIG2, TPS65218_CONFIG2_LS3ILIM_MASK, + NULL, 0, 0, 0, 0, 0), +}; + +static int tps65218_regulator_probe(struct platform_device *pdev) +{ + struct tps65218 *tps = dev_get_drvdata(pdev->dev.parent); + struct regulator_dev *rdev; + struct regulator_config config = { }; + int i, ret; + unsigned int val; + + config.dev = &pdev->dev; + config.dev->of_node = tps->dev->of_node; + config.driver_data = tps; + config.regmap = tps->regmap; + + /* Allocate memory for strobes */ + tps->strobes = devm_kcalloc(&pdev->dev, + TPS65218_NUM_REGULATOR, sizeof(u8), + GFP_KERNEL); + if (!tps->strobes) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(regulators); i++) { + rdev = devm_regulator_register(&pdev->dev, ®ulators[i], + &config); + if (IS_ERR(rdev)) { + dev_err(tps->dev, "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + + ret = regmap_read(tps->regmap, regulators[i].bypass_reg, &val); + if (ret) + return ret; + + tps->strobes[i] = val & regulators[i].bypass_mask; + } + + return 0; +} + +static const struct platform_device_id tps65218_regulator_id_table[] = { + { "tps65218-regulator", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, tps65218_regulator_id_table); + +static struct platform_driver tps65218_regulator_driver = { + .driver = { + .name = "tps65218-pmic", + }, + .probe = tps65218_regulator_probe, + .id_table = tps65218_regulator_id_table, +}; + +module_platform_driver(tps65218_regulator_driver); + +MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>"); +MODULE_DESCRIPTION("TPS65218 voltage regulator driver"); +MODULE_ALIAS("platform:tps65218-pmic"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/tps6524x-regulator.c b/drivers/regulator/tps6524x-regulator.c new file mode 100644 index 000000000..67cac2682 --- /dev/null +++ b/drivers/regulator/tps6524x-regulator.c @@ -0,0 +1,640 @@ +/* + * Regulator driver for TPS6524x PMIC + * + * Copyright (C) 2010 Texas Instruments + * + * 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/err.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> + +#define REG_LDO_SET 0x0 +#define LDO_ILIM_MASK 1 /* 0 = 400-800, 1 = 900-1500 */ +#define LDO_VSEL_MASK 0x0f +#define LDO2_ILIM_SHIFT 12 +#define LDO2_VSEL_SHIFT 4 +#define LDO1_ILIM_SHIFT 8 +#define LDO1_VSEL_SHIFT 0 + +#define REG_BLOCK_EN 0x1 +#define BLOCK_MASK 1 +#define BLOCK_LDO1_SHIFT 0 +#define BLOCK_LDO2_SHIFT 1 +#define BLOCK_LCD_SHIFT 2 +#define BLOCK_USB_SHIFT 3 + +#define REG_DCDC_SET 0x2 +#define DCDC_VDCDC_MASK 0x1f +#define DCDC_VDCDC1_SHIFT 0 +#define DCDC_VDCDC2_SHIFT 5 +#define DCDC_VDCDC3_SHIFT 10 + +#define REG_DCDC_EN 0x3 +#define DCDCDCDC_EN_MASK 0x1 +#define DCDCDCDC1_EN_SHIFT 0 +#define DCDCDCDC1_PG_MSK BIT(1) +#define DCDCDCDC2_EN_SHIFT 2 +#define DCDCDCDC2_PG_MSK BIT(3) +#define DCDCDCDC3_EN_SHIFT 4 +#define DCDCDCDC3_PG_MSK BIT(5) + +#define REG_USB 0x4 +#define USB_ILIM_SHIFT 0 +#define USB_ILIM_MASK 0x3 +#define USB_TSD_SHIFT 2 +#define USB_TSD_MASK 0x3 +#define USB_TWARN_SHIFT 4 +#define USB_TWARN_MASK 0x3 +#define USB_IWARN_SD BIT(6) +#define USB_FAST_LOOP BIT(7) + +#define REG_ALARM 0x5 +#define ALARM_LDO1 BIT(0) +#define ALARM_DCDC1 BIT(1) +#define ALARM_DCDC2 BIT(2) +#define ALARM_DCDC3 BIT(3) +#define ALARM_LDO2 BIT(4) +#define ALARM_USB_WARN BIT(5) +#define ALARM_USB_ALARM BIT(6) +#define ALARM_LCD BIT(9) +#define ALARM_TEMP_WARM BIT(10) +#define ALARM_TEMP_HOT BIT(11) +#define ALARM_NRST BIT(14) +#define ALARM_POWERUP BIT(15) + +#define REG_INT_ENABLE 0x6 +#define INT_LDO1 BIT(0) +#define INT_DCDC1 BIT(1) +#define INT_DCDC2 BIT(2) +#define INT_DCDC3 BIT(3) +#define INT_LDO2 BIT(4) +#define INT_USB_WARN BIT(5) +#define INT_USB_ALARM BIT(6) +#define INT_LCD BIT(9) +#define INT_TEMP_WARM BIT(10) +#define INT_TEMP_HOT BIT(11) +#define INT_GLOBAL_EN BIT(15) + +#define REG_INT_STATUS 0x7 +#define STATUS_LDO1 BIT(0) +#define STATUS_DCDC1 BIT(1) +#define STATUS_DCDC2 BIT(2) +#define STATUS_DCDC3 BIT(3) +#define STATUS_LDO2 BIT(4) +#define STATUS_USB_WARN BIT(5) +#define STATUS_USB_ALARM BIT(6) +#define STATUS_LCD BIT(9) +#define STATUS_TEMP_WARM BIT(10) +#define STATUS_TEMP_HOT BIT(11) + +#define REG_SOFTWARE_RESET 0xb +#define REG_WRITE_ENABLE 0xd +#define REG_REV_ID 0xf + +#define N_DCDC 3 +#define N_LDO 2 +#define N_SWITCH 2 +#define N_REGULATORS (N_DCDC + N_LDO + N_SWITCH) + +#define CMD_READ(reg) ((reg) << 6) +#define CMD_WRITE(reg) (BIT(5) | (reg) << 6) +#define STAT_CLK BIT(3) +#define STAT_WRITE BIT(2) +#define STAT_INVALID BIT(1) +#define STAT_WP BIT(0) + +struct field { + int reg; + int shift; + int mask; +}; + +struct supply_info { + const char *name; + int n_voltages; + const unsigned int *voltages; + int n_ilimsels; + const unsigned int *ilimsels; + struct field enable, voltage, ilimsel; +}; + +struct tps6524x { + struct device *dev; + struct spi_device *spi; + struct mutex lock; + struct regulator_desc desc[N_REGULATORS]; + struct regulator_dev *rdev[N_REGULATORS]; +}; + +static int __read_reg(struct tps6524x *hw, int reg) +{ + int error = 0; + u16 cmd = CMD_READ(reg), in; + u8 status; + struct spi_message m; + struct spi_transfer t[3]; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = &cmd; + t[0].len = 2; + t[0].bits_per_word = 12; + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = ∈ + t[1].len = 2; + t[1].bits_per_word = 16; + spi_message_add_tail(&t[1], &m); + + t[2].rx_buf = &status; + t[2].len = 1; + t[2].bits_per_word = 4; + spi_message_add_tail(&t[2], &m); + + error = spi_sync(hw->spi, &m); + if (error < 0) + return error; + + dev_dbg(hw->dev, "read reg %d, data %x, status %x\n", + reg, in, status); + + if (!(status & STAT_CLK) || (status & STAT_WRITE)) + return -EIO; + + if (status & STAT_INVALID) + return -EINVAL; + + return in; +} + +static int read_reg(struct tps6524x *hw, int reg) +{ + int ret; + + mutex_lock(&hw->lock); + ret = __read_reg(hw, reg); + mutex_unlock(&hw->lock); + + return ret; +} + +static int __write_reg(struct tps6524x *hw, int reg, int val) +{ + int error = 0; + u16 cmd = CMD_WRITE(reg), out = val; + u8 status; + struct spi_message m; + struct spi_transfer t[3]; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = &cmd; + t[0].len = 2; + t[0].bits_per_word = 12; + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = &out; + t[1].len = 2; + t[1].bits_per_word = 16; + spi_message_add_tail(&t[1], &m); + + t[2].rx_buf = &status; + t[2].len = 1; + t[2].bits_per_word = 4; + spi_message_add_tail(&t[2], &m); + + error = spi_sync(hw->spi, &m); + if (error < 0) + return error; + + dev_dbg(hw->dev, "wrote reg %d, data %x, status %x\n", + reg, out, status); + + if (!(status & STAT_CLK) || !(status & STAT_WRITE)) + return -EIO; + + if (status & (STAT_INVALID | STAT_WP)) + return -EINVAL; + + return error; +} + +static int __rmw_reg(struct tps6524x *hw, int reg, int mask, int val) +{ + int ret; + + ret = __read_reg(hw, reg); + if (ret < 0) + return ret; + + ret &= ~mask; + ret |= val; + + ret = __write_reg(hw, reg, ret); + + return (ret < 0) ? ret : 0; +} + +static int rmw_protect(struct tps6524x *hw, int reg, int mask, int val) +{ + int ret; + + mutex_lock(&hw->lock); + + ret = __write_reg(hw, REG_WRITE_ENABLE, 1); + if (ret) { + dev_err(hw->dev, "failed to set write enable\n"); + goto error; + } + + ret = __rmw_reg(hw, reg, mask, val); + if (ret) + dev_err(hw->dev, "failed to rmw register %d\n", reg); + + ret = __write_reg(hw, REG_WRITE_ENABLE, 0); + if (ret) { + dev_err(hw->dev, "failed to clear write enable\n"); + goto error; + } + +error: + mutex_unlock(&hw->lock); + + return ret; +} + +static int read_field(struct tps6524x *hw, const struct field *field) +{ + int tmp; + + tmp = read_reg(hw, field->reg); + if (tmp < 0) + return tmp; + + return (tmp >> field->shift) & field->mask; +} + +static int write_field(struct tps6524x *hw, const struct field *field, + int val) +{ + if (val & ~field->mask) + return -EOVERFLOW; + + return rmw_protect(hw, field->reg, + field->mask << field->shift, + val << field->shift); +} + +static const unsigned int dcdc1_voltages[] = { + 800000, 825000, 850000, 875000, + 900000, 925000, 950000, 975000, + 1000000, 1025000, 1050000, 1075000, + 1100000, 1125000, 1150000, 1175000, + 1200000, 1225000, 1250000, 1275000, + 1300000, 1325000, 1350000, 1375000, + 1400000, 1425000, 1450000, 1475000, + 1500000, 1525000, 1550000, 1575000, +}; + +static const unsigned int dcdc2_voltages[] = { + 1400000, 1450000, 1500000, 1550000, + 1600000, 1650000, 1700000, 1750000, + 1800000, 1850000, 1900000, 1950000, + 2000000, 2050000, 2100000, 2150000, + 2200000, 2250000, 2300000, 2350000, + 2400000, 2450000, 2500000, 2550000, + 2600000, 2650000, 2700000, 2750000, + 2800000, 2850000, 2900000, 2950000, +}; + +static const unsigned int dcdc3_voltages[] = { + 2400000, 2450000, 2500000, 2550000, 2600000, + 2650000, 2700000, 2750000, 2800000, 2850000, + 2900000, 2950000, 3000000, 3050000, 3100000, + 3150000, 3200000, 3250000, 3300000, 3350000, + 3400000, 3450000, 3500000, 3550000, 3600000, +}; + +static const unsigned int ldo1_voltages[] = { + 4300000, 4350000, 4400000, 4450000, + 4500000, 4550000, 4600000, 4650000, + 4700000, 4750000, 4800000, 4850000, + 4900000, 4950000, 5000000, 5050000, +}; + +static const unsigned int ldo2_voltages[] = { + 1100000, 1150000, 1200000, 1250000, + 1300000, 1700000, 1750000, 1800000, + 1850000, 1900000, 3150000, 3200000, + 3250000, 3300000, 3350000, 3400000, +}; + +static const unsigned int fixed_5000000_voltage[] = { + 5000000 +}; + +static const unsigned int ldo_ilimsel[] = { + 400000, 1500000 +}; + +static const unsigned int usb_ilimsel[] = { + 200000, 400000, 800000, 1000000 +}; + +static const unsigned int fixed_2400000_ilimsel[] = { + 2400000 +}; + +static const unsigned int fixed_1200000_ilimsel[] = { + 1200000 +}; + +static const unsigned int fixed_400000_ilimsel[] = { + 400000 +}; + +#define __MK_FIELD(_reg, _mask, _shift) \ + { .reg = (_reg), .mask = (_mask), .shift = (_shift), } + +static const struct supply_info supply_info[N_REGULATORS] = { + { + .name = "DCDC1", + .n_voltages = ARRAY_SIZE(dcdc1_voltages), + .voltages = dcdc1_voltages, + .n_ilimsels = ARRAY_SIZE(fixed_2400000_ilimsel), + .ilimsels = fixed_2400000_ilimsel, + .enable = __MK_FIELD(REG_DCDC_EN, DCDCDCDC_EN_MASK, + DCDCDCDC1_EN_SHIFT), + .voltage = __MK_FIELD(REG_DCDC_SET, DCDC_VDCDC_MASK, + DCDC_VDCDC1_SHIFT), + }, + { + .name = "DCDC2", + .n_voltages = ARRAY_SIZE(dcdc2_voltages), + .voltages = dcdc2_voltages, + .n_ilimsels = ARRAY_SIZE(fixed_1200000_ilimsel), + .ilimsels = fixed_1200000_ilimsel, + .enable = __MK_FIELD(REG_DCDC_EN, DCDCDCDC_EN_MASK, + DCDCDCDC2_EN_SHIFT), + .voltage = __MK_FIELD(REG_DCDC_SET, DCDC_VDCDC_MASK, + DCDC_VDCDC2_SHIFT), + }, + { + .name = "DCDC3", + .n_voltages = ARRAY_SIZE(dcdc3_voltages), + .voltages = dcdc3_voltages, + .n_ilimsels = ARRAY_SIZE(fixed_1200000_ilimsel), + .ilimsels = fixed_1200000_ilimsel, + .enable = __MK_FIELD(REG_DCDC_EN, DCDCDCDC_EN_MASK, + DCDCDCDC3_EN_SHIFT), + .voltage = __MK_FIELD(REG_DCDC_SET, DCDC_VDCDC_MASK, + DCDC_VDCDC3_SHIFT), + }, + { + .name = "LDO1", + .n_voltages = ARRAY_SIZE(ldo1_voltages), + .voltages = ldo1_voltages, + .n_ilimsels = ARRAY_SIZE(ldo_ilimsel), + .ilimsels = ldo_ilimsel, + .enable = __MK_FIELD(REG_BLOCK_EN, BLOCK_MASK, + BLOCK_LDO1_SHIFT), + .voltage = __MK_FIELD(REG_LDO_SET, LDO_VSEL_MASK, + LDO1_VSEL_SHIFT), + .ilimsel = __MK_FIELD(REG_LDO_SET, LDO_ILIM_MASK, + LDO1_ILIM_SHIFT), + }, + { + .name = "LDO2", + .n_voltages = ARRAY_SIZE(ldo2_voltages), + .voltages = ldo2_voltages, + .n_ilimsels = ARRAY_SIZE(ldo_ilimsel), + .ilimsels = ldo_ilimsel, + .enable = __MK_FIELD(REG_BLOCK_EN, BLOCK_MASK, + BLOCK_LDO2_SHIFT), + .voltage = __MK_FIELD(REG_LDO_SET, LDO_VSEL_MASK, + LDO2_VSEL_SHIFT), + .ilimsel = __MK_FIELD(REG_LDO_SET, LDO_ILIM_MASK, + LDO2_ILIM_SHIFT), + }, + { + .name = "USB", + .n_voltages = ARRAY_SIZE(fixed_5000000_voltage), + .voltages = fixed_5000000_voltage, + .n_ilimsels = ARRAY_SIZE(usb_ilimsel), + .ilimsels = usb_ilimsel, + .enable = __MK_FIELD(REG_BLOCK_EN, BLOCK_MASK, + BLOCK_USB_SHIFT), + .ilimsel = __MK_FIELD(REG_USB, USB_ILIM_MASK, + USB_ILIM_SHIFT), + }, + { + .name = "LCD", + .n_voltages = ARRAY_SIZE(fixed_5000000_voltage), + .voltages = fixed_5000000_voltage, + .n_ilimsels = ARRAY_SIZE(fixed_400000_ilimsel), + .ilimsels = fixed_400000_ilimsel, + .enable = __MK_FIELD(REG_BLOCK_EN, BLOCK_MASK, + BLOCK_LCD_SHIFT), + }, +}; + +static int set_voltage_sel(struct regulator_dev *rdev, unsigned selector) +{ + const struct supply_info *info; + struct tps6524x *hw; + + hw = rdev_get_drvdata(rdev); + info = &supply_info[rdev_get_id(rdev)]; + + if (rdev->desc->n_voltages == 1) + return -EINVAL; + + return write_field(hw, &info->voltage, selector); +} + +static int get_voltage_sel(struct regulator_dev *rdev) +{ + const struct supply_info *info; + struct tps6524x *hw; + int ret; + + hw = rdev_get_drvdata(rdev); + info = &supply_info[rdev_get_id(rdev)]; + + if (rdev->desc->n_voltages == 1) + return 0; + + ret = read_field(hw, &info->voltage); + if (ret < 0) + return ret; + if (WARN_ON(ret >= info->n_voltages)) + return -EIO; + + return ret; +} + +static int set_current_limit(struct regulator_dev *rdev, int min_uA, + int max_uA) +{ + const struct supply_info *info; + struct tps6524x *hw; + int i; + + hw = rdev_get_drvdata(rdev); + info = &supply_info[rdev_get_id(rdev)]; + + if (info->n_ilimsels == 1) + return -EINVAL; + + for (i = info->n_ilimsels - 1; i >= 0; i--) { + if (min_uA <= info->ilimsels[i] && + max_uA >= info->ilimsels[i]) + return write_field(hw, &info->ilimsel, i); + } + + return -EINVAL; +} + +static int get_current_limit(struct regulator_dev *rdev) +{ + const struct supply_info *info; + struct tps6524x *hw; + int ret; + + hw = rdev_get_drvdata(rdev); + info = &supply_info[rdev_get_id(rdev)]; + + if (info->n_ilimsels == 1) + return info->ilimsels[0]; + + ret = read_field(hw, &info->ilimsel); + if (ret < 0) + return ret; + if (WARN_ON(ret >= info->n_ilimsels)) + return -EIO; + + return info->ilimsels[ret]; +} + +static int enable_supply(struct regulator_dev *rdev) +{ + const struct supply_info *info; + struct tps6524x *hw; + + hw = rdev_get_drvdata(rdev); + info = &supply_info[rdev_get_id(rdev)]; + + return write_field(hw, &info->enable, 1); +} + +static int disable_supply(struct regulator_dev *rdev) +{ + const struct supply_info *info; + struct tps6524x *hw; + + hw = rdev_get_drvdata(rdev); + info = &supply_info[rdev_get_id(rdev)]; + + return write_field(hw, &info->enable, 0); +} + +static int is_supply_enabled(struct regulator_dev *rdev) +{ + const struct supply_info *info; + struct tps6524x *hw; + + hw = rdev_get_drvdata(rdev); + info = &supply_info[rdev_get_id(rdev)]; + + return read_field(hw, &info->enable); +} + +static struct regulator_ops regulator_ops = { + .is_enabled = is_supply_enabled, + .enable = enable_supply, + .disable = disable_supply, + .get_voltage_sel = get_voltage_sel, + .set_voltage_sel = set_voltage_sel, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .set_current_limit = set_current_limit, + .get_current_limit = get_current_limit, +}; + +static int pmic_probe(struct spi_device *spi) +{ + struct tps6524x *hw; + struct device *dev = &spi->dev; + const struct supply_info *info = supply_info; + struct regulator_init_data *init_data; + struct regulator_config config = { }; + int i; + + init_data = dev_get_platdata(dev); + if (!init_data) { + dev_err(dev, "could not find regulator platform data\n"); + return -EINVAL; + } + + hw = devm_kzalloc(&spi->dev, sizeof(struct tps6524x), GFP_KERNEL); + if (!hw) + return -ENOMEM; + + spi_set_drvdata(spi, hw); + + memset(hw, 0, sizeof(struct tps6524x)); + hw->dev = dev; + hw->spi = spi; + mutex_init(&hw->lock); + + for (i = 0; i < N_REGULATORS; i++, info++, init_data++) { + hw->desc[i].name = info->name; + hw->desc[i].id = i; + hw->desc[i].n_voltages = info->n_voltages; + hw->desc[i].volt_table = info->voltages; + hw->desc[i].ops = ®ulator_ops; + hw->desc[i].type = REGULATOR_VOLTAGE; + hw->desc[i].owner = THIS_MODULE; + + config.dev = dev; + config.init_data = init_data; + config.driver_data = hw; + + hw->rdev[i] = devm_regulator_register(dev, &hw->desc[i], + &config); + if (IS_ERR(hw->rdev[i])) + return PTR_ERR(hw->rdev[i]); + } + + return 0; +} + +static struct spi_driver pmic_driver = { + .probe = pmic_probe, + .driver = { + .name = "tps6524x", + }, +}; + +module_spi_driver(pmic_driver); + +MODULE_DESCRIPTION("TPS6524X PMIC Driver"); +MODULE_AUTHOR("Cyril Chemparathy"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:tps6524x"); diff --git a/drivers/regulator/tps6586x-regulator.c b/drivers/regulator/tps6586x-regulator.c new file mode 100644 index 000000000..ba3dae7b2 --- /dev/null +++ b/drivers/regulator/tps6586x-regulator.c @@ -0,0 +1,545 @@ +/* + * Regulator driver for TI TPS6586x + * + * Copyright (C) 2010 Compulab Ltd. + * Author: Mike Rapoport <mike@compulab.co.il> + * + * Based on da903x + * Copyright (C) 2006-2008 Marvell International Ltd. + * Copyright (C) 2008 Compulab Ltd. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/tps6586x.h> + +/* supply control and voltage setting */ +#define TPS6586X_SUPPLYENA 0x10 +#define TPS6586X_SUPPLYENB 0x11 +#define TPS6586X_SUPPLYENC 0x12 +#define TPS6586X_SUPPLYEND 0x13 +#define TPS6586X_SUPPLYENE 0x14 +#define TPS6586X_VCC1 0x20 +#define TPS6586X_VCC2 0x21 +#define TPS6586X_SM1V1 0x23 +#define TPS6586X_SM1V2 0x24 +#define TPS6586X_SM1SL 0x25 +#define TPS6586X_SM0V1 0x26 +#define TPS6586X_SM0V2 0x27 +#define TPS6586X_SM0SL 0x28 +#define TPS6586X_LDO2AV1 0x29 +#define TPS6586X_LDO2AV2 0x2A +#define TPS6586X_LDO2BV1 0x2F +#define TPS6586X_LDO2BV2 0x30 +#define TPS6586X_LDO4V1 0x32 +#define TPS6586X_LDO4V2 0x33 + +/* converter settings */ +#define TPS6586X_SUPPLYV1 0x41 +#define TPS6586X_SUPPLYV2 0x42 +#define TPS6586X_SUPPLYV3 0x43 +#define TPS6586X_SUPPLYV4 0x44 +#define TPS6586X_SUPPLYV5 0x45 +#define TPS6586X_SUPPLYV6 0x46 +#define TPS6586X_SMODE1 0x47 +#define TPS6586X_SMODE2 0x48 + +struct tps6586x_regulator { + struct regulator_desc desc; + + int enable_bit[2]; + int enable_reg[2]; +}; + +static struct regulator_ops tps6586x_rw_regulator_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static struct regulator_ops tps6586x_rw_linear_regulator_ops = { + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static struct regulator_ops tps6586x_ro_regulator_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static struct regulator_ops tps6586x_sys_regulator_ops = { +}; + +static const unsigned int tps6586x_ldo0_voltages[] = { + 1200000, 1500000, 1800000, 2500000, 2700000, 2850000, 3100000, 3300000, +}; + +static const unsigned int tps6586x_ldo_voltages[] = { + 1250000, 1500000, 1800000, 2500000, 2700000, 2850000, 3100000, 3300000, +}; + +static const unsigned int tps658640_rtc_voltages[] = { + 2500000, 2850000, 3100000, 3300000, +}; + +#define TPS6586X_REGULATOR(_id, _ops, _pin_name, vdata, vreg, shift, nbits, \ + ereg0, ebit0, ereg1, ebit1, goreg, gobit) \ + .desc = { \ + .supply_name = _pin_name, \ + .name = "REG-" #_id, \ + .ops = &tps6586x_## _ops ## _regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = TPS6586X_ID_##_id, \ + .n_voltages = ARRAY_SIZE(vdata##_voltages), \ + .volt_table = vdata##_voltages, \ + .owner = THIS_MODULE, \ + .enable_reg = TPS6586X_SUPPLY##ereg0, \ + .enable_mask = 1 << (ebit0), \ + .vsel_reg = TPS6586X_##vreg, \ + .vsel_mask = ((1 << (nbits)) - 1) << (shift), \ + .apply_reg = (goreg), \ + .apply_bit = (gobit), \ + }, \ + .enable_reg[0] = TPS6586X_SUPPLY##ereg0, \ + .enable_bit[0] = (ebit0), \ + .enable_reg[1] = TPS6586X_SUPPLY##ereg1, \ + .enable_bit[1] = (ebit1), + +#define TPS6586X_REGULATOR_LINEAR(_id, _ops, _pin_name, n_volt, min_uv, \ + uv_step, vreg, shift, nbits, ereg0, \ + ebit0, ereg1, ebit1, goreg, gobit) \ + .desc = { \ + .supply_name = _pin_name, \ + .name = "REG-" #_id, \ + .ops = &tps6586x_## _ops ## _regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = TPS6586X_ID_##_id, \ + .n_voltages = n_volt, \ + .min_uV = min_uv, \ + .uV_step = uv_step, \ + .owner = THIS_MODULE, \ + .enable_reg = TPS6586X_SUPPLY##ereg0, \ + .enable_mask = 1 << (ebit0), \ + .vsel_reg = TPS6586X_##vreg, \ + .vsel_mask = ((1 << (nbits)) - 1) << (shift), \ + .apply_reg = (goreg), \ + .apply_bit = (gobit), \ + }, \ + .enable_reg[0] = TPS6586X_SUPPLY##ereg0, \ + .enable_bit[0] = (ebit0), \ + .enable_reg[1] = TPS6586X_SUPPLY##ereg1, \ + .enable_bit[1] = (ebit1), + +#define TPS6586X_LDO(_id, _pname, vdata, vreg, shift, nbits, \ + ereg0, ebit0, ereg1, ebit1) \ +{ \ + TPS6586X_REGULATOR(_id, rw, _pname, vdata, vreg, shift, nbits, \ + ereg0, ebit0, ereg1, ebit1, 0, 0) \ +} + +#define TPS6586X_LDO_LINEAR(_id, _pname, n_volt, min_uv, uv_step, vreg, \ + shift, nbits, ereg0, ebit0, ereg1, ebit1) \ +{ \ + TPS6586X_REGULATOR_LINEAR(_id, rw_linear, _pname, n_volt, \ + min_uv, uv_step, vreg, shift, nbits, \ + ereg0, ebit0, ereg1, ebit1, 0, 0) \ +} + +#define TPS6586X_FIXED_LDO(_id, _pname, vdata, vreg, shift, nbits, \ + ereg0, ebit0, ereg1, ebit1) \ +{ \ + TPS6586X_REGULATOR(_id, ro, _pname, vdata, vreg, shift, nbits, \ + ereg0, ebit0, ereg1, ebit1, 0, 0) \ +} + +#define TPS6586X_DVM(_id, _pname, n_volt, min_uv, uv_step, vreg, shift, \ + nbits, ereg0, ebit0, ereg1, ebit1, goreg, gobit) \ +{ \ + TPS6586X_REGULATOR_LINEAR(_id, rw_linear, _pname, n_volt, \ + min_uv, uv_step, vreg, shift, nbits, \ + ereg0, ebit0, ereg1, ebit1, goreg, \ + gobit) \ +} + +#define TPS6586X_SYS_REGULATOR() \ +{ \ + .desc = { \ + .supply_name = "sys", \ + .name = "REG-SYS", \ + .ops = &tps6586x_sys_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = TPS6586X_ID_SYS, \ + .owner = THIS_MODULE, \ + }, \ +} + +static struct tps6586x_regulator tps6586x_regulator[] = { + TPS6586X_SYS_REGULATOR(), + TPS6586X_LDO(LDO_0, "vinldo01", tps6586x_ldo0, SUPPLYV1, 5, 3, ENC, 0, + END, 0), + TPS6586X_LDO(LDO_3, "vinldo23", tps6586x_ldo, SUPPLYV4, 0, 3, ENC, 2, + END, 2), + TPS6586X_LDO(LDO_5, "REG-SYS", tps6586x_ldo, SUPPLYV6, 0, 3, ENE, 6, + ENE, 6), + TPS6586X_LDO(LDO_6, "vinldo678", tps6586x_ldo, SUPPLYV3, 0, 3, ENC, 4, + END, 4), + TPS6586X_LDO(LDO_7, "vinldo678", tps6586x_ldo, SUPPLYV3, 3, 3, ENC, 5, + END, 5), + TPS6586X_LDO(LDO_8, "vinldo678", tps6586x_ldo, SUPPLYV2, 5, 3, ENC, 6, + END, 6), + TPS6586X_LDO(LDO_9, "vinldo9", tps6586x_ldo, SUPPLYV6, 3, 3, ENE, 7, + ENE, 7), + TPS6586X_LDO(LDO_RTC, "REG-SYS", tps6586x_ldo, SUPPLYV4, 3, 3, V4, 7, + V4, 7), + TPS6586X_LDO_LINEAR(LDO_1, "vinldo01", 32, 725000, 25000, SUPPLYV1, + 0, 5, ENC, 1, END, 1), + TPS6586X_LDO_LINEAR(SM_2, "vin-sm2", 32, 3000000, 50000, SUPPLYV2, + 0, 5, ENC, 7, END, 7), + TPS6586X_DVM(LDO_2, "vinldo23", 32, 725000, 25000, LDO2BV1, 0, 5, + ENA, 3, ENB, 3, TPS6586X_VCC2, BIT(6)), + TPS6586X_DVM(LDO_4, "vinldo4", 32, 1700000, 25000, LDO4V1, 0, 5, + ENC, 3, END, 3, TPS6586X_VCC1, BIT(6)), + TPS6586X_DVM(SM_0, "vin-sm0", 32, 725000, 25000, SM0V1, 0, 5, + ENA, 1, ENB, 1, TPS6586X_VCC1, BIT(2)), + TPS6586X_DVM(SM_1, "vin-sm1", 32, 725000, 25000, SM1V1, 0, 5, + ENA, 0, ENB, 0, TPS6586X_VCC1, BIT(0)), +}; + +static struct tps6586x_regulator tps658623_regulator[] = { + TPS6586X_LDO_LINEAR(SM_2, "vin-sm2", 32, 1700000, 25000, SUPPLYV2, + 0, 5, ENC, 7, END, 7), +}; + +static struct tps6586x_regulator tps658640_regulator[] = { + TPS6586X_LDO(LDO_3, "vinldo23", tps6586x_ldo0, SUPPLYV4, 0, 3, + ENC, 2, END, 2), + TPS6586X_LDO(LDO_5, "REG-SYS", tps6586x_ldo0, SUPPLYV6, 0, 3, + ENE, 6, ENE, 6), + TPS6586X_LDO(LDO_6, "vinldo678", tps6586x_ldo0, SUPPLYV3, 0, 3, + ENC, 4, END, 4), + TPS6586X_LDO(LDO_7, "vinldo678", tps6586x_ldo0, SUPPLYV3, 3, 3, + ENC, 5, END, 5), + TPS6586X_LDO(LDO_8, "vinldo678", tps6586x_ldo0, SUPPLYV2, 5, 3, + ENC, 6, END, 6), + TPS6586X_LDO(LDO_9, "vinldo9", tps6586x_ldo0, SUPPLYV6, 3, 3, + ENE, 7, ENE, 7), + TPS6586X_LDO_LINEAR(SM_2, "vin-sm2", 32, 2150000, 50000, SUPPLYV2, + 0, 5, ENC, 7, END, 7), + + TPS6586X_FIXED_LDO(LDO_RTC, "REG-SYS", tps658640_rtc, SUPPLYV4, 3, 2, + V4, 7, V4, 7), +}; + +static struct tps6586x_regulator tps658643_regulator[] = { + TPS6586X_LDO_LINEAR(SM_2, "vin-sm2", 32, 1025000, 25000, SUPPLYV2, + 0, 5, ENC, 7, END, 7), +}; + +/* + * TPS6586X has 2 enable bits that are OR'ed to determine the actual + * regulator state. Clearing one of this bits allows switching + * regulator on and of with single register write. + */ +static inline int tps6586x_regulator_preinit(struct device *parent, + struct tps6586x_regulator *ri) +{ + uint8_t val1, val2; + int ret; + + if (ri->enable_reg[0] == ri->enable_reg[1] && + ri->enable_bit[0] == ri->enable_bit[1]) + return 0; + + ret = tps6586x_read(parent, ri->enable_reg[0], &val1); + if (ret) + return ret; + + ret = tps6586x_read(parent, ri->enable_reg[1], &val2); + if (ret) + return ret; + + if (!(val2 & (1 << ri->enable_bit[1]))) + return 0; + + /* + * The regulator is on, but it's enabled with the bit we don't + * want to use, so we switch the enable bits + */ + if (!(val1 & (1 << ri->enable_bit[0]))) { + ret = tps6586x_set_bits(parent, ri->enable_reg[0], + 1 << ri->enable_bit[0]); + if (ret) + return ret; + } + + return tps6586x_clr_bits(parent, ri->enable_reg[1], + 1 << ri->enable_bit[1]); +} + +static int tps6586x_regulator_set_slew_rate(struct platform_device *pdev, + int id, struct regulator_init_data *p) +{ + struct device *parent = pdev->dev.parent; + struct tps6586x_settings *setting = p->driver_data; + uint8_t reg; + + if (setting == NULL) + return 0; + + if (!(setting->slew_rate & TPS6586X_SLEW_RATE_SET)) + return 0; + + /* only SM0 and SM1 can have the slew rate settings */ + switch (id) { + case TPS6586X_ID_SM_0: + reg = TPS6586X_SM0SL; + break; + case TPS6586X_ID_SM_1: + reg = TPS6586X_SM1SL; + break; + default: + dev_err(&pdev->dev, "Only SM0/SM1 can set slew rate\n"); + return -EINVAL; + } + + return tps6586x_write(parent, reg, + setting->slew_rate & TPS6586X_SLEW_RATE_MASK); +} + +static struct tps6586x_regulator *find_regulator_info(int id, int version) +{ + struct tps6586x_regulator *ri; + struct tps6586x_regulator *table = NULL; + int num; + int i; + + switch (version) { + case TPS658623: + case TPS658624: + table = tps658623_regulator; + num = ARRAY_SIZE(tps658623_regulator); + break; + case TPS658640: + case TPS658640v2: + table = tps658640_regulator; + num = ARRAY_SIZE(tps658640_regulator); + break; + case TPS658643: + table = tps658643_regulator; + num = ARRAY_SIZE(tps658643_regulator); + break; + } + + /* Search version specific table first */ + if (table) { + for (i = 0; i < num; i++) { + ri = &table[i]; + if (ri->desc.id == id) + return ri; + } + } + + for (i = 0; i < ARRAY_SIZE(tps6586x_regulator); i++) { + ri = &tps6586x_regulator[i]; + if (ri->desc.id == id) + return ri; + } + return NULL; +} + +#ifdef CONFIG_OF +static struct of_regulator_match tps6586x_matches[] = { + { .name = "sys", .driver_data = (void *)TPS6586X_ID_SYS }, + { .name = "sm0", .driver_data = (void *)TPS6586X_ID_SM_0 }, + { .name = "sm1", .driver_data = (void *)TPS6586X_ID_SM_1 }, + { .name = "sm2", .driver_data = (void *)TPS6586X_ID_SM_2 }, + { .name = "ldo0", .driver_data = (void *)TPS6586X_ID_LDO_0 }, + { .name = "ldo1", .driver_data = (void *)TPS6586X_ID_LDO_1 }, + { .name = "ldo2", .driver_data = (void *)TPS6586X_ID_LDO_2 }, + { .name = "ldo3", .driver_data = (void *)TPS6586X_ID_LDO_3 }, + { .name = "ldo4", .driver_data = (void *)TPS6586X_ID_LDO_4 }, + { .name = "ldo5", .driver_data = (void *)TPS6586X_ID_LDO_5 }, + { .name = "ldo6", .driver_data = (void *)TPS6586X_ID_LDO_6 }, + { .name = "ldo7", .driver_data = (void *)TPS6586X_ID_LDO_7 }, + { .name = "ldo8", .driver_data = (void *)TPS6586X_ID_LDO_8 }, + { .name = "ldo9", .driver_data = (void *)TPS6586X_ID_LDO_9 }, + { .name = "ldo_rtc", .driver_data = (void *)TPS6586X_ID_LDO_RTC }, +}; + +static struct tps6586x_platform_data *tps6586x_parse_regulator_dt( + struct platform_device *pdev, + struct of_regulator_match **tps6586x_reg_matches) +{ + const unsigned int num = ARRAY_SIZE(tps6586x_matches); + struct device_node *np = pdev->dev.parent->of_node; + struct device_node *regs; + const char *sys_rail = NULL; + unsigned int i; + struct tps6586x_platform_data *pdata; + int err; + + regs = of_get_child_by_name(np, "regulators"); + if (!regs) { + dev_err(&pdev->dev, "regulator node not found\n"); + return NULL; + } + + err = of_regulator_match(&pdev->dev, regs, tps6586x_matches, num); + of_node_put(regs); + if (err < 0) { + dev_err(&pdev->dev, "Regulator match failed, e %d\n", err); + return NULL; + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + for (i = 0; i < num; i++) { + uintptr_t id; + if (!tps6586x_matches[i].init_data) + continue; + + pdata->reg_init_data[i] = tps6586x_matches[i].init_data; + id = (uintptr_t)tps6586x_matches[i].driver_data; + if (id == TPS6586X_ID_SYS) + sys_rail = pdata->reg_init_data[i]->constraints.name; + + if ((id == TPS6586X_ID_LDO_5) || (id == TPS6586X_ID_LDO_RTC)) + pdata->reg_init_data[i]->supply_regulator = sys_rail; + } + *tps6586x_reg_matches = tps6586x_matches; + return pdata; +} +#else +static struct tps6586x_platform_data *tps6586x_parse_regulator_dt( + struct platform_device *pdev, + struct of_regulator_match **tps6586x_reg_matches) +{ + *tps6586x_reg_matches = NULL; + return NULL; +} +#endif + +static int tps6586x_regulator_probe(struct platform_device *pdev) +{ + struct tps6586x_regulator *ri = NULL; + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct regulator_init_data *reg_data; + struct tps6586x_platform_data *pdata; + struct of_regulator_match *tps6586x_reg_matches = NULL; + int version; + int id; + int err; + + dev_dbg(&pdev->dev, "Probing regulator\n"); + + pdata = dev_get_platdata(pdev->dev.parent); + if ((!pdata) && (pdev->dev.parent->of_node)) + pdata = tps6586x_parse_regulator_dt(pdev, + &tps6586x_reg_matches); + + if (!pdata) { + dev_err(&pdev->dev, "Platform data not available, exiting\n"); + return -ENODEV; + } + + version = tps6586x_get_version(pdev->dev.parent); + + for (id = 0; id < TPS6586X_ID_MAX_REGULATOR; ++id) { + reg_data = pdata->reg_init_data[id]; + + ri = find_regulator_info(id, version); + + if (!ri) { + dev_err(&pdev->dev, "invalid regulator ID specified\n"); + return -EINVAL; + } + + err = tps6586x_regulator_preinit(pdev->dev.parent, ri); + if (err) { + dev_err(&pdev->dev, + "regulator %d preinit failed, e %d\n", id, err); + return err; + } + + config.dev = pdev->dev.parent; + config.init_data = reg_data; + config.driver_data = ri; + + if (tps6586x_reg_matches) + config.of_node = tps6586x_reg_matches[id].of_node; + + rdev = devm_regulator_register(&pdev->dev, &ri->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register regulator %s\n", + ri->desc.name); + return PTR_ERR(rdev); + } + + if (reg_data) { + err = tps6586x_regulator_set_slew_rate(pdev, id, + reg_data); + if (err < 0) { + dev_err(&pdev->dev, + "Slew rate config failed, e %d\n", err); + return err; + } + } + } + + platform_set_drvdata(pdev, rdev); + return 0; +} + +static struct platform_driver tps6586x_regulator_driver = { + .driver = { + .name = "tps6586x-regulator", + }, + .probe = tps6586x_regulator_probe, +}; + +static int __init tps6586x_regulator_init(void) +{ + return platform_driver_register(&tps6586x_regulator_driver); +} +subsys_initcall(tps6586x_regulator_init); + +static void __exit tps6586x_regulator_exit(void) +{ + platform_driver_unregister(&tps6586x_regulator_driver); +} +module_exit(tps6586x_regulator_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); +MODULE_DESCRIPTION("Regulator Driver for TI TPS6586X PMIC"); +MODULE_ALIAS("platform:tps6586x-regulator"); diff --git a/drivers/regulator/tps65910-regulator.c b/drivers/regulator/tps65910-regulator.c new file mode 100644 index 000000000..5ebb6ee73 --- /dev/null +++ b/drivers/regulator/tps65910-regulator.c @@ -0,0 +1,1284 @@ +/* + * tps65910.c -- TI tps65910 + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk> + * + * 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/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/mfd/tps65910.h> +#include <linux/regulator/of_regulator.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 | \ + TPS65911_SLEEP_CONTROL_EXT_INPUT_SLEEP) + +/* supported VIO voltages in microvolts */ +static const unsigned int VIO_VSEL_table[] = { + 1500000, 1800000, 2500000, 3300000, +}; + +/* VSEL tables for TPS65910 specific LDOs and dcdc's */ + +/* supported VRTC voltages in microvolts */ +static const unsigned int VRTC_VSEL_table[] = { + 1800000, +}; + +/* supported VDD3 voltages in microvolts */ +static const unsigned int VDD3_VSEL_table[] = { + 5000000, +}; + +/* supported VDIG1 voltages in microvolts */ +static const unsigned int VDIG1_VSEL_table[] = { + 1200000, 1500000, 1800000, 2700000, +}; + +/* supported VDIG2 voltages in microvolts */ +static const unsigned int VDIG2_VSEL_table[] = { + 1000000, 1100000, 1200000, 1800000, +}; + +/* supported VPLL voltages in microvolts */ +static const unsigned int VPLL_VSEL_table[] = { + 1000000, 1100000, 1800000, 2500000, +}; + +/* supported VDAC voltages in microvolts */ +static const unsigned int VDAC_VSEL_table[] = { + 1800000, 2600000, 2800000, 2850000, +}; + +/* supported VAUX1 voltages in microvolts */ +static const unsigned int VAUX1_VSEL_table[] = { + 1800000, 2500000, 2800000, 2850000, +}; + +/* supported VAUX2 voltages in microvolts */ +static const unsigned int VAUX2_VSEL_table[] = { + 1800000, 2800000, 2900000, 3300000, +}; + +/* supported VAUX33 voltages in microvolts */ +static const unsigned int VAUX33_VSEL_table[] = { + 1800000, 2000000, 2800000, 3300000, +}; + +/* supported VMMC voltages in microvolts */ +static const unsigned int VMMC_VSEL_table[] = { + 1800000, 2800000, 3000000, 3300000, +}; + +/* supported BBCH voltages in microvolts */ +static const unsigned int VBB_VSEL_table[] = { + 3000000, 2520000, 3150000, 5000000, +}; + +struct tps_info { + const char *name; + const char *vin_name; + u8 n_voltages; + const unsigned int *voltage_table; + int enable_time_us; +}; + +static struct tps_info tps65910_regs[] = { + { + .name = "vrtc", + .vin_name = "vcc7", + .n_voltages = ARRAY_SIZE(VRTC_VSEL_table), + .voltage_table = VRTC_VSEL_table, + .enable_time_us = 2200, + }, + { + .name = "vio", + .vin_name = "vccio", + .n_voltages = ARRAY_SIZE(VIO_VSEL_table), + .voltage_table = VIO_VSEL_table, + .enable_time_us = 350, + }, + { + .name = "vdd1", + .vin_name = "vcc1", + .enable_time_us = 350, + }, + { + .name = "vdd2", + .vin_name = "vcc2", + .enable_time_us = 350, + }, + { + .name = "vdd3", + .n_voltages = ARRAY_SIZE(VDD3_VSEL_table), + .voltage_table = VDD3_VSEL_table, + .enable_time_us = 200, + }, + { + .name = "vdig1", + .vin_name = "vcc6", + .n_voltages = ARRAY_SIZE(VDIG1_VSEL_table), + .voltage_table = VDIG1_VSEL_table, + .enable_time_us = 100, + }, + { + .name = "vdig2", + .vin_name = "vcc6", + .n_voltages = ARRAY_SIZE(VDIG2_VSEL_table), + .voltage_table = VDIG2_VSEL_table, + .enable_time_us = 100, + }, + { + .name = "vpll", + .vin_name = "vcc5", + .n_voltages = ARRAY_SIZE(VPLL_VSEL_table), + .voltage_table = VPLL_VSEL_table, + .enable_time_us = 100, + }, + { + .name = "vdac", + .vin_name = "vcc5", + .n_voltages = ARRAY_SIZE(VDAC_VSEL_table), + .voltage_table = VDAC_VSEL_table, + .enable_time_us = 100, + }, + { + .name = "vaux1", + .vin_name = "vcc4", + .n_voltages = ARRAY_SIZE(VAUX1_VSEL_table), + .voltage_table = VAUX1_VSEL_table, + .enable_time_us = 100, + }, + { + .name = "vaux2", + .vin_name = "vcc4", + .n_voltages = ARRAY_SIZE(VAUX2_VSEL_table), + .voltage_table = VAUX2_VSEL_table, + .enable_time_us = 100, + }, + { + .name = "vaux33", + .vin_name = "vcc3", + .n_voltages = ARRAY_SIZE(VAUX33_VSEL_table), + .voltage_table = VAUX33_VSEL_table, + .enable_time_us = 100, + }, + { + .name = "vmmc", + .vin_name = "vcc3", + .n_voltages = ARRAY_SIZE(VMMC_VSEL_table), + .voltage_table = VMMC_VSEL_table, + .enable_time_us = 100, + }, + { + .name = "vbb", + .vin_name = "vcc7", + .n_voltages = ARRAY_SIZE(VBB_VSEL_table), + .voltage_table = VBB_VSEL_table, + }, +}; + +static struct tps_info tps65911_regs[] = { + { + .name = "vrtc", + .vin_name = "vcc7", + .enable_time_us = 2200, + }, + { + .name = "vio", + .vin_name = "vccio", + .n_voltages = ARRAY_SIZE(VIO_VSEL_table), + .voltage_table = VIO_VSEL_table, + .enable_time_us = 350, + }, + { + .name = "vdd1", + .vin_name = "vcc1", + .n_voltages = 0x4C, + .enable_time_us = 350, + }, + { + .name = "vdd2", + .vin_name = "vcc2", + .n_voltages = 0x4C, + .enable_time_us = 350, + }, + { + .name = "vddctrl", + .n_voltages = 0x44, + .enable_time_us = 900, + }, + { + .name = "ldo1", + .vin_name = "vcc6", + .n_voltages = 0x33, + .enable_time_us = 420, + }, + { + .name = "ldo2", + .vin_name = "vcc6", + .n_voltages = 0x33, + .enable_time_us = 420, + }, + { + .name = "ldo3", + .vin_name = "vcc5", + .n_voltages = 0x1A, + .enable_time_us = 230, + }, + { + .name = "ldo4", + .vin_name = "vcc5", + .n_voltages = 0x33, + .enable_time_us = 230, + }, + { + .name = "ldo5", + .vin_name = "vcc4", + .n_voltages = 0x1A, + .enable_time_us = 230, + }, + { + .name = "ldo6", + .vin_name = "vcc3", + .n_voltages = 0x1A, + .enable_time_us = 230, + }, + { + .name = "ldo7", + .vin_name = "vcc3", + .n_voltages = 0x1A, + .enable_time_us = 230, + }, + { + .name = "ldo8", + .vin_name = "vcc3", + .n_voltages = 0x1A, + .enable_time_us = 230, + }, +}; + +#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; + struct regulator_dev **rdev; + struct tps_info **info; + int num_regulators; + int mode; + int (*get_ctrl_reg)(int); + unsigned int *ext_sleep_control; + unsigned int board_ext_control[TPS65910_NUM_REGS]; +}; + +static int tps65910_get_ctrl_register(int id) +{ + switch (id) { + case TPS65910_REG_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; + case TPS65910_REG_VBB: + return TPS65910_BBCH; + default: + return -EINVAL; + } +} + +static int tps65911_get_ctrl_register(int id) +{ + switch (id) { + case TPS65910_REG_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 TPS65911_REG_VDDCTRL: + return TPS65911_VDDCTRL; + case TPS65911_REG_LDO1: + return TPS65911_LDO1; + case TPS65911_REG_LDO2: + return TPS65911_LDO2; + case TPS65911_REG_LDO3: + return TPS65911_LDO3; + case TPS65911_REG_LDO4: + return TPS65911_LDO4; + case TPS65911_REG_LDO5: + return TPS65911_LDO5; + case TPS65911_REG_LDO6: + return TPS65911_LDO6; + case TPS65911_REG_LDO7: + return TPS65911_LDO7; + case TPS65911_REG_LDO8: + return TPS65911_LDO8; + default: + return -EINVAL; + } +} + +static int tps65910_set_mode(struct regulator_dev *dev, unsigned int mode) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + struct tps65910 *mfd = pmic->mfd; + int reg, value, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + return tps65910_reg_update_bits(pmic->mfd, reg, + LDO_ST_MODE_BIT | LDO_ST_ON_BIT, + LDO_ST_ON_BIT); + case REGULATOR_MODE_IDLE: + value = LDO_ST_ON_BIT | LDO_ST_MODE_BIT; + return tps65910_reg_set_bits(mfd, reg, value); + case REGULATOR_MODE_STANDBY: + return tps65910_reg_clear_bits(mfd, reg, LDO_ST_ON_BIT); + } + + return -EINVAL; +} + +static unsigned int tps65910_get_mode(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int ret, reg, value, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + ret = tps65910_reg_read(pmic->mfd, reg, &value); + if (ret < 0) + return ret; + + 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_sel(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int ret, id = rdev_get_id(dev); + int opvsel = 0, srvsel = 0, vselmax = 0, mult = 0, sr = 0; + + switch (id) { + case TPS65910_REG_VDD1: + ret = tps65910_reg_read(pmic->mfd, TPS65910_VDD1_OP, &opvsel); + if (ret < 0) + return ret; + ret = tps65910_reg_read(pmic->mfd, TPS65910_VDD1, &mult); + if (ret < 0) + return ret; + mult = (mult & VDD1_VGAIN_SEL_MASK) >> VDD1_VGAIN_SEL_SHIFT; + ret = tps65910_reg_read(pmic->mfd, TPS65910_VDD1_SR, &srvsel); + if (ret < 0) + return ret; + sr = opvsel & VDD1_OP_CMD_MASK; + opvsel &= VDD1_OP_SEL_MASK; + srvsel &= VDD1_SR_SEL_MASK; + vselmax = 75; + break; + case TPS65910_REG_VDD2: + ret = tps65910_reg_read(pmic->mfd, TPS65910_VDD2_OP, &opvsel); + if (ret < 0) + return ret; + ret = tps65910_reg_read(pmic->mfd, TPS65910_VDD2, &mult); + if (ret < 0) + return ret; + mult = (mult & VDD2_VGAIN_SEL_MASK) >> VDD2_VGAIN_SEL_SHIFT; + ret = tps65910_reg_read(pmic->mfd, TPS65910_VDD2_SR, &srvsel); + if (ret < 0) + return ret; + sr = opvsel & VDD2_OP_CMD_MASK; + opvsel &= VDD2_OP_SEL_MASK; + srvsel &= VDD2_SR_SEL_MASK; + vselmax = 75; + break; + case TPS65911_REG_VDDCTRL: + ret = tps65910_reg_read(pmic->mfd, TPS65911_VDDCTRL_OP, + &opvsel); + if (ret < 0) + return ret; + ret = tps65910_reg_read(pmic->mfd, TPS65911_VDDCTRL_SR, + &srvsel); + if (ret < 0) + return ret; + sr = opvsel & VDDCTRL_OP_CMD_MASK; + opvsel &= VDDCTRL_OP_SEL_MASK; + srvsel &= VDDCTRL_SR_SEL_MASK; + vselmax = 64; + break; + } + + /* multiplier 0 == 1 but 2,3 normal */ + if (!mult) + mult = 1; + + if (sr) { + /* normalise to valid range */ + if (srvsel < 3) + srvsel = 3; + if (srvsel > vselmax) + srvsel = vselmax; + return srvsel - 3; + } else { + + /* normalise to valid range*/ + if (opvsel < 3) + opvsel = 3; + if (opvsel > vselmax) + opvsel = vselmax; + return opvsel - 3; + } + return -EINVAL; +} + +static int tps65910_get_voltage_sel(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int ret, reg, value, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + ret = tps65910_reg_read(pmic->mfd, reg, &value); + if (ret < 0) + return ret; + + switch (id) { + 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; + case TPS65910_REG_VBB: + value &= BBCH_BBSEL_MASK; + value >>= BBCH_BBSEL_SHIFT; + break; + default: + return -EINVAL; + } + + return value; +} + +static int tps65910_get_voltage_vdd3(struct regulator_dev *dev) +{ + return dev->desc->volt_table[0]; +} + +static int tps65911_get_voltage_sel(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int ret, id = rdev_get_id(dev); + unsigned int value, reg; + + reg = pmic->get_ctrl_reg(id); + + ret = tps65910_reg_read(pmic->mfd, reg, &value); + if (ret < 0) + return ret; + + switch (id) { + case TPS65911_REG_LDO1: + case TPS65911_REG_LDO2: + case TPS65911_REG_LDO4: + value &= LDO1_SEL_MASK; + value >>= LDO_SEL_SHIFT; + break; + case TPS65911_REG_LDO3: + case TPS65911_REG_LDO5: + case TPS65911_REG_LDO6: + case TPS65911_REG_LDO7: + case TPS65911_REG_LDO8: + value &= LDO3_SEL_MASK; + value >>= LDO_SEL_SHIFT; + break; + case TPS65910_REG_VIO: + value &= LDO_SEL_MASK; + value >>= LDO_SEL_SHIFT; + break; + default: + return -EINVAL; + } + + return value; +} + +static int tps65910_set_voltage_dcdc_sel(struct regulator_dev *dev, + unsigned selector) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int id = rdev_get_id(dev), vsel; + int dcdc_mult = 0; + + switch (id) { + case TPS65910_REG_VDD1: + dcdc_mult = (selector / VDD1_2_NUM_VOLT_FINE) + 1; + if (dcdc_mult == 1) + dcdc_mult--; + vsel = (selector % VDD1_2_NUM_VOLT_FINE) + 3; + + tps65910_reg_update_bits(pmic->mfd, TPS65910_VDD1, + VDD1_VGAIN_SEL_MASK, + dcdc_mult << VDD1_VGAIN_SEL_SHIFT); + tps65910_reg_write(pmic->mfd, TPS65910_VDD1_OP, vsel); + break; + case TPS65910_REG_VDD2: + dcdc_mult = (selector / VDD1_2_NUM_VOLT_FINE) + 1; + if (dcdc_mult == 1) + dcdc_mult--; + vsel = (selector % VDD1_2_NUM_VOLT_FINE) + 3; + + tps65910_reg_update_bits(pmic->mfd, TPS65910_VDD2, + VDD1_VGAIN_SEL_MASK, + dcdc_mult << VDD2_VGAIN_SEL_SHIFT); + tps65910_reg_write(pmic->mfd, TPS65910_VDD2_OP, vsel); + break; + case TPS65911_REG_VDDCTRL: + vsel = selector + 3; + tps65910_reg_write(pmic->mfd, TPS65911_VDDCTRL_OP, vsel); + } + + return 0; +} + +static int tps65910_set_voltage_sel(struct regulator_dev *dev, + unsigned selector) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int reg, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + switch (id) { + 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: + return tps65910_reg_update_bits(pmic->mfd, reg, LDO_SEL_MASK, + selector << LDO_SEL_SHIFT); + case TPS65910_REG_VBB: + return tps65910_reg_update_bits(pmic->mfd, reg, BBCH_BBSEL_MASK, + selector << BBCH_BBSEL_SHIFT); + } + + return -EINVAL; +} + +static int tps65911_set_voltage_sel(struct regulator_dev *dev, + unsigned selector) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int reg, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + switch (id) { + case TPS65911_REG_LDO1: + case TPS65911_REG_LDO2: + case TPS65911_REG_LDO4: + return tps65910_reg_update_bits(pmic->mfd, reg, LDO1_SEL_MASK, + selector << LDO_SEL_SHIFT); + case TPS65911_REG_LDO3: + case TPS65911_REG_LDO5: + case TPS65911_REG_LDO6: + case TPS65911_REG_LDO7: + case TPS65911_REG_LDO8: + return tps65910_reg_update_bits(pmic->mfd, reg, LDO3_SEL_MASK, + selector << LDO_SEL_SHIFT); + case TPS65910_REG_VIO: + return tps65910_reg_update_bits(pmic->mfd, reg, LDO_SEL_MASK, + selector << LDO_SEL_SHIFT); + case TPS65910_REG_VBB: + return tps65910_reg_update_bits(pmic->mfd, reg, BBCH_BBSEL_MASK, + selector << BBCH_BBSEL_SHIFT); + } + + return -EINVAL; +} + + +static int tps65910_list_voltage_dcdc(struct regulator_dev *dev, + unsigned selector) +{ + int volt, mult = 1, id = rdev_get_id(dev); + + switch (id) { + case TPS65910_REG_VDD1: + case TPS65910_REG_VDD2: + mult = (selector / VDD1_2_NUM_VOLT_FINE) + 1; + volt = VDD1_2_MIN_VOLT + + (selector % VDD1_2_NUM_VOLT_FINE) * VDD1_2_OFFSET; + break; + case TPS65911_REG_VDDCTRL: + volt = VDDCTRL_MIN_VOLT + (selector * VDDCTRL_OFFSET); + break; + default: + BUG(); + return -EINVAL; + } + + return volt * 100 * mult; +} + +static int tps65911_list_voltage(struct regulator_dev *dev, unsigned selector) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int step_mv = 0, id = rdev_get_id(dev); + + switch (id) { + case TPS65911_REG_LDO1: + case TPS65911_REG_LDO2: + case TPS65911_REG_LDO4: + /* The first 5 values of the selector correspond to 1V */ + if (selector < 5) + selector = 0; + else + selector -= 4; + + step_mv = 50; + break; + case TPS65911_REG_LDO3: + case TPS65911_REG_LDO5: + case TPS65911_REG_LDO6: + case TPS65911_REG_LDO7: + case TPS65911_REG_LDO8: + /* The first 3 values of the selector correspond to 1V */ + if (selector < 3) + selector = 0; + else + selector -= 2; + + step_mv = 100; + break; + case TPS65910_REG_VIO: + return pmic->info[id]->voltage_table[selector]; + default: + return -EINVAL; + } + + return (LDO_MIN_VOLT + selector * step_mv) * 1000; +} + +/* Regulator ops (except VRTC) */ +static struct regulator_ops tps65910_ops_dcdc = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .set_mode = tps65910_set_mode, + .get_mode = tps65910_get_mode, + .get_voltage_sel = tps65910_get_voltage_dcdc_sel, + .set_voltage_sel = tps65910_set_voltage_dcdc_sel, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .list_voltage = tps65910_list_voltage_dcdc, + .map_voltage = regulator_map_voltage_ascend, +}; + +static struct regulator_ops tps65910_ops_vdd3 = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .set_mode = tps65910_set_mode, + .get_mode = tps65910_get_mode, + .get_voltage = tps65910_get_voltage_vdd3, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, +}; + +static struct regulator_ops tps65910_ops_vbb = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .set_mode = tps65910_set_mode, + .get_mode = tps65910_get_mode, + .get_voltage_sel = tps65910_get_voltage_sel, + .set_voltage_sel = tps65910_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, +}; + +static struct regulator_ops tps65910_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .set_mode = tps65910_set_mode, + .get_mode = tps65910_get_mode, + .get_voltage_sel = tps65910_get_voltage_sel, + .set_voltage_sel = tps65910_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, +}; + +static struct regulator_ops tps65911_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .set_mode = tps65910_set_mode, + .get_mode = tps65910_get_mode, + .get_voltage_sel = tps65911_get_voltage_sel, + .set_voltage_sel = tps65911_set_voltage_sel, + .list_voltage = tps65911_list_voltage, + .map_voltage = regulator_map_voltage_ascend, +}; + +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); + en_count += ((ext_sleep_config & + TPS65911_SLEEP_CONTROL_EXT_INPUT_SLEEP) != 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_reg_set_bits(mfd, + TPS65910_EN1_LDO_ASS + regoffs, bit_pos); + else + ret = tps65910_reg_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_reg_set_bits(mfd, + TPS65910_EN2_LDO_ASS + regoffs, bit_pos); + else + ret = tps65910_reg_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_reg_set_bits(mfd, + TPS65910_EN3_LDO_ASS + regoffs, bit_pos); + else + ret = tps65910_reg_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_reg_clear_bits(mfd, + TPS65910_SLEEP_KEEP_LDO_ON + regoffs, bit_pos); + if (!ret) + ret = tps65910_reg_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, srvsel; + + ret = tps65910_reg_read(pmic->mfd, op_reg_add, &opvsel); + if (ret < 0) + return ret; + ret = tps65910_reg_read(pmic->mfd, sr_reg_add, &srvsel); + if (ret < 0) + return ret; + + if (opvsel & VDD1_OP_CMD_MASK) { + u8 reg_val = srvsel & VDD1_OP_SEL_MASK; + + ret = tps65910_reg_write(pmic->mfd, 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->mfd, sr_reg_add, 0); + if (ret < 0) { + dev_err(mfd->dev, "Error in setting sr register\n"); + return ret; + } + } + + ret = tps65910_reg_clear_bits(mfd, + TPS65910_SLEEP_KEEP_LDO_ON + regoffs, bit_pos); + if (!ret) { + if (ext_sleep_config & TPS65911_SLEEP_CONTROL_EXT_INPUT_SLEEP) + ret = tps65910_reg_set_bits(mfd, + TPS65910_SLEEP_SET_LDO_OFF + regoffs, bit_pos); + else + ret = tps65910_reg_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; +} + +#ifdef CONFIG_OF + +static struct of_regulator_match tps65910_matches[] = { + { .name = "vrtc", .driver_data = (void *) &tps65910_regs[0] }, + { .name = "vio", .driver_data = (void *) &tps65910_regs[1] }, + { .name = "vdd1", .driver_data = (void *) &tps65910_regs[2] }, + { .name = "vdd2", .driver_data = (void *) &tps65910_regs[3] }, + { .name = "vdd3", .driver_data = (void *) &tps65910_regs[4] }, + { .name = "vdig1", .driver_data = (void *) &tps65910_regs[5] }, + { .name = "vdig2", .driver_data = (void *) &tps65910_regs[6] }, + { .name = "vpll", .driver_data = (void *) &tps65910_regs[7] }, + { .name = "vdac", .driver_data = (void *) &tps65910_regs[8] }, + { .name = "vaux1", .driver_data = (void *) &tps65910_regs[9] }, + { .name = "vaux2", .driver_data = (void *) &tps65910_regs[10] }, + { .name = "vaux33", .driver_data = (void *) &tps65910_regs[11] }, + { .name = "vmmc", .driver_data = (void *) &tps65910_regs[12] }, + { .name = "vbb", .driver_data = (void *) &tps65910_regs[13] }, +}; + +static struct of_regulator_match tps65911_matches[] = { + { .name = "vrtc", .driver_data = (void *) &tps65911_regs[0] }, + { .name = "vio", .driver_data = (void *) &tps65911_regs[1] }, + { .name = "vdd1", .driver_data = (void *) &tps65911_regs[2] }, + { .name = "vdd2", .driver_data = (void *) &tps65911_regs[3] }, + { .name = "vddctrl", .driver_data = (void *) &tps65911_regs[4] }, + { .name = "ldo1", .driver_data = (void *) &tps65911_regs[5] }, + { .name = "ldo2", .driver_data = (void *) &tps65911_regs[6] }, + { .name = "ldo3", .driver_data = (void *) &tps65911_regs[7] }, + { .name = "ldo4", .driver_data = (void *) &tps65911_regs[8] }, + { .name = "ldo5", .driver_data = (void *) &tps65911_regs[9] }, + { .name = "ldo6", .driver_data = (void *) &tps65911_regs[10] }, + { .name = "ldo7", .driver_data = (void *) &tps65911_regs[11] }, + { .name = "ldo8", .driver_data = (void *) &tps65911_regs[12] }, +}; + +static struct tps65910_board *tps65910_parse_dt_reg_data( + struct platform_device *pdev, + struct of_regulator_match **tps65910_reg_matches) +{ + struct tps65910_board *pmic_plat_data; + struct tps65910 *tps65910 = dev_get_drvdata(pdev->dev.parent); + struct device_node *np, *regulators; + struct of_regulator_match *matches; + unsigned int prop; + int idx = 0, ret, count; + + pmic_plat_data = devm_kzalloc(&pdev->dev, sizeof(*pmic_plat_data), + GFP_KERNEL); + if (!pmic_plat_data) + return NULL; + + np = pdev->dev.parent->of_node; + regulators = of_get_child_by_name(np, "regulators"); + if (!regulators) { + dev_err(&pdev->dev, "regulator node not found\n"); + return NULL; + } + + switch (tps65910_chip_id(tps65910)) { + case TPS65910: + count = ARRAY_SIZE(tps65910_matches); + matches = tps65910_matches; + break; + case TPS65911: + count = ARRAY_SIZE(tps65911_matches); + matches = tps65911_matches; + break; + default: + of_node_put(regulators); + dev_err(&pdev->dev, "Invalid tps chip version\n"); + return NULL; + } + + ret = of_regulator_match(&pdev->dev, regulators, matches, count); + of_node_put(regulators); + if (ret < 0) { + dev_err(&pdev->dev, "Error parsing regulator init data: %d\n", + ret); + return NULL; + } + + *tps65910_reg_matches = matches; + + for (idx = 0; idx < count; idx++) { + if (!matches[idx].of_node) + continue; + + pmic_plat_data->tps65910_pmic_init_data[idx] = + matches[idx].init_data; + + ret = of_property_read_u32(matches[idx].of_node, + "ti,regulator-ext-sleep-control", &prop); + if (!ret) + pmic_plat_data->regulator_ext_sleep_control[idx] = prop; + + } + + return pmic_plat_data; +} +#else +static inline struct tps65910_board *tps65910_parse_dt_reg_data( + struct platform_device *pdev, + struct of_regulator_match **tps65910_reg_matches) +{ + *tps65910_reg_matches = NULL; + return NULL; +} +#endif + +static int tps65910_probe(struct platform_device *pdev) +{ + struct tps65910 *tps65910 = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = { }; + struct tps_info *info; + struct regulator_dev *rdev; + struct tps65910_reg *pmic; + struct tps65910_board *pmic_plat_data; + struct of_regulator_match *tps65910_reg_matches = NULL; + int i, err; + + pmic_plat_data = dev_get_platdata(tps65910->dev); + if (!pmic_plat_data && tps65910->dev->of_node) + pmic_plat_data = tps65910_parse_dt_reg_data(pdev, + &tps65910_reg_matches); + + if (!pmic_plat_data) { + dev_err(&pdev->dev, "Platform data not found\n"); + return -EINVAL; + } + + pmic = devm_kzalloc(&pdev->dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + pmic->mfd = tps65910; + platform_set_drvdata(pdev, pmic); + + /* Give control of all register to control port */ + err = tps65910_reg_set_bits(pmic->mfd, TPS65910_DEVCTRL, + DEVCTRL_SR_CTL_I2C_SEL_MASK); + if (err < 0) + return err; + + switch (tps65910_chip_id(tps65910)) { + case TPS65910: + BUILD_BUG_ON(TPS65910_NUM_REGS < ARRAY_SIZE(tps65910_regs)); + 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; + /* Work around silicon erratum SWCZ010: output programmed + * voltage level can go higher than expected or crash + * Workaround: use no synchronization of DCDC clocks + */ + tps65910_reg_clear_bits(pmic->mfd, TPS65910_DCDCCTRL, + DCDCCTRL_DCDCCKSYNC_MASK); + break; + case TPS65911: + BUILD_BUG_ON(TPS65910_NUM_REGS < ARRAY_SIZE(tps65911_regs)); + 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: + dev_err(&pdev->dev, "Invalid tps chip version\n"); + return -ENODEV; + } + + pmic->desc = devm_kcalloc(&pdev->dev, + pmic->num_regulators, + sizeof(struct regulator_desc), + GFP_KERNEL); + if (!pmic->desc) + return -ENOMEM; + + pmic->info = devm_kcalloc(&pdev->dev, + pmic->num_regulators, + sizeof(struct tps_info *), + GFP_KERNEL); + if (!pmic->info) + return -ENOMEM; + + pmic->rdev = devm_kcalloc(&pdev->dev, + pmic->num_regulators, + sizeof(struct regulator_dev *), + GFP_KERNEL); + if (!pmic->rdev) + return -ENOMEM; + + for (i = 0; i < pmic->num_regulators; i++, info++) { + /* Register the regulators */ + pmic->info[i] = info; + + pmic->desc[i].name = info->name; + pmic->desc[i].supply_name = info->vin_name; + pmic->desc[i].id = i; + pmic->desc[i].n_voltages = info->n_voltages; + pmic->desc[i].enable_time = info->enable_time_us; + + if (i == TPS65910_REG_VDD1 || i == TPS65910_REG_VDD2) { + pmic->desc[i].ops = &tps65910_ops_dcdc; + pmic->desc[i].n_voltages = VDD1_2_NUM_VOLT_FINE * + VDD1_2_NUM_VOLT_COARSE; + pmic->desc[i].ramp_delay = 12500; + } else if (i == TPS65910_REG_VDD3) { + if (tps65910_chip_id(tps65910) == TPS65910) { + pmic->desc[i].ops = &tps65910_ops_vdd3; + pmic->desc[i].volt_table = info->voltage_table; + } else { + pmic->desc[i].ops = &tps65910_ops_dcdc; + pmic->desc[i].ramp_delay = 5000; + } + } else if (i == TPS65910_REG_VBB && + tps65910_chip_id(tps65910) == TPS65910) { + pmic->desc[i].ops = &tps65910_ops_vbb; + pmic->desc[i].volt_table = info->voltage_table; + } else { + if (tps65910_chip_id(tps65910) == TPS65910) { + pmic->desc[i].ops = &tps65910_ops; + pmic->desc[i].volt_table = info->voltage_table; + } else { + 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; + pmic->desc[i].enable_reg = pmic->get_ctrl_reg(i); + pmic->desc[i].enable_mask = TPS65910_SUPPLY_STATE_ENABLED; + + config.dev = tps65910->dev; + config.init_data = pmic_plat_data->tps65910_pmic_init_data[i]; + config.driver_data = pmic; + config.regmap = tps65910->regmap; + + if (tps65910_reg_matches) + config.of_node = tps65910_reg_matches[i].of_node; + + rdev = devm_regulator_register(&pdev->dev, &pmic->desc[i], + &config); + if (IS_ERR(rdev)) { + dev_err(tps65910->dev, + "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + + /* Save regulator for cleanup */ + pmic->rdev[i] = rdev; + } + 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", + }, + .probe = tps65910_probe, + .shutdown = tps65910_shutdown, +}; + +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 <gg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS65910/TPS65911 voltage regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps65910-pmic"); diff --git a/drivers/regulator/tps65912-regulator.c b/drivers/regulator/tps65912-regulator.c new file mode 100644 index 000000000..276faeddc --- /dev/null +++ b/drivers/regulator/tps65912-regulator.c @@ -0,0 +1,169 @@ +/* + * Regulator driver for TI TPS65912x PMICs + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ + * Andrew F. Davis <afd@ti.com> + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether expressed or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License version 2 for more details. + * + * Based on the TPS65218 driver and the previous TPS65912 driver by + * Margarita Olaya Cabrera <magi@slimlogic.co.uk> + */ + +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> + +#include <linux/mfd/tps65912.h> + +enum tps65912_regulators { DCDC1, DCDC2, DCDC3, DCDC4, LDO1, LDO2, LDO3, + LDO4, LDO5, LDO6, LDO7, LDO8, LDO9, LDO10 }; + +#define TPS65912_REGULATOR(_name, _id, _of_match, _ops, _vr, _er, _lr) \ + [_id] = { \ + .name = _name, \ + .of_match = _of_match, \ + .regulators_node = "regulators", \ + .id = _id, \ + .ops = &_ops, \ + .n_voltages = 64, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .vsel_reg = _vr, \ + .vsel_mask = 0x3f, \ + .enable_reg = _er, \ + .enable_mask = BIT(7), \ + .volt_table = NULL, \ + .linear_ranges = _lr, \ + .n_linear_ranges = ARRAY_SIZE(_lr), \ + } + +static const struct regulator_linear_range tps65912_dcdc_ranges[] = { + REGULATOR_LINEAR_RANGE(500000, 0x0, 0x3f, 50000), +}; + +static const struct regulator_linear_range tps65912_ldo_ranges[] = { + REGULATOR_LINEAR_RANGE(800000, 0x0, 0x20, 25000), + REGULATOR_LINEAR_RANGE(1650000, 0x21, 0x3c, 50000), + REGULATOR_LINEAR_RANGE(3100000, 0x3d, 0x3f, 100000), +}; + +/* Operations permitted on DCDCx */ +static struct regulator_ops tps65912_ops_dcdc = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, +}; + +/* Operations permitted on LDOx */ +static struct regulator_ops tps65912_ops_ldo = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, +}; + +static const struct regulator_desc regulators[] = { + TPS65912_REGULATOR("DCDC1", DCDC1, "dcdc1", tps65912_ops_dcdc, + TPS65912_DCDC1_OP, TPS65912_DCDC1_CTRL, + tps65912_dcdc_ranges), + TPS65912_REGULATOR("DCDC2", DCDC2, "dcdc2", tps65912_ops_dcdc, + TPS65912_DCDC2_OP, TPS65912_DCDC2_CTRL, + tps65912_dcdc_ranges), + TPS65912_REGULATOR("DCDC3", DCDC3, "dcdc3", tps65912_ops_dcdc, + TPS65912_DCDC3_OP, TPS65912_DCDC3_CTRL, + tps65912_dcdc_ranges), + TPS65912_REGULATOR("DCDC4", DCDC4, "dcdc4", tps65912_ops_dcdc, + TPS65912_DCDC4_OP, TPS65912_DCDC4_CTRL, + tps65912_dcdc_ranges), + TPS65912_REGULATOR("LDO1", LDO1, "ldo1", tps65912_ops_ldo, + TPS65912_LDO1_OP, TPS65912_LDO1_AVS, + tps65912_ldo_ranges), + TPS65912_REGULATOR("LDO2", LDO2, "ldo2", tps65912_ops_ldo, + TPS65912_LDO2_OP, TPS65912_LDO2_AVS, + tps65912_ldo_ranges), + TPS65912_REGULATOR("LDO3", LDO3, "ldo3", tps65912_ops_ldo, + TPS65912_LDO3_OP, TPS65912_LDO3_AVS, + tps65912_ldo_ranges), + TPS65912_REGULATOR("LDO4", LDO4, "ldo4", tps65912_ops_ldo, + TPS65912_LDO4_OP, TPS65912_LDO4_AVS, + tps65912_ldo_ranges), + TPS65912_REGULATOR("LDO5", LDO5, "ldo5", tps65912_ops_ldo, + TPS65912_LDO5, TPS65912_LDO5, + tps65912_ldo_ranges), + TPS65912_REGULATOR("LDO6", LDO6, "ldo6", tps65912_ops_ldo, + TPS65912_LDO6, TPS65912_LDO6, + tps65912_ldo_ranges), + TPS65912_REGULATOR("LDO7", LDO7, "ldo7", tps65912_ops_ldo, + TPS65912_LDO7, TPS65912_LDO7, + tps65912_ldo_ranges), + TPS65912_REGULATOR("LDO8", LDO8, "ldo8", tps65912_ops_ldo, + TPS65912_LDO8, TPS65912_LDO8, + tps65912_ldo_ranges), + TPS65912_REGULATOR("LDO9", LDO9, "ldo9", tps65912_ops_ldo, + TPS65912_LDO9, TPS65912_LDO9, + tps65912_ldo_ranges), + TPS65912_REGULATOR("LDO10", LDO10, "ldo10", tps65912_ops_ldo, + TPS65912_LDO10, TPS65912_LDO10, + tps65912_ldo_ranges), +}; + +static int tps65912_regulator_probe(struct platform_device *pdev) +{ + struct tps65912 *tps = dev_get_drvdata(pdev->dev.parent); + struct regulator_config config = { }; + struct regulator_dev *rdev; + int i; + + platform_set_drvdata(pdev, tps); + + config.dev = &pdev->dev; + config.driver_data = tps; + config.dev->of_node = tps->dev->of_node; + config.regmap = tps->regmap; + + for (i = 0; i < ARRAY_SIZE(regulators); i++) { + rdev = devm_regulator_register(&pdev->dev, ®ulators[i], + &config); + if (IS_ERR(rdev)) { + dev_err(tps->dev, "failed to register %s regulator\n", + pdev->name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct platform_device_id tps65912_regulator_id_table[] = { + { "tps65912-regulator", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, tps65912_regulator_id_table); + +static struct platform_driver tps65912_regulator_driver = { + .driver = { + .name = "tps65912-regulator", + }, + .probe = tps65912_regulator_probe, + .id_table = tps65912_regulator_id_table, +}; +module_platform_driver(tps65912_regulator_driver); + +MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>"); +MODULE_DESCRIPTION("TPS65912 voltage regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/tps80031-regulator.c b/drivers/regulator/tps80031-regulator.c new file mode 100644 index 000000000..100114740 --- /dev/null +++ b/drivers/regulator/tps80031-regulator.c @@ -0,0 +1,768 @@ +/* + * tps80031-regulator.c -- TI TPS80031 regulator driver. + * + * Regulator driver for TI TPS80031/TPS80032 Fully Integrated Power + * Management with Power Path and Battery Charger. + * + * Copyright (c) 2012, NVIDIA Corporation. + * + * Author: Laxman Dewangan <ldewangan@nvidia.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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mfd/tps80031.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/slab.h> + +/* Flags for DCDC Voltage reading */ +#define DCDC_OFFSET_EN BIT(0) +#define DCDC_EXTENDED_EN BIT(1) +#define TRACK_MODE_ENABLE BIT(2) + +#define SMPS_MULTOFFSET_VIO BIT(1) +#define SMPS_MULTOFFSET_SMPS1 BIT(3) +#define SMPS_MULTOFFSET_SMPS2 BIT(4) +#define SMPS_MULTOFFSET_SMPS3 BIT(6) +#define SMPS_MULTOFFSET_SMPS4 BIT(0) + +#define SMPS_CMD_MASK 0xC0 +#define SMPS_VSEL_MASK 0x3F +#define LDO_VSEL_MASK 0x1F +#define LDO_TRACK_VSEL_MASK 0x3F + +#define MISC2_LDOUSB_IN_VSYS BIT(4) +#define MISC2_LDOUSB_IN_PMID BIT(3) +#define MISC2_LDOUSB_IN_MASK 0x18 + +#define MISC2_LDO3_SEL_VIB_VAL BIT(0) +#define MISC2_LDO3_SEL_VIB_MASK 0x1 + +#define BOOST_HW_PWR_EN BIT(5) +#define BOOST_HW_PWR_EN_MASK BIT(5) + +#define OPA_MODE_EN BIT(6) +#define OPA_MODE_EN_MASK BIT(6) + +#define USB_VBUS_CTRL_SET 0x04 +#define USB_VBUS_CTRL_CLR 0x05 +#define VBUS_DISCHRG 0x20 + +struct tps80031_regulator_info { + /* Regulator register address.*/ + u8 trans_reg; + u8 state_reg; + u8 force_reg; + u8 volt_reg; + u8 volt_id; + + /*Power request bits */ + int preq_bit; + + /* used by regulator core */ + struct regulator_desc desc; + +}; + +struct tps80031_regulator { + struct device *dev; + struct regulator_dev *rdev; + struct tps80031_regulator_info *rinfo; + + u8 device_flags; + unsigned int config_flags; + unsigned int ext_ctrl_flag; +}; + +static inline struct device *to_tps80031_dev(struct regulator_dev *rdev) +{ + return rdev_get_dev(rdev)->parent->parent; +} + +static int tps80031_reg_is_enabled(struct regulator_dev *rdev) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + struct device *parent = to_tps80031_dev(rdev); + u8 reg_val; + int ret; + + if (ri->ext_ctrl_flag & TPS80031_EXT_PWR_REQ) + return true; + + ret = tps80031_read(parent, TPS80031_SLAVE_ID1, ri->rinfo->state_reg, + ®_val); + if (ret < 0) { + dev_err(&rdev->dev, "Reg 0x%02x read failed, err = %d\n", + ri->rinfo->state_reg, ret); + return ret; + } + return (reg_val & TPS80031_STATE_MASK) == TPS80031_STATE_ON; +} + +static int tps80031_reg_enable(struct regulator_dev *rdev) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + struct device *parent = to_tps80031_dev(rdev); + int ret; + + if (ri->ext_ctrl_flag & TPS80031_EXT_PWR_REQ) + return 0; + + ret = tps80031_update(parent, TPS80031_SLAVE_ID1, ri->rinfo->state_reg, + TPS80031_STATE_ON, TPS80031_STATE_MASK); + if (ret < 0) { + dev_err(&rdev->dev, "Reg 0x%02x update failed, err = %d\n", + ri->rinfo->state_reg, ret); + return ret; + } + return ret; +} + +static int tps80031_reg_disable(struct regulator_dev *rdev) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + struct device *parent = to_tps80031_dev(rdev); + int ret; + + if (ri->ext_ctrl_flag & TPS80031_EXT_PWR_REQ) + return 0; + + ret = tps80031_update(parent, TPS80031_SLAVE_ID1, ri->rinfo->state_reg, + TPS80031_STATE_OFF, TPS80031_STATE_MASK); + if (ret < 0) + dev_err(&rdev->dev, "Reg 0x%02x update failed, err = %d\n", + ri->rinfo->state_reg, ret); + return ret; +} + +/* DCDC voltages for the selector of 58 to 63 */ +static int tps80031_dcdc_voltages[4][5] = { + { 1350, 1500, 1800, 1900, 2100}, + { 1350, 1500, 1800, 1900, 2100}, + { 2084, 2315, 2778, 2932, 3241}, + { 4167, 2315, 2778, 2932, 3241}, +}; + +static int tps80031_dcdc_list_voltage(struct regulator_dev *rdev, unsigned sel) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + int volt_index = ri->device_flags & 0x3; + + if (sel == 0) + return 0; + else if (sel < 58) + return regulator_list_voltage_linear(rdev, sel - 1); + else + return tps80031_dcdc_voltages[volt_index][sel - 58] * 1000; +} + +static int tps80031_dcdc_set_voltage_sel(struct regulator_dev *rdev, + unsigned vsel) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + struct device *parent = to_tps80031_dev(rdev); + int ret; + u8 reg_val; + + if (ri->rinfo->force_reg) { + ret = tps80031_read(parent, ri->rinfo->volt_id, + ri->rinfo->force_reg, ®_val); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x read failed, e = %d\n", + ri->rinfo->force_reg, ret); + return ret; + } + if (!(reg_val & SMPS_CMD_MASK)) { + ret = tps80031_update(parent, ri->rinfo->volt_id, + ri->rinfo->force_reg, vsel, SMPS_VSEL_MASK); + if (ret < 0) + dev_err(ri->dev, + "reg 0x%02x update failed, e = %d\n", + ri->rinfo->force_reg, ret); + return ret; + } + } + ret = tps80031_update(parent, ri->rinfo->volt_id, + ri->rinfo->volt_reg, vsel, SMPS_VSEL_MASK); + if (ret < 0) + dev_err(ri->dev, "reg 0x%02x update failed, e = %d\n", + ri->rinfo->volt_reg, ret); + return ret; +} + +static int tps80031_dcdc_get_voltage_sel(struct regulator_dev *rdev) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + struct device *parent = to_tps80031_dev(rdev); + uint8_t vsel = 0; + int ret; + + if (ri->rinfo->force_reg) { + ret = tps80031_read(parent, ri->rinfo->volt_id, + ri->rinfo->force_reg, &vsel); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x read failed, e = %d\n", + ri->rinfo->force_reg, ret); + return ret; + } + + if (!(vsel & SMPS_CMD_MASK)) + return vsel & SMPS_VSEL_MASK; + } + ret = tps80031_read(parent, ri->rinfo->volt_id, + ri->rinfo->volt_reg, &vsel); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x read failed, e = %d\n", + ri->rinfo->volt_reg, ret); + return ret; + } + return vsel & SMPS_VSEL_MASK; +} + +static int tps80031_ldo_list_voltage(struct regulator_dev *rdev, + unsigned int sel) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + struct device *parent = to_tps80031_dev(rdev); + + /* Check for valid setting for TPS80031 or TPS80032-ES1.0 */ + if ((ri->rinfo->desc.id == TPS80031_REGULATOR_LDO2) && + (ri->device_flags & TRACK_MODE_ENABLE)) { + unsigned nvsel = (sel) & 0x1F; + if (((tps80031_get_chip_info(parent) == TPS80031) || + ((tps80031_get_chip_info(parent) == TPS80032) && + (tps80031_get_pmu_version(parent) == 0x0))) && + ((nvsel == 0x0) || (nvsel >= 0x19 && nvsel <= 0x1F))) { + dev_err(ri->dev, + "Invalid sel %d in track mode LDO2\n", + nvsel); + return -EINVAL; + } + } + + return regulator_list_voltage_linear(rdev, sel); +} + +static int tps80031_ldo_map_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + struct device *parent = to_tps80031_dev(rdev); + + /* Check for valid setting for TPS80031 or TPS80032-ES1.0 */ + if ((ri->rinfo->desc.id == TPS80031_REGULATOR_LDO2) && + (ri->device_flags & TRACK_MODE_ENABLE)) { + if (((tps80031_get_chip_info(parent) == TPS80031) || + ((tps80031_get_chip_info(parent) == TPS80032) && + (tps80031_get_pmu_version(parent) == 0x0)))) { + return regulator_map_voltage_iterate(rdev, min_uV, + max_uV); + } + } + + return regulator_map_voltage_linear(rdev, min_uV, max_uV); +} + +static int tps80031_vbus_is_enabled(struct regulator_dev *rdev) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + struct device *parent = to_tps80031_dev(rdev); + int ret = -EIO; + uint8_t ctrl1 = 0; + uint8_t ctrl3 = 0; + + ret = tps80031_read(parent, TPS80031_SLAVE_ID2, + TPS80031_CHARGERUSB_CTRL1, &ctrl1); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x read failed, e = %d\n", + TPS80031_CHARGERUSB_CTRL1, ret); + return ret; + } + ret = tps80031_read(parent, TPS80031_SLAVE_ID2, + TPS80031_CHARGERUSB_CTRL3, &ctrl3); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x read failed, e = %d\n", + TPS80031_CHARGERUSB_CTRL3, ret); + return ret; + } + if ((ctrl1 & OPA_MODE_EN) && (ctrl3 & BOOST_HW_PWR_EN)) + return 1; + return ret; +} + +static int tps80031_vbus_enable(struct regulator_dev *rdev) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + struct device *parent = to_tps80031_dev(rdev); + int ret; + + ret = tps80031_set_bits(parent, TPS80031_SLAVE_ID2, + TPS80031_CHARGERUSB_CTRL1, OPA_MODE_EN); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x read failed, e = %d\n", + TPS80031_CHARGERUSB_CTRL1, ret); + return ret; + } + + ret = tps80031_set_bits(parent, TPS80031_SLAVE_ID2, + TPS80031_CHARGERUSB_CTRL3, BOOST_HW_PWR_EN); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x read failed, e = %d\n", + TPS80031_CHARGERUSB_CTRL3, ret); + return ret; + } + return ret; +} + +static int tps80031_vbus_disable(struct regulator_dev *rdev) +{ + struct tps80031_regulator *ri = rdev_get_drvdata(rdev); + struct device *parent = to_tps80031_dev(rdev); + int ret = 0; + + if (ri->config_flags & TPS80031_VBUS_DISCHRG_EN_PDN) { + ret = tps80031_write(parent, TPS80031_SLAVE_ID2, + USB_VBUS_CTRL_SET, VBUS_DISCHRG); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x write failed, e = %d\n", + USB_VBUS_CTRL_SET, ret); + return ret; + } + } + + ret = tps80031_clr_bits(parent, TPS80031_SLAVE_ID2, + TPS80031_CHARGERUSB_CTRL1, OPA_MODE_EN); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x clearbit failed, e = %d\n", + TPS80031_CHARGERUSB_CTRL1, ret); + return ret; + } + + ret = tps80031_clr_bits(parent, TPS80031_SLAVE_ID2, + TPS80031_CHARGERUSB_CTRL3, BOOST_HW_PWR_EN); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x clearbit failed, e = %d\n", + TPS80031_CHARGERUSB_CTRL3, ret); + return ret; + } + + mdelay(DIV_ROUND_UP(ri->rinfo->desc.enable_time, 1000)); + if (ri->config_flags & TPS80031_VBUS_DISCHRG_EN_PDN) { + ret = tps80031_write(parent, TPS80031_SLAVE_ID2, + USB_VBUS_CTRL_CLR, VBUS_DISCHRG); + if (ret < 0) { + dev_err(ri->dev, "reg 0x%02x write failed, e = %d\n", + USB_VBUS_CTRL_CLR, ret); + return ret; + } + } + return ret; +} + +static struct regulator_ops tps80031_dcdc_ops = { + .list_voltage = tps80031_dcdc_list_voltage, + .set_voltage_sel = tps80031_dcdc_set_voltage_sel, + .get_voltage_sel = tps80031_dcdc_get_voltage_sel, + .enable = tps80031_reg_enable, + .disable = tps80031_reg_disable, + .is_enabled = tps80031_reg_is_enabled, +}; + +static struct regulator_ops tps80031_ldo_ops = { + .list_voltage = tps80031_ldo_list_voltage, + .map_voltage = tps80031_ldo_map_voltage, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = tps80031_reg_enable, + .disable = tps80031_reg_disable, + .is_enabled = tps80031_reg_is_enabled, +}; + +static struct regulator_ops tps80031_vbus_sw_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = tps80031_vbus_enable, + .disable = tps80031_vbus_disable, + .is_enabled = tps80031_vbus_is_enabled, +}; + +static struct regulator_ops tps80031_vbus_hw_ops = { + .list_voltage = regulator_list_voltage_linear, +}; + +static struct regulator_ops tps80031_ext_reg_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = tps80031_reg_enable, + .disable = tps80031_reg_disable, + .is_enabled = tps80031_reg_is_enabled, +}; + +/* Non-exiting default definition for some register */ +#define TPS80031_SMPS3_CFG_FORCE 0 +#define TPS80031_SMPS4_CFG_FORCE 0 + +#define TPS80031_VBUS_CFG_TRANS 0 +#define TPS80031_VBUS_CFG_STATE 0 + +#define TPS80031_REG_SMPS(_id, _volt_id, _pbit) \ +{ \ + .trans_reg = TPS80031_##_id##_CFG_TRANS, \ + .state_reg = TPS80031_##_id##_CFG_STATE, \ + .force_reg = TPS80031_##_id##_CFG_FORCE, \ + .volt_reg = TPS80031_##_id##_CFG_VOLTAGE, \ + .volt_id = TPS80031_SLAVE_##_volt_id, \ + .preq_bit = _pbit, \ + .desc = { \ + .name = "tps80031_"#_id, \ + .id = TPS80031_REGULATOR_##_id, \ + .n_voltages = 63, \ + .ops = &tps80031_dcdc_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .enable_time = 500, \ + }, \ +} + +#define TPS80031_REG_LDO(_id, _preq_bit) \ +{ \ + .trans_reg = TPS80031_##_id##_CFG_TRANS, \ + .state_reg = TPS80031_##_id##_CFG_STATE, \ + .volt_reg = TPS80031_##_id##_CFG_VOLTAGE, \ + .volt_id = TPS80031_SLAVE_ID1, \ + .preq_bit = _preq_bit, \ + .desc = { \ + .owner = THIS_MODULE, \ + .name = "tps80031_"#_id, \ + .id = TPS80031_REGULATOR_##_id, \ + .ops = &tps80031_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .min_uV = 1000000, \ + .uV_step = 100000, \ + .linear_min_sel = 1, \ + .n_voltages = 25, \ + .vsel_reg = TPS80031_##_id##_CFG_VOLTAGE, \ + .vsel_mask = LDO_VSEL_MASK, \ + .enable_time = 500, \ + }, \ +} + +#define TPS80031_REG_FIXED(_id, max_mV, _ops, _delay, _pbit) \ +{ \ + .trans_reg = TPS80031_##_id##_CFG_TRANS, \ + .state_reg = TPS80031_##_id##_CFG_STATE, \ + .volt_id = TPS80031_SLAVE_ID1, \ + .preq_bit = _pbit, \ + .desc = { \ + .name = "tps80031_"#_id, \ + .id = TPS80031_REGULATOR_##_id, \ + .min_uV = max_mV * 1000, \ + .n_voltages = 1, \ + .ops = &_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .enable_time = _delay, \ + }, \ +} + +static struct tps80031_regulator_info tps80031_rinfo[TPS80031_REGULATOR_MAX] = { + TPS80031_REG_SMPS(VIO, ID0, 4), + TPS80031_REG_SMPS(SMPS1, ID0, 0), + TPS80031_REG_SMPS(SMPS2, ID0, 1), + TPS80031_REG_SMPS(SMPS3, ID1, 2), + TPS80031_REG_SMPS(SMPS4, ID1, 3), + TPS80031_REG_LDO(VANA, -1), + TPS80031_REG_LDO(LDO1, 8), + TPS80031_REG_LDO(LDO2, 9), + TPS80031_REG_LDO(LDO3, 10), + TPS80031_REG_LDO(LDO4, 11), + TPS80031_REG_LDO(LDO5, 12), + TPS80031_REG_LDO(LDO6, 13), + TPS80031_REG_LDO(LDO7, 14), + TPS80031_REG_LDO(LDOLN, 15), + TPS80031_REG_LDO(LDOUSB, 5), + TPS80031_REG_FIXED(VBUS, 5000, tps80031_vbus_hw_ops, 100000, -1), + TPS80031_REG_FIXED(REGEN1, 3300, tps80031_ext_reg_ops, 0, 16), + TPS80031_REG_FIXED(REGEN2, 3300, tps80031_ext_reg_ops, 0, 17), + TPS80031_REG_FIXED(SYSEN, 3300, tps80031_ext_reg_ops, 0, 18), +}; + +static int tps80031_power_req_config(struct device *parent, + struct tps80031_regulator *ri, + struct tps80031_regulator_platform_data *tps80031_pdata) +{ + int ret = 0; + + if (ri->rinfo->preq_bit < 0) + goto skip_pwr_req_config; + + ret = tps80031_ext_power_req_config(parent, ri->ext_ctrl_flag, + ri->rinfo->preq_bit, ri->rinfo->state_reg, + ri->rinfo->trans_reg); + if (ret < 0) { + dev_err(ri->dev, "ext powerreq config failed, err = %d\n", ret); + return ret; + } + +skip_pwr_req_config: + if (tps80031_pdata->ext_ctrl_flag & TPS80031_PWR_ON_ON_SLEEP) { + ret = tps80031_update(parent, TPS80031_SLAVE_ID1, + ri->rinfo->trans_reg, TPS80031_TRANS_SLEEP_ON, + TPS80031_TRANS_SLEEP_MASK); + if (ret < 0) { + dev_err(ri->dev, "Reg 0x%02x update failed, e %d\n", + ri->rinfo->trans_reg, ret); + return ret; + } + } + return ret; +} + +static int tps80031_regulator_config(struct device *parent, + struct tps80031_regulator *ri, + struct tps80031_regulator_platform_data *tps80031_pdata) +{ + int ret = 0; + + switch (ri->rinfo->desc.id) { + case TPS80031_REGULATOR_LDOUSB: + if (ri->config_flags & (TPS80031_USBLDO_INPUT_VSYS | + TPS80031_USBLDO_INPUT_PMID)) { + unsigned val = 0; + if (ri->config_flags & TPS80031_USBLDO_INPUT_VSYS) + val = MISC2_LDOUSB_IN_VSYS; + else + val = MISC2_LDOUSB_IN_PMID; + + ret = tps80031_update(parent, TPS80031_SLAVE_ID1, + TPS80031_MISC2, val, + MISC2_LDOUSB_IN_MASK); + if (ret < 0) { + dev_err(ri->dev, + "LDOUSB config failed, e= %d\n", ret); + return ret; + } + } + break; + + case TPS80031_REGULATOR_LDO3: + if (ri->config_flags & TPS80031_LDO3_OUTPUT_VIB) { + ret = tps80031_update(parent, TPS80031_SLAVE_ID1, + TPS80031_MISC2, MISC2_LDO3_SEL_VIB_VAL, + MISC2_LDO3_SEL_VIB_MASK); + if (ret < 0) { + dev_err(ri->dev, + "LDO3 config failed, e = %d\n", ret); + return ret; + } + } + break; + + case TPS80031_REGULATOR_VBUS: + /* Provide SW control Ops if VBUS is SW control */ + if (!(ri->config_flags & TPS80031_VBUS_SW_ONLY)) + ri->rinfo->desc.ops = &tps80031_vbus_sw_ops; + break; + default: + break; + } + + /* Configure Active state to ON, SLEEP to OFF and OFF_state to OFF */ + ret = tps80031_update(parent, TPS80031_SLAVE_ID1, ri->rinfo->trans_reg, + TPS80031_TRANS_ACTIVE_ON | TPS80031_TRANS_SLEEP_OFF | + TPS80031_TRANS_OFF_OFF, TPS80031_TRANS_ACTIVE_MASK | + TPS80031_TRANS_SLEEP_MASK | TPS80031_TRANS_OFF_MASK); + if (ret < 0) { + dev_err(ri->dev, "trans reg update failed, e %d\n", ret); + return ret; + } + + return ret; +} + +static int check_smps_mode_mult(struct device *parent, + struct tps80031_regulator *ri) +{ + int mult_offset; + int ret; + u8 smps_offset; + u8 smps_mult; + + ret = tps80031_read(parent, TPS80031_SLAVE_ID1, + TPS80031_SMPS_OFFSET, &smps_offset); + if (ret < 0) { + dev_err(parent, "Error in reading smps offset register\n"); + return ret; + } + + ret = tps80031_read(parent, TPS80031_SLAVE_ID1, + TPS80031_SMPS_MULT, &smps_mult); + if (ret < 0) { + dev_err(parent, "Error in reading smps mult register\n"); + return ret; + } + + switch (ri->rinfo->desc.id) { + case TPS80031_REGULATOR_VIO: + mult_offset = SMPS_MULTOFFSET_VIO; + break; + case TPS80031_REGULATOR_SMPS1: + mult_offset = SMPS_MULTOFFSET_SMPS1; + break; + case TPS80031_REGULATOR_SMPS2: + mult_offset = SMPS_MULTOFFSET_SMPS2; + break; + case TPS80031_REGULATOR_SMPS3: + mult_offset = SMPS_MULTOFFSET_SMPS3; + break; + case TPS80031_REGULATOR_SMPS4: + mult_offset = SMPS_MULTOFFSET_SMPS4; + break; + case TPS80031_REGULATOR_LDO2: + ri->device_flags = smps_mult & BIT(5) ? TRACK_MODE_ENABLE : 0; + /* TRACK mode the ldo2 varies from 600mV to 1300mV */ + if (ri->device_flags & TRACK_MODE_ENABLE) { + ri->rinfo->desc.min_uV = 600000; + ri->rinfo->desc.uV_step = 12500; + ri->rinfo->desc.n_voltages = 57; + ri->rinfo->desc.vsel_mask = LDO_TRACK_VSEL_MASK; + } + return 0; + default: + return 0; + } + + ri->device_flags = (smps_offset & mult_offset) ? DCDC_OFFSET_EN : 0; + ri->device_flags |= (smps_mult & mult_offset) ? DCDC_EXTENDED_EN : 0; + switch (ri->device_flags) { + case 0: + ri->rinfo->desc.min_uV = 607700; + ri->rinfo->desc.uV_step = 12660; + break; + case DCDC_OFFSET_EN: + ri->rinfo->desc.min_uV = 700000; + ri->rinfo->desc.uV_step = 12500; + break; + case DCDC_EXTENDED_EN: + ri->rinfo->desc.min_uV = 1852000; + ri->rinfo->desc.uV_step = 38600; + break; + case DCDC_OFFSET_EN | DCDC_EXTENDED_EN: + ri->rinfo->desc.min_uV = 2161000; + ri->rinfo->desc.uV_step = 38600; + break; + } + return 0; +} + +static int tps80031_regulator_probe(struct platform_device *pdev) +{ + struct tps80031_platform_data *pdata; + struct tps80031_regulator_platform_data *tps_pdata; + struct tps80031_regulator *ri; + struct tps80031_regulator *pmic; + struct regulator_dev *rdev; + struct regulator_config config = { }; + struct tps80031 *tps80031_mfd = dev_get_drvdata(pdev->dev.parent); + int ret; + int num; + + pdata = dev_get_platdata(pdev->dev.parent); + + if (!pdata) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } + + pmic = devm_kcalloc(&pdev->dev, + TPS80031_REGULATOR_MAX, sizeof(*pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + for (num = 0; num < TPS80031_REGULATOR_MAX; ++num) { + tps_pdata = pdata->regulator_pdata[num]; + ri = &pmic[num]; + ri->rinfo = &tps80031_rinfo[num]; + ri->dev = &pdev->dev; + + check_smps_mode_mult(pdev->dev.parent, ri); + config.dev = &pdev->dev; + config.init_data = NULL; + config.driver_data = ri; + config.regmap = tps80031_mfd->regmap[ri->rinfo->volt_id]; + + if (tps_pdata) { + config.init_data = tps_pdata->reg_init_data; + ri->config_flags = tps_pdata->config_flags; + ri->ext_ctrl_flag = tps_pdata->ext_ctrl_flag; + ret = tps80031_regulator_config(pdev->dev.parent, + ri, tps_pdata); + if (ret < 0) { + dev_err(&pdev->dev, + "regulator config failed, e %d\n", ret); + return ret; + } + + ret = tps80031_power_req_config(pdev->dev.parent, + ri, tps_pdata); + if (ret < 0) { + dev_err(&pdev->dev, + "pwr_req config failed, err %d\n", ret); + return ret; + } + } + rdev = devm_regulator_register(&pdev->dev, &ri->rinfo->desc, + &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, + "register regulator failed %s\n", + ri->rinfo->desc.name); + return PTR_ERR(rdev); + } + ri->rdev = rdev; + } + + platform_set_drvdata(pdev, pmic); + return 0; +} + +static struct platform_driver tps80031_regulator_driver = { + .driver = { + .name = "tps80031-pmic", + }, + .probe = tps80031_regulator_probe, +}; + +static int __init tps80031_regulator_init(void) +{ + return platform_driver_register(&tps80031_regulator_driver); +} +subsys_initcall(tps80031_regulator_init); + +static void __exit tps80031_regulator_exit(void) +{ + platform_driver_unregister(&tps80031_regulator_driver); +} +module_exit(tps80031_regulator_exit); + +MODULE_ALIAS("platform:tps80031-regulator"); +MODULE_DESCRIPTION("Regulator Driver for TI TPS80031/TPS80032 PMIC"); +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/twl-regulator.c b/drivers/regulator/twl-regulator.c new file mode 100644 index 000000000..884c7505e --- /dev/null +++ b/drivers/regulator/twl-regulator.c @@ -0,0 +1,674 @@ +/* + * twl-regulator.c -- support regulators in twl4030/twl6030 family chips + * + * Copyright (C) 2008 David Brownell + * + * 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/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/twl.h> +#include <linux/delay.h> + +/* + * The TWL4030/TW5030/TPS659x0 family chips include power management, a + * USB OTG transceiver, an RTC, ADC, PWM, and lots more. Some versions + * include an audio codec, battery charger, and more voltage regulators. + * These chips are often used in OMAP-based systems. + * + * This driver implements software-based resource control for various + * voltage regulators. This is usually augmented with state machine + * based control. + */ + +struct twlreg_info { + /* start of regulator's PM_RECEIVER control register bank */ + u8 base; + + /* twl resource ID, for resource control state machine */ + u8 id; + + /* voltage in mV = table[VSEL]; table_len must be a power-of-two */ + u8 table_len; + const u16 *table; + + /* State REMAP default configuration */ + u8 remap; + + /* used by regulator core */ + struct regulator_desc desc; + + /* chip specific features */ + unsigned long features; + + /* data passed from board for external get/set voltage */ + void *data; +}; + + +/* LDO control registers ... offset is from the base of its register bank. + * The first three registers of all power resource banks help hardware to + * manage the various resource groups. + */ +/* Common offset in TWL4030/6030 */ +#define VREG_GRP 0 +/* TWL4030 register offsets */ +#define VREG_TYPE 1 +#define VREG_REMAP 2 +#define VREG_DEDICATED 3 /* LDO control */ +#define VREG_VOLTAGE_SMPS_4030 9 +/* TWL6030 register offsets */ +#define VREG_TRANS 1 +#define VREG_STATE 2 +#define VREG_VOLTAGE 3 +#define VREG_VOLTAGE_SMPS 4 + +static inline int +twlreg_read(struct twlreg_info *info, unsigned slave_subgp, unsigned offset) +{ + u8 value; + int status; + + status = twl_i2c_read_u8(slave_subgp, + &value, info->base + offset); + return (status < 0) ? status : value; +} + +static inline int +twlreg_write(struct twlreg_info *info, unsigned slave_subgp, unsigned offset, + u8 value) +{ + return twl_i2c_write_u8(slave_subgp, + value, info->base + offset); +} + +/*----------------------------------------------------------------------*/ + +/* generic power resource operations, which work on all regulators */ + +static int twlreg_grp(struct regulator_dev *rdev) +{ + return twlreg_read(rdev_get_drvdata(rdev), TWL_MODULE_PM_RECEIVER, + VREG_GRP); +} + +/* + * Enable/disable regulators by joining/leaving the P1 (processor) group. + * We assume nobody else is updating the DEV_GRP registers. + */ +/* definition for 4030 family */ +#define P3_GRP_4030 BIT(7) /* "peripherals" */ +#define P2_GRP_4030 BIT(6) /* secondary processor, modem, etc */ +#define P1_GRP_4030 BIT(5) /* CPU/Linux */ +/* definition for 6030 family */ +#define P3_GRP_6030 BIT(2) /* secondary processor, modem, etc */ +#define P2_GRP_6030 BIT(1) /* "peripherals" */ +#define P1_GRP_6030 BIT(0) /* CPU/Linux */ + +static int twl4030reg_is_enabled(struct regulator_dev *rdev) +{ + int state = twlreg_grp(rdev); + + if (state < 0) + return state; + + return state & P1_GRP_4030; +} + +#define PB_I2C_BUSY BIT(0) +#define PB_I2C_BWEN BIT(1) + +/* Wait until buffer empty/ready to send a word on power bus. */ +static int twl4030_wait_pb_ready(void) +{ + + int ret; + int timeout = 10; + u8 val; + + do { + ret = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &val, + TWL4030_PM_MASTER_PB_CFG); + if (ret < 0) + return ret; + + if (!(val & PB_I2C_BUSY)) + return 0; + + mdelay(1); + timeout--; + } while (timeout); + + return -ETIMEDOUT; +} + +/* Send a word over the powerbus */ +static int twl4030_send_pb_msg(unsigned msg) +{ + u8 val; + int ret; + + /* save powerbus configuration */ + ret = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &val, + TWL4030_PM_MASTER_PB_CFG); + if (ret < 0) + return ret; + + /* Enable i2c access to powerbus */ + ret = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, val | PB_I2C_BWEN, + TWL4030_PM_MASTER_PB_CFG); + if (ret < 0) + return ret; + + ret = twl4030_wait_pb_ready(); + if (ret < 0) + return ret; + + ret = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, msg >> 8, + TWL4030_PM_MASTER_PB_WORD_MSB); + if (ret < 0) + return ret; + + ret = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, msg & 0xff, + TWL4030_PM_MASTER_PB_WORD_LSB); + if (ret < 0) + return ret; + + ret = twl4030_wait_pb_ready(); + if (ret < 0) + return ret; + + /* Restore powerbus configuration */ + return twl_i2c_write_u8(TWL_MODULE_PM_MASTER, val, + TWL4030_PM_MASTER_PB_CFG); +} + +static int twl4030reg_enable(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int grp; + int ret; + + grp = twlreg_grp(rdev); + if (grp < 0) + return grp; + + grp |= P1_GRP_4030; + + ret = twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_GRP, grp); + + return ret; +} + +static int twl4030reg_disable(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int grp; + int ret; + + grp = twlreg_grp(rdev); + if (grp < 0) + return grp; + + grp &= ~(P1_GRP_4030 | P2_GRP_4030 | P3_GRP_4030); + + ret = twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_GRP, grp); + + return ret; +} + +static int twl4030reg_get_status(struct regulator_dev *rdev) +{ + int state = twlreg_grp(rdev); + + if (state < 0) + return state; + state &= 0x0f; + + /* assume state != WARM_RESET; we'd not be running... */ + if (!state) + return REGULATOR_STATUS_OFF; + return (state & BIT(3)) + ? REGULATOR_STATUS_NORMAL + : REGULATOR_STATUS_STANDBY; +} + +static int twl4030reg_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + unsigned message; + + /* We can only set the mode through state machine commands... */ + switch (mode) { + case REGULATOR_MODE_NORMAL: + message = MSG_SINGULAR(DEV_GRP_P1, info->id, RES_STATE_ACTIVE); + break; + case REGULATOR_MODE_STANDBY: + message = MSG_SINGULAR(DEV_GRP_P1, info->id, RES_STATE_SLEEP); + break; + default: + return -EINVAL; + } + + return twl4030_send_pb_msg(message); +} + +static inline unsigned int twl4030reg_map_mode(unsigned int mode) +{ + switch (mode) { + case RES_STATE_ACTIVE: + return REGULATOR_MODE_NORMAL; + case RES_STATE_SLEEP: + return REGULATOR_MODE_STANDBY; + default: + return REGULATOR_MODE_INVALID; + } +} + +/*----------------------------------------------------------------------*/ + +/* + * Support for adjustable-voltage LDOs uses a four bit (or less) voltage + * select field in its control register. We use tables indexed by VSEL + * to record voltages in milliVolts. (Accuracy is about three percent.) + * + * Note that VSEL values for VAUX2 changed in twl5030 and newer silicon; + * currently handled by listing two slightly different VAUX2 regulators, + * only one of which will be configured. + * + * VSEL values documented as "TI cannot support these values" are flagged + * in these tables as UNSUP() values; we normally won't assign them. + * + * VAUX3 at 3V is incorrectly listed in some TI manuals as unsupported. + * TI are revising the twl5030/tps659x0 specs to support that 3.0V setting. + */ +#define UNSUP_MASK 0x8000 + +#define UNSUP(x) (UNSUP_MASK | (x)) +#define IS_UNSUP(info, x) \ + ((UNSUP_MASK & (x)) && \ + !((info)->features & TWL4030_ALLOW_UNSUPPORTED)) +#define LDO_MV(x) (~UNSUP_MASK & (x)) + + +static const u16 VAUX1_VSEL_table[] = { + UNSUP(1500), UNSUP(1800), 2500, 2800, + 3000, 3000, 3000, 3000, +}; +static const u16 VAUX2_4030_VSEL_table[] = { + UNSUP(1000), UNSUP(1000), UNSUP(1200), 1300, + 1500, 1800, UNSUP(1850), 2500, + UNSUP(2600), 2800, UNSUP(2850), UNSUP(3000), + UNSUP(3150), UNSUP(3150), UNSUP(3150), UNSUP(3150), +}; +static const u16 VAUX2_VSEL_table[] = { + 1700, 1700, 1900, 1300, + 1500, 1800, 2000, 2500, + 2100, 2800, 2200, 2300, + 2400, 2400, 2400, 2400, +}; +static const u16 VAUX3_VSEL_table[] = { + 1500, 1800, 2500, 2800, + 3000, 3000, 3000, 3000, +}; +static const u16 VAUX4_VSEL_table[] = { + 700, 1000, 1200, UNSUP(1300), + 1500, 1800, UNSUP(1850), 2500, + UNSUP(2600), 2800, UNSUP(2850), UNSUP(3000), + UNSUP(3150), UNSUP(3150), UNSUP(3150), UNSUP(3150), +}; +static const u16 VMMC1_VSEL_table[] = { + 1850, 2850, 3000, 3150, +}; +static const u16 VMMC2_VSEL_table[] = { + UNSUP(1000), UNSUP(1000), UNSUP(1200), UNSUP(1300), + UNSUP(1500), UNSUP(1800), 1850, UNSUP(2500), + 2600, 2800, 2850, 3000, + 3150, 3150, 3150, 3150, +}; +static const u16 VPLL1_VSEL_table[] = { + 1000, 1200, 1300, 1800, + UNSUP(2800), UNSUP(3000), UNSUP(3000), UNSUP(3000), +}; +static const u16 VPLL2_VSEL_table[] = { + 700, 1000, 1200, 1300, + UNSUP(1500), 1800, UNSUP(1850), UNSUP(2500), + UNSUP(2600), UNSUP(2800), UNSUP(2850), UNSUP(3000), + UNSUP(3150), UNSUP(3150), UNSUP(3150), UNSUP(3150), +}; +static const u16 VSIM_VSEL_table[] = { + UNSUP(1000), UNSUP(1200), UNSUP(1300), 1800, + 2800, 3000, 3000, 3000, +}; +static const u16 VDAC_VSEL_table[] = { + 1200, 1300, 1800, 1800, +}; +static const u16 VIO_VSEL_table[] = { + 1800, 1850, +}; +static const u16 VINTANA2_VSEL_table[] = { + 2500, 2750, +}; + +static int twl4030ldo_list_voltage(struct regulator_dev *rdev, unsigned index) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int mV = info->table[index]; + + return IS_UNSUP(info, mV) ? 0 : (LDO_MV(mV) * 1000); +} + +static int +twl4030ldo_set_voltage_sel(struct regulator_dev *rdev, unsigned selector) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + + return twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE, + selector); +} + +static int twl4030ldo_get_voltage_sel(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int vsel = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE); + + if (vsel < 0) + return vsel; + + vsel &= info->table_len - 1; + return vsel; +} + +static struct regulator_ops twl4030ldo_ops = { + .list_voltage = twl4030ldo_list_voltage, + + .set_voltage_sel = twl4030ldo_set_voltage_sel, + .get_voltage_sel = twl4030ldo_get_voltage_sel, + + .enable = twl4030reg_enable, + .disable = twl4030reg_disable, + .is_enabled = twl4030reg_is_enabled, + + .set_mode = twl4030reg_set_mode, + + .get_status = twl4030reg_get_status, +}; + +static int +twl4030smps_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV, + unsigned *selector) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int vsel = DIV_ROUND_UP(min_uV - 600000, 12500); + + twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE_SMPS_4030, vsel); + + return 0; +} + +static int twl4030smps_get_voltage(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int vsel; + + vsel = twlreg_read(info, TWL_MODULE_PM_RECEIVER, + VREG_VOLTAGE_SMPS_4030); + + return vsel * 12500 + 600000; +} + +static struct regulator_ops twl4030smps_ops = { + .set_voltage = twl4030smps_set_voltage, + .get_voltage = twl4030smps_get_voltage, +}; + +/*----------------------------------------------------------------------*/ + +static struct regulator_ops twl4030fixed_ops = { + .list_voltage = regulator_list_voltage_linear, + + .enable = twl4030reg_enable, + .disable = twl4030reg_disable, + .is_enabled = twl4030reg_is_enabled, + + .set_mode = twl4030reg_set_mode, + + .get_status = twl4030reg_get_status, +}; + +/*----------------------------------------------------------------------*/ + +#define TWL4030_ADJUSTABLE_LDO(label, offset, num, turnon_delay, remap_conf) \ +static const struct twlreg_info TWL4030_INFO_##label = { \ + .base = offset, \ + .id = num, \ + .table_len = ARRAY_SIZE(label##_VSEL_table), \ + .table = label##_VSEL_table, \ + .remap = remap_conf, \ + .desc = { \ + .name = #label, \ + .id = TWL4030_REG_##label, \ + .n_voltages = ARRAY_SIZE(label##_VSEL_table), \ + .ops = &twl4030ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .enable_time = turnon_delay, \ + .of_map_mode = twl4030reg_map_mode, \ + }, \ + } + +#define TWL4030_ADJUSTABLE_SMPS(label, offset, num, turnon_delay, remap_conf) \ +static const struct twlreg_info TWL4030_INFO_##label = { \ + .base = offset, \ + .id = num, \ + .remap = remap_conf, \ + .desc = { \ + .name = #label, \ + .id = TWL4030_REG_##label, \ + .ops = &twl4030smps_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .enable_time = turnon_delay, \ + .of_map_mode = twl4030reg_map_mode, \ + }, \ + } + +#define TWL4030_FIXED_LDO(label, offset, mVolts, num, turnon_delay, \ + remap_conf) \ +static const struct twlreg_info TWLFIXED_INFO_##label = { \ + .base = offset, \ + .id = num, \ + .remap = remap_conf, \ + .desc = { \ + .name = #label, \ + .id = TWL4030##_REG_##label, \ + .n_voltages = 1, \ + .ops = &twl4030fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = mVolts * 1000, \ + .enable_time = turnon_delay, \ + .of_map_mode = twl4030reg_map_mode, \ + }, \ + } + +/* + * We list regulators here if systems need some level of + * software control over them after boot. + */ +TWL4030_ADJUSTABLE_LDO(VAUX1, 0x17, 1, 100, 0x08); +TWL4030_ADJUSTABLE_LDO(VAUX2_4030, 0x1b, 2, 100, 0x08); +TWL4030_ADJUSTABLE_LDO(VAUX2, 0x1b, 2, 100, 0x08); +TWL4030_ADJUSTABLE_LDO(VAUX3, 0x1f, 3, 100, 0x08); +TWL4030_ADJUSTABLE_LDO(VAUX4, 0x23, 4, 100, 0x08); +TWL4030_ADJUSTABLE_LDO(VMMC1, 0x27, 5, 100, 0x08); +TWL4030_ADJUSTABLE_LDO(VMMC2, 0x2b, 6, 100, 0x08); +TWL4030_ADJUSTABLE_LDO(VPLL1, 0x2f, 7, 100, 0x00); +TWL4030_ADJUSTABLE_LDO(VPLL2, 0x33, 8, 100, 0x08); +TWL4030_ADJUSTABLE_LDO(VSIM, 0x37, 9, 100, 0x00); +TWL4030_ADJUSTABLE_LDO(VDAC, 0x3b, 10, 100, 0x08); +TWL4030_ADJUSTABLE_LDO(VINTANA2, 0x43, 12, 100, 0x08); +TWL4030_ADJUSTABLE_LDO(VIO, 0x4b, 14, 1000, 0x08); +TWL4030_ADJUSTABLE_SMPS(VDD1, 0x55, 15, 1000, 0x08); +TWL4030_ADJUSTABLE_SMPS(VDD2, 0x63, 16, 1000, 0x08); +/* VUSBCP is managed *only* by the USB subchip */ +TWL4030_FIXED_LDO(VINTANA1, 0x3f, 1500, 11, 100, 0x08); +TWL4030_FIXED_LDO(VINTDIG, 0x47, 1500, 13, 100, 0x08); +TWL4030_FIXED_LDO(VUSB1V5, 0x71, 1500, 17, 100, 0x08); +TWL4030_FIXED_LDO(VUSB1V8, 0x74, 1800, 18, 100, 0x08); +TWL4030_FIXED_LDO(VUSB3V1, 0x77, 3100, 19, 150, 0x08); + +#define TWL_OF_MATCH(comp, family, label) \ + { \ + .compatible = comp, \ + .data = &family##_INFO_##label, \ + } + +#define TWL4030_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWL4030, label) +#define TWL6030_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWL6030, label) +#define TWL6032_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWL6032, label) +#define TWLFIXED_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWLFIXED, label) +#define TWLSMPS_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWLSMPS, label) + +static const struct of_device_id twl_of_match[] = { + TWL4030_OF_MATCH("ti,twl4030-vaux1", VAUX1), + TWL4030_OF_MATCH("ti,twl4030-vaux2", VAUX2_4030), + TWL4030_OF_MATCH("ti,twl5030-vaux2", VAUX2), + TWL4030_OF_MATCH("ti,twl4030-vaux3", VAUX3), + TWL4030_OF_MATCH("ti,twl4030-vaux4", VAUX4), + TWL4030_OF_MATCH("ti,twl4030-vmmc1", VMMC1), + TWL4030_OF_MATCH("ti,twl4030-vmmc2", VMMC2), + TWL4030_OF_MATCH("ti,twl4030-vpll1", VPLL1), + TWL4030_OF_MATCH("ti,twl4030-vpll2", VPLL2), + TWL4030_OF_MATCH("ti,twl4030-vsim", VSIM), + TWL4030_OF_MATCH("ti,twl4030-vdac", VDAC), + TWL4030_OF_MATCH("ti,twl4030-vintana2", VINTANA2), + TWL4030_OF_MATCH("ti,twl4030-vio", VIO), + TWL4030_OF_MATCH("ti,twl4030-vdd1", VDD1), + TWL4030_OF_MATCH("ti,twl4030-vdd2", VDD2), + TWLFIXED_OF_MATCH("ti,twl4030-vintana1", VINTANA1), + TWLFIXED_OF_MATCH("ti,twl4030-vintdig", VINTDIG), + TWLFIXED_OF_MATCH("ti,twl4030-vusb1v5", VUSB1V5), + TWLFIXED_OF_MATCH("ti,twl4030-vusb1v8", VUSB1V8), + TWLFIXED_OF_MATCH("ti,twl4030-vusb3v1", VUSB3V1), + {}, +}; +MODULE_DEVICE_TABLE(of, twl_of_match); + +static int twlreg_probe(struct platform_device *pdev) +{ + int id; + struct twlreg_info *info; + const struct twlreg_info *template; + struct regulator_init_data *initdata; + struct regulation_constraints *c; + struct regulator_dev *rdev; + const struct of_device_id *match; + struct regulator_config config = { }; + + match = of_match_device(twl_of_match, &pdev->dev); + if (!match) + return -ENODEV; + + template = match->data; + if (!template) + return -ENODEV; + + id = template->desc.id; + initdata = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node, + &template->desc); + if (!initdata) + return -EINVAL; + + info = devm_kmemdup(&pdev->dev, template, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + /* Constrain board-specific capabilities according to what + * this driver and the chip itself can actually do. + */ + c = &initdata->constraints; + c->valid_modes_mask &= REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; + c->valid_ops_mask &= REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS; + switch (id) { + case TWL4030_REG_VIO: + case TWL4030_REG_VDD1: + case TWL4030_REG_VDD2: + case TWL4030_REG_VPLL1: + case TWL4030_REG_VINTANA1: + case TWL4030_REG_VINTANA2: + case TWL4030_REG_VINTDIG: + c->always_on = true; + break; + default: + break; + } + + config.dev = &pdev->dev; + config.init_data = initdata; + config.driver_data = info; + config.of_node = pdev->dev.of_node; + + rdev = devm_regulator_register(&pdev->dev, &info->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "can't register %s, %ld\n", + info->desc.name, PTR_ERR(rdev)); + return PTR_ERR(rdev); + } + platform_set_drvdata(pdev, rdev); + + twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_REMAP, info->remap); + + /* NOTE: many regulators support short-circuit IRQs (presentable + * as REGULATOR_OVER_CURRENT notifications?) configured via: + * - SC_CONFIG + * - SC_DETECT1 (vintana2, vmmc1/2, vaux1/2/3/4) + * - SC_DETECT2 (vusb, vdac, vio, vdd1/2, vpll2) + * - IT_CONFIG + */ + + return 0; +} + +MODULE_ALIAS("platform:twl4030_reg"); + +static struct platform_driver twlreg_driver = { + .probe = twlreg_probe, + /* NOTE: short name, to work around driver model truncation of + * "twl_regulator.12" (and friends) to "twl_regulator.1". + */ + .driver = { + .name = "twl4030_reg", + .of_match_table = of_match_ptr(twl_of_match), + }, +}; + +static int __init twlreg_init(void) +{ + return platform_driver_register(&twlreg_driver); +} +subsys_initcall(twlreg_init); + +static void __exit twlreg_exit(void) +{ + platform_driver_unregister(&twlreg_driver); +} +module_exit(twlreg_exit) + +MODULE_DESCRIPTION("TWL4030 regulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/twl6030-regulator.c b/drivers/regulator/twl6030-regulator.c new file mode 100644 index 000000000..219cbd910 --- /dev/null +++ b/drivers/regulator/twl6030-regulator.c @@ -0,0 +1,791 @@ +/* + * Split TWL6030 logic from twl-regulator.c: + * Copyright (C) 2008 David Brownell + * + * Copyright (C) 2016 Nicolae Rosia <nicolae.rosia@gmail.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; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/twl.h> +#include <linux/delay.h> + +struct twlreg_info { + /* start of regulator's PM_RECEIVER control register bank */ + u8 base; + + /* twl resource ID, for resource control state machine */ + u8 id; + + /* chip constraints on regulator behavior */ + u16 min_mV; + + u8 flags; + + /* used by regulator core */ + struct regulator_desc desc; + + /* chip specific features */ + unsigned long features; + + /* data passed from board for external get/set voltage */ + void *data; +}; + + +/* LDO control registers ... offset is from the base of its register bank. + * The first three registers of all power resource banks help hardware to + * manage the various resource groups. + */ +/* Common offset in TWL4030/6030 */ +#define VREG_GRP 0 +/* TWL6030 register offsets */ +#define VREG_TRANS 1 +#define VREG_STATE 2 +#define VREG_VOLTAGE 3 +#define VREG_VOLTAGE_SMPS 4 +/* TWL6030 Misc register offsets */ +#define VREG_BC_ALL 1 +#define VREG_BC_REF 2 +#define VREG_BC_PROC 3 +#define VREG_BC_CLK_RST 4 + +/* TWL6030 LDO register values for CFG_STATE */ +#define TWL6030_CFG_STATE_OFF 0x00 +#define TWL6030_CFG_STATE_ON 0x01 +#define TWL6030_CFG_STATE_OFF2 0x02 +#define TWL6030_CFG_STATE_SLEEP 0x03 +#define TWL6030_CFG_STATE_GRP_SHIFT 5 +#define TWL6030_CFG_STATE_APP_SHIFT 2 +#define TWL6030_CFG_STATE_APP_MASK (0x03 << TWL6030_CFG_STATE_APP_SHIFT) +#define TWL6030_CFG_STATE_APP(v) (((v) & TWL6030_CFG_STATE_APP_MASK) >>\ + TWL6030_CFG_STATE_APP_SHIFT) + +/* Flags for SMPS Voltage reading */ +#define SMPS_OFFSET_EN BIT(0) +#define SMPS_EXTENDED_EN BIT(1) + +/* twl6032 SMPS EPROM values */ +#define TWL6030_SMPS_OFFSET 0xB0 +#define TWL6030_SMPS_MULT 0xB3 +#define SMPS_MULTOFFSET_SMPS4 BIT(0) +#define SMPS_MULTOFFSET_VIO BIT(1) +#define SMPS_MULTOFFSET_SMPS3 BIT(6) + +static inline int +twlreg_read(struct twlreg_info *info, unsigned slave_subgp, unsigned offset) +{ + u8 value; + int status; + + status = twl_i2c_read_u8(slave_subgp, + &value, info->base + offset); + return (status < 0) ? status : value; +} + +static inline int +twlreg_write(struct twlreg_info *info, unsigned slave_subgp, unsigned offset, + u8 value) +{ + return twl_i2c_write_u8(slave_subgp, + value, info->base + offset); +} + +/* generic power resource operations, which work on all regulators */ +static int twlreg_grp(struct regulator_dev *rdev) +{ + return twlreg_read(rdev_get_drvdata(rdev), TWL_MODULE_PM_RECEIVER, + VREG_GRP); +} + +/* + * Enable/disable regulators by joining/leaving the P1 (processor) group. + * We assume nobody else is updating the DEV_GRP registers. + */ +/* definition for 6030 family */ +#define P3_GRP_6030 BIT(2) /* secondary processor, modem, etc */ +#define P2_GRP_6030 BIT(1) /* "peripherals" */ +#define P1_GRP_6030 BIT(0) /* CPU/Linux */ + +static int twl6030reg_is_enabled(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int grp = 0, val; + + if (!(twl_class_is_6030() && (info->features & TWL6032_SUBCLASS))) { + grp = twlreg_grp(rdev); + if (grp < 0) + return grp; + grp &= P1_GRP_6030; + } else { + grp = 1; + } + + val = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_STATE); + val = TWL6030_CFG_STATE_APP(val); + + return grp && (val == TWL6030_CFG_STATE_ON); +} + +#define PB_I2C_BUSY BIT(0) +#define PB_I2C_BWEN BIT(1) + + +static int twl6030reg_enable(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int grp = 0; + int ret; + + if (!(twl_class_is_6030() && (info->features & TWL6032_SUBCLASS))) + grp = twlreg_grp(rdev); + if (grp < 0) + return grp; + + ret = twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_STATE, + grp << TWL6030_CFG_STATE_GRP_SHIFT | + TWL6030_CFG_STATE_ON); + return ret; +} + +static int twl6030reg_disable(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int grp = 0; + int ret; + + if (!(twl_class_is_6030() && (info->features & TWL6032_SUBCLASS))) + grp = P1_GRP_6030 | P2_GRP_6030 | P3_GRP_6030; + + /* For 6030, set the off state for all grps enabled */ + ret = twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_STATE, + (grp) << TWL6030_CFG_STATE_GRP_SHIFT | + TWL6030_CFG_STATE_OFF); + + return ret; +} + +static int twl6030reg_get_status(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int val; + + val = twlreg_grp(rdev); + if (val < 0) + return val; + + val = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_STATE); + + switch (TWL6030_CFG_STATE_APP(val)) { + case TWL6030_CFG_STATE_ON: + return REGULATOR_STATUS_NORMAL; + + case TWL6030_CFG_STATE_SLEEP: + return REGULATOR_STATUS_STANDBY; + + case TWL6030_CFG_STATE_OFF: + case TWL6030_CFG_STATE_OFF2: + default: + break; + } + + return REGULATOR_STATUS_OFF; +} + +static int twl6030reg_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int grp = 0; + int val; + + if (!(twl_class_is_6030() && (info->features & TWL6032_SUBCLASS))) + grp = twlreg_grp(rdev); + + if (grp < 0) + return grp; + + /* Compose the state register settings */ + val = grp << TWL6030_CFG_STATE_GRP_SHIFT; + /* We can only set the mode through state machine commands... */ + switch (mode) { + case REGULATOR_MODE_NORMAL: + val |= TWL6030_CFG_STATE_ON; + break; + case REGULATOR_MODE_STANDBY: + val |= TWL6030_CFG_STATE_SLEEP; + break; + + default: + return -EINVAL; + } + + return twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_STATE, val); +} + +static int twl6030coresmps_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + return -ENODEV; +} + +static int twl6030coresmps_get_voltage(struct regulator_dev *rdev) +{ + return -ENODEV; +} + +static struct regulator_ops twl6030coresmps_ops = { + .set_voltage = twl6030coresmps_set_voltage, + .get_voltage = twl6030coresmps_get_voltage, +}; + +static int twl6030ldo_list_voltage(struct regulator_dev *rdev, unsigned sel) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + + switch (sel) { + case 0: + return 0; + case 1 ... 24: + /* Linear mapping from 00000001 to 00011000: + * Absolute voltage value = 1.0 V + 0.1 V × (sel – 00000001) + */ + return (info->min_mV + 100 * (sel - 1)) * 1000; + case 25 ... 30: + return -EINVAL; + case 31: + return 2750000; + default: + return -EINVAL; + } +} + +static int +twl6030ldo_set_voltage_sel(struct regulator_dev *rdev, unsigned selector) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + + return twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE, + selector); +} + +static int twl6030ldo_get_voltage_sel(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int vsel = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE); + + return vsel; +} + +static struct regulator_ops twl6030ldo_ops = { + .list_voltage = twl6030ldo_list_voltage, + + .set_voltage_sel = twl6030ldo_set_voltage_sel, + .get_voltage_sel = twl6030ldo_get_voltage_sel, + + .enable = twl6030reg_enable, + .disable = twl6030reg_disable, + .is_enabled = twl6030reg_is_enabled, + + .set_mode = twl6030reg_set_mode, + + .get_status = twl6030reg_get_status, +}; + +static struct regulator_ops twl6030fixed_ops = { + .list_voltage = regulator_list_voltage_linear, + + .enable = twl6030reg_enable, + .disable = twl6030reg_disable, + .is_enabled = twl6030reg_is_enabled, + + .set_mode = twl6030reg_set_mode, + + .get_status = twl6030reg_get_status, +}; + +/* + * SMPS status and control + */ + +static int twl6030smps_list_voltage(struct regulator_dev *rdev, unsigned index) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + + int voltage = 0; + + switch (info->flags) { + case SMPS_OFFSET_EN: + voltage = 100000; + /* fall through */ + case 0: + switch (index) { + case 0: + voltage = 0; + break; + case 58: + voltage = 1350 * 1000; + break; + case 59: + voltage = 1500 * 1000; + break; + case 60: + voltage = 1800 * 1000; + break; + case 61: + voltage = 1900 * 1000; + break; + case 62: + voltage = 2100 * 1000; + break; + default: + voltage += (600000 + (12500 * (index - 1))); + } + break; + case SMPS_EXTENDED_EN: + switch (index) { + case 0: + voltage = 0; + break; + case 58: + voltage = 2084 * 1000; + break; + case 59: + voltage = 2315 * 1000; + break; + case 60: + voltage = 2778 * 1000; + break; + case 61: + voltage = 2932 * 1000; + break; + case 62: + voltage = 3241 * 1000; + break; + default: + voltage = (1852000 + (38600 * (index - 1))); + } + break; + case SMPS_OFFSET_EN | SMPS_EXTENDED_EN: + switch (index) { + case 0: + voltage = 0; + break; + case 58: + voltage = 4167 * 1000; + break; + case 59: + voltage = 2315 * 1000; + break; + case 60: + voltage = 2778 * 1000; + break; + case 61: + voltage = 2932 * 1000; + break; + case 62: + voltage = 3241 * 1000; + break; + default: + voltage = (2161000 + (38600 * (index - 1))); + } + break; + } + + return voltage; +} + +static int twl6030smps_map_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int vsel = 0; + + switch (info->flags) { + case 0: + if (min_uV == 0) + vsel = 0; + else if ((min_uV >= 600000) && (min_uV <= 1300000)) { + vsel = DIV_ROUND_UP(min_uV - 600000, 12500); + vsel++; + } + /* Values 1..57 for vsel are linear and can be calculated + * values 58..62 are non linear. + */ + else if ((min_uV > 1900000) && (min_uV <= 2100000)) + vsel = 62; + else if ((min_uV > 1800000) && (min_uV <= 1900000)) + vsel = 61; + else if ((min_uV > 1500000) && (min_uV <= 1800000)) + vsel = 60; + else if ((min_uV > 1350000) && (min_uV <= 1500000)) + vsel = 59; + else if ((min_uV > 1300000) && (min_uV <= 1350000)) + vsel = 58; + else + return -EINVAL; + break; + case SMPS_OFFSET_EN: + if (min_uV == 0) + vsel = 0; + else if ((min_uV >= 700000) && (min_uV <= 1420000)) { + vsel = DIV_ROUND_UP(min_uV - 700000, 12500); + vsel++; + } + /* Values 1..57 for vsel are linear and can be calculated + * values 58..62 are non linear. + */ + else if ((min_uV > 1900000) && (min_uV <= 2100000)) + vsel = 62; + else if ((min_uV > 1800000) && (min_uV <= 1900000)) + vsel = 61; + else if ((min_uV > 1500000) && (min_uV <= 1800000)) + vsel = 60; + else if ((min_uV > 1350000) && (min_uV <= 1500000)) + vsel = 59; + else + return -EINVAL; + break; + case SMPS_EXTENDED_EN: + if (min_uV == 0) { + vsel = 0; + } else if ((min_uV >= 1852000) && (max_uV <= 4013600)) { + vsel = DIV_ROUND_UP(min_uV - 1852000, 38600); + vsel++; + } + break; + case SMPS_OFFSET_EN|SMPS_EXTENDED_EN: + if (min_uV == 0) { + vsel = 0; + } else if ((min_uV >= 2161000) && (min_uV <= 4321000)) { + vsel = DIV_ROUND_UP(min_uV - 2161000, 38600); + vsel++; + } + break; + } + + return vsel; +} + +static int twl6030smps_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + + return twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE_SMPS, + selector); +} + +static int twl6030smps_get_voltage_sel(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + + return twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE_SMPS); +} + +static struct regulator_ops twlsmps_ops = { + .list_voltage = twl6030smps_list_voltage, + .map_voltage = twl6030smps_map_voltage, + + .set_voltage_sel = twl6030smps_set_voltage_sel, + .get_voltage_sel = twl6030smps_get_voltage_sel, + + .enable = twl6030reg_enable, + .disable = twl6030reg_disable, + .is_enabled = twl6030reg_is_enabled, + + .set_mode = twl6030reg_set_mode, + + .get_status = twl6030reg_get_status, +}; + +/*----------------------------------------------------------------------*/ + +#define TWL6030_ADJUSTABLE_SMPS(label) \ +static const struct twlreg_info TWL6030_INFO_##label = { \ + .desc = { \ + .name = #label, \ + .id = TWL6030_REG_##label, \ + .ops = &twl6030coresmps_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + } + +#define TWL6030_ADJUSTABLE_LDO(label, offset, min_mVolts) \ +static const struct twlreg_info TWL6030_INFO_##label = { \ + .base = offset, \ + .min_mV = min_mVolts, \ + .desc = { \ + .name = #label, \ + .id = TWL6030_REG_##label, \ + .n_voltages = 32, \ + .ops = &twl6030ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + } + +#define TWL6032_ADJUSTABLE_LDO(label, offset, min_mVolts) \ +static const struct twlreg_info TWL6032_INFO_##label = { \ + .base = offset, \ + .min_mV = min_mVolts, \ + .desc = { \ + .name = #label, \ + .id = TWL6032_REG_##label, \ + .n_voltages = 32, \ + .ops = &twl6030ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + } + +#define TWL6030_FIXED_LDO(label, offset, mVolts, turnon_delay) \ +static const struct twlreg_info TWLFIXED_INFO_##label = { \ + .base = offset, \ + .id = 0, \ + .min_mV = mVolts, \ + .desc = { \ + .name = #label, \ + .id = TWL6030##_REG_##label, \ + .n_voltages = 1, \ + .ops = &twl6030fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = mVolts * 1000, \ + .enable_time = turnon_delay, \ + .of_map_mode = NULL, \ + }, \ + } + +#define TWL6032_ADJUSTABLE_SMPS(label, offset) \ +static const struct twlreg_info TWLSMPS_INFO_##label = { \ + .base = offset, \ + .min_mV = 600, \ + .desc = { \ + .name = #label, \ + .id = TWL6032_REG_##label, \ + .n_voltages = 63, \ + .ops = &twlsmps_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + } + +/* VUSBCP is managed *only* by the USB subchip */ +/* 6030 REG with base as PMC Slave Misc : 0x0030 */ +/* Turnon-delay and remap configuration values for 6030 are not + verified since the specification is not public */ +TWL6030_ADJUSTABLE_SMPS(VDD1); +TWL6030_ADJUSTABLE_SMPS(VDD2); +TWL6030_ADJUSTABLE_SMPS(VDD3); +TWL6030_ADJUSTABLE_LDO(VAUX1_6030, 0x54, 1000); +TWL6030_ADJUSTABLE_LDO(VAUX2_6030, 0x58, 1000); +TWL6030_ADJUSTABLE_LDO(VAUX3_6030, 0x5c, 1000); +TWL6030_ADJUSTABLE_LDO(VMMC, 0x68, 1000); +TWL6030_ADJUSTABLE_LDO(VPP, 0x6c, 1000); +TWL6030_ADJUSTABLE_LDO(VUSIM, 0x74, 1000); +/* 6025 are renamed compared to 6030 versions */ +TWL6032_ADJUSTABLE_LDO(LDO2, 0x54, 1000); +TWL6032_ADJUSTABLE_LDO(LDO4, 0x58, 1000); +TWL6032_ADJUSTABLE_LDO(LDO3, 0x5c, 1000); +TWL6032_ADJUSTABLE_LDO(LDO5, 0x68, 1000); +TWL6032_ADJUSTABLE_LDO(LDO1, 0x6c, 1000); +TWL6032_ADJUSTABLE_LDO(LDO7, 0x74, 1000); +TWL6032_ADJUSTABLE_LDO(LDO6, 0x60, 1000); +TWL6032_ADJUSTABLE_LDO(LDOLN, 0x64, 1000); +TWL6032_ADJUSTABLE_LDO(LDOUSB, 0x70, 1000); +TWL6030_FIXED_LDO(VANA, 0x50, 2100, 0); +TWL6030_FIXED_LDO(VCXIO, 0x60, 1800, 0); +TWL6030_FIXED_LDO(VDAC, 0x64, 1800, 0); +TWL6030_FIXED_LDO(VUSB, 0x70, 3300, 0); +TWL6030_FIXED_LDO(V1V8, 0x16, 1800, 0); +TWL6030_FIXED_LDO(V2V1, 0x1c, 2100, 0); +TWL6032_ADJUSTABLE_SMPS(SMPS3, 0x34); +TWL6032_ADJUSTABLE_SMPS(SMPS4, 0x10); +TWL6032_ADJUSTABLE_SMPS(VIO, 0x16); + +static u8 twl_get_smps_offset(void) +{ + u8 value; + + twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &value, + TWL6030_SMPS_OFFSET); + return value; +} + +static u8 twl_get_smps_mult(void) +{ + u8 value; + + twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &value, + TWL6030_SMPS_MULT); + return value; +} + +#define TWL_OF_MATCH(comp, family, label) \ + { \ + .compatible = comp, \ + .data = &family##_INFO_##label, \ + } + +#define TWL6030_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWL6030, label) +#define TWL6032_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWL6032, label) +#define TWLFIXED_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWLFIXED, label) +#define TWLSMPS_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWLSMPS, label) + +static const struct of_device_id twl_of_match[] = { + TWL6030_OF_MATCH("ti,twl6030-vdd1", VDD1), + TWL6030_OF_MATCH("ti,twl6030-vdd2", VDD2), + TWL6030_OF_MATCH("ti,twl6030-vdd3", VDD3), + TWL6030_OF_MATCH("ti,twl6030-vaux1", VAUX1_6030), + TWL6030_OF_MATCH("ti,twl6030-vaux2", VAUX2_6030), + TWL6030_OF_MATCH("ti,twl6030-vaux3", VAUX3_6030), + TWL6030_OF_MATCH("ti,twl6030-vmmc", VMMC), + TWL6030_OF_MATCH("ti,twl6030-vpp", VPP), + TWL6030_OF_MATCH("ti,twl6030-vusim", VUSIM), + TWL6032_OF_MATCH("ti,twl6032-ldo2", LDO2), + TWL6032_OF_MATCH("ti,twl6032-ldo4", LDO4), + TWL6032_OF_MATCH("ti,twl6032-ldo3", LDO3), + TWL6032_OF_MATCH("ti,twl6032-ldo5", LDO5), + TWL6032_OF_MATCH("ti,twl6032-ldo1", LDO1), + TWL6032_OF_MATCH("ti,twl6032-ldo7", LDO7), + TWL6032_OF_MATCH("ti,twl6032-ldo6", LDO6), + TWL6032_OF_MATCH("ti,twl6032-ldoln", LDOLN), + TWL6032_OF_MATCH("ti,twl6032-ldousb", LDOUSB), + TWLFIXED_OF_MATCH("ti,twl6030-vana", VANA), + TWLFIXED_OF_MATCH("ti,twl6030-vcxio", VCXIO), + TWLFIXED_OF_MATCH("ti,twl6030-vdac", VDAC), + TWLFIXED_OF_MATCH("ti,twl6030-vusb", VUSB), + TWLFIXED_OF_MATCH("ti,twl6030-v1v8", V1V8), + TWLFIXED_OF_MATCH("ti,twl6030-v2v1", V2V1), + TWLSMPS_OF_MATCH("ti,twl6032-smps3", SMPS3), + TWLSMPS_OF_MATCH("ti,twl6032-smps4", SMPS4), + TWLSMPS_OF_MATCH("ti,twl6032-vio", VIO), + {}, +}; +MODULE_DEVICE_TABLE(of, twl_of_match); + +static int twlreg_probe(struct platform_device *pdev) +{ + int id; + struct twlreg_info *info; + const struct twlreg_info *template; + struct regulator_init_data *initdata; + struct regulation_constraints *c; + struct regulator_dev *rdev; + const struct of_device_id *match; + struct regulator_config config = { }; + + match = of_match_device(twl_of_match, &pdev->dev); + if (!match) + return -ENODEV; + + template = match->data; + if (!template) + return -ENODEV; + + id = template->desc.id; + initdata = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node, + &template->desc); + if (!initdata) + return -EINVAL; + + info = devm_kmemdup(&pdev->dev, template, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + /* Constrain board-specific capabilities according to what + * this driver and the chip itself can actually do. + */ + c = &initdata->constraints; + c->valid_modes_mask &= REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; + c->valid_ops_mask &= REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS; + + switch (id) { + case TWL6032_REG_SMPS3: + if (twl_get_smps_mult() & SMPS_MULTOFFSET_SMPS3) + info->flags |= SMPS_EXTENDED_EN; + if (twl_get_smps_offset() & SMPS_MULTOFFSET_SMPS3) + info->flags |= SMPS_OFFSET_EN; + break; + case TWL6032_REG_SMPS4: + if (twl_get_smps_mult() & SMPS_MULTOFFSET_SMPS4) + info->flags |= SMPS_EXTENDED_EN; + if (twl_get_smps_offset() & SMPS_MULTOFFSET_SMPS4) + info->flags |= SMPS_OFFSET_EN; + break; + case TWL6032_REG_VIO: + if (twl_get_smps_mult() & SMPS_MULTOFFSET_VIO) + info->flags |= SMPS_EXTENDED_EN; + if (twl_get_smps_offset() & SMPS_MULTOFFSET_VIO) + info->flags |= SMPS_OFFSET_EN; + break; + } + + config.dev = &pdev->dev; + config.init_data = initdata; + config.driver_data = info; + config.of_node = pdev->dev.of_node; + + rdev = devm_regulator_register(&pdev->dev, &info->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "can't register %s, %ld\n", + info->desc.name, PTR_ERR(rdev)); + return PTR_ERR(rdev); + } + platform_set_drvdata(pdev, rdev); + + /* NOTE: many regulators support short-circuit IRQs (presentable + * as REGULATOR_OVER_CURRENT notifications?) configured via: + * - SC_CONFIG + * - SC_DETECT1 (vintana2, vmmc1/2, vaux1/2/3/4) + * - SC_DETECT2 (vusb, vdac, vio, vdd1/2, vpll2) + * - IT_CONFIG + */ + + return 0; +} + +MODULE_ALIAS("platform:twl6030_reg"); + +static struct platform_driver twlreg_driver = { + .probe = twlreg_probe, + /* NOTE: short name, to work around driver model truncation of + * "twl_regulator.12" (and friends) to "twl_regulator.1". + */ + .driver = { + .name = "twl6030_reg", + .of_match_table = of_match_ptr(twl_of_match), + }, +}; + +static int __init twlreg_init(void) +{ + return platform_driver_register(&twlreg_driver); +} +subsys_initcall(twlreg_init); + +static void __exit twlreg_exit(void) +{ + platform_driver_unregister(&twlreg_driver); +} +module_exit(twlreg_exit) + +MODULE_DESCRIPTION("TWL6030 regulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/uniphier-regulator.c b/drivers/regulator/uniphier-regulator.c new file mode 100644 index 000000000..a2e3654b6 --- /dev/null +++ b/drivers/regulator/uniphier-regulator.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Regulator controller driver for UniPhier SoC +// Copyright 2018 Socionext Inc. +// Author: Kunihiko Hayashi <hayashi.kunihiko@socionext.com> + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/reset.h> + +#define MAX_CLKS 2 +#define MAX_RSTS 2 + +struct uniphier_regulator_soc_data { + int nclks; + const char * const *clock_names; + int nrsts; + const char * const *reset_names; + const struct regulator_desc *desc; + const struct regmap_config *regconf; +}; + +struct uniphier_regulator_priv { + struct clk_bulk_data clk[MAX_CLKS]; + struct reset_control *rst[MAX_RSTS]; + const struct uniphier_regulator_soc_data *data; +}; + +static struct regulator_ops uniphier_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static int uniphier_regulator_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct uniphier_regulator_priv *priv; + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct regmap *regmap; + struct resource *res; + void __iomem *base; + const char *name; + int i, ret, nr; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->data = of_device_get_match_data(dev); + if (WARN_ON(!priv->data)) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + for (i = 0; i < priv->data->nclks; i++) + priv->clk[i].id = priv->data->clock_names[i]; + ret = devm_clk_bulk_get(dev, priv->data->nclks, priv->clk); + if (ret) + return ret; + + for (i = 0; i < priv->data->nrsts; i++) { + name = priv->data->reset_names[i]; + priv->rst[i] = devm_reset_control_get_shared(dev, name); + if (IS_ERR(priv->rst[i])) + return PTR_ERR(priv->rst[i]); + } + + ret = clk_bulk_prepare_enable(priv->data->nclks, priv->clk); + if (ret) + return ret; + + for (nr = 0; nr < priv->data->nrsts; nr++) { + ret = reset_control_deassert(priv->rst[nr]); + if (ret) + goto out_rst_assert; + } + + regmap = devm_regmap_init_mmio(dev, base, priv->data->regconf); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + config.dev = dev; + config.driver_data = priv; + config.of_node = dev->of_node; + config.regmap = regmap; + config.init_data = of_get_regulator_init_data(dev, dev->of_node, + priv->data->desc); + rdev = devm_regulator_register(dev, priv->data->desc, &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + goto out_rst_assert; + } + + platform_set_drvdata(pdev, priv); + + return 0; + +out_rst_assert: + while (nr--) + reset_control_assert(priv->rst[nr]); + + clk_bulk_disable_unprepare(priv->data->nclks, priv->clk); + + return ret; +} + +static int uniphier_regulator_remove(struct platform_device *pdev) +{ + struct uniphier_regulator_priv *priv = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < priv->data->nrsts; i++) + reset_control_assert(priv->rst[i]); + + clk_bulk_disable_unprepare(priv->data->nclks, priv->clk); + + return 0; +} + +/* USB3 controller data */ +#define USB3VBUS_OFFSET 0x0 +#define USB3VBUS_REG BIT(4) +#define USB3VBUS_REG_EN BIT(3) +static const struct regulator_desc uniphier_usb3_regulator_desc = { + .name = "vbus", + .of_match = of_match_ptr("vbus"), + .ops = &uniphier_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = USB3VBUS_OFFSET, + .enable_mask = USB3VBUS_REG_EN | USB3VBUS_REG, + .enable_val = USB3VBUS_REG_EN | USB3VBUS_REG, + .disable_val = USB3VBUS_REG_EN, +}; + +static const struct regmap_config uniphier_usb3_regulator_regconf = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 1, +}; + +static const char * const uniphier_pro4_clock_reset_names[] = { + "gio", "link", +}; + +static const struct uniphier_regulator_soc_data uniphier_pro4_usb3_data = { + .nclks = ARRAY_SIZE(uniphier_pro4_clock_reset_names), + .clock_names = uniphier_pro4_clock_reset_names, + .nrsts = ARRAY_SIZE(uniphier_pro4_clock_reset_names), + .reset_names = uniphier_pro4_clock_reset_names, + .desc = &uniphier_usb3_regulator_desc, + .regconf = &uniphier_usb3_regulator_regconf, +}; + +static const char * const uniphier_pxs2_clock_reset_names[] = { + "link", +}; + +static const struct uniphier_regulator_soc_data uniphier_pxs2_usb3_data = { + .nclks = ARRAY_SIZE(uniphier_pxs2_clock_reset_names), + .clock_names = uniphier_pxs2_clock_reset_names, + .nrsts = ARRAY_SIZE(uniphier_pxs2_clock_reset_names), + .reset_names = uniphier_pxs2_clock_reset_names, + .desc = &uniphier_usb3_regulator_desc, + .regconf = &uniphier_usb3_regulator_regconf, +}; + +static const struct of_device_id uniphier_regulator_match[] = { + /* USB VBUS */ + { + .compatible = "socionext,uniphier-pro4-usb3-regulator", + .data = &uniphier_pro4_usb3_data, + }, + { + .compatible = "socionext,uniphier-pxs2-usb3-regulator", + .data = &uniphier_pxs2_usb3_data, + }, + { + .compatible = "socionext,uniphier-ld20-usb3-regulator", + .data = &uniphier_pxs2_usb3_data, + }, + { + .compatible = "socionext,uniphier-pxs3-usb3-regulator", + .data = &uniphier_pxs2_usb3_data, + }, + { /* Sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, uniphier_regulator_match); + +static struct platform_driver uniphier_regulator_driver = { + .probe = uniphier_regulator_probe, + .remove = uniphier_regulator_remove, + .driver = { + .name = "uniphier-regulator", + .of_match_table = uniphier_regulator_match, + }, +}; +module_platform_driver(uniphier_regulator_driver); + +MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>"); +MODULE_DESCRIPTION("UniPhier Regulator Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/userspace-consumer.c b/drivers/regulator/userspace-consumer.c new file mode 100644 index 000000000..765acc11c --- /dev/null +++ b/drivers/regulator/userspace-consumer.c @@ -0,0 +1,186 @@ +/* + * userspace-consumer.c + * + * Copyright 2009 CompuLab, Ltd. + * + * Author: Mike Rapoport <mike@compulab.co.il> + * + * Based of virtual consumer driver: + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the + * License, or (at your option) any later version. + * + */ + +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/userspace-consumer.h> +#include <linux/slab.h> + +struct userspace_consumer_data { + const char *name; + + struct mutex lock; + bool enabled; + + int num_supplies; + struct regulator_bulk_data *supplies; +}; + +static ssize_t reg_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct userspace_consumer_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + +static ssize_t reg_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct userspace_consumer_data *data = dev_get_drvdata(dev); + + if (data->enabled) + return sprintf(buf, "enabled\n"); + + return sprintf(buf, "disabled\n"); +} + +static ssize_t reg_set_state(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct userspace_consumer_data *data = dev_get_drvdata(dev); + bool enabled; + int ret; + + /* + * sysfs_streq() doesn't need the \n's, but we add them so the strings + * will be shared with show_state(), above. + */ + if (sysfs_streq(buf, "enabled\n") || sysfs_streq(buf, "1")) + enabled = true; + else if (sysfs_streq(buf, "disabled\n") || sysfs_streq(buf, "0")) + enabled = false; + else { + dev_err(dev, "Configuring invalid mode\n"); + return count; + } + + mutex_lock(&data->lock); + if (enabled != data->enabled) { + if (enabled) + ret = regulator_bulk_enable(data->num_supplies, + data->supplies); + else + ret = regulator_bulk_disable(data->num_supplies, + data->supplies); + + if (ret == 0) + data->enabled = enabled; + else + dev_err(dev, "Failed to configure state: %d\n", ret); + } + mutex_unlock(&data->lock); + + return count; +} + +static DEVICE_ATTR(name, 0444, reg_show_name, NULL); +static DEVICE_ATTR(state, 0644, reg_show_state, reg_set_state); + +static struct attribute *attributes[] = { + &dev_attr_name.attr, + &dev_attr_state.attr, + NULL, +}; + +static const struct attribute_group attr_group = { + .attrs = attributes, +}; + +static int regulator_userspace_consumer_probe(struct platform_device *pdev) +{ + struct regulator_userspace_consumer_data *pdata; + struct userspace_consumer_data *drvdata; + int ret; + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) + return -EINVAL; + + drvdata = devm_kzalloc(&pdev->dev, + sizeof(struct userspace_consumer_data), + GFP_KERNEL); + if (drvdata == NULL) + return -ENOMEM; + + drvdata->name = pdata->name; + drvdata->num_supplies = pdata->num_supplies; + drvdata->supplies = pdata->supplies; + + mutex_init(&drvdata->lock); + + ret = devm_regulator_bulk_get(&pdev->dev, drvdata->num_supplies, + drvdata->supplies); + if (ret) { + dev_err(&pdev->dev, "Failed to get supplies: %d\n", ret); + return ret; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &attr_group); + if (ret != 0) + return ret; + + if (pdata->init_on) { + ret = regulator_bulk_enable(drvdata->num_supplies, + drvdata->supplies); + if (ret) { + dev_err(&pdev->dev, + "Failed to set initial state: %d\n", ret); + goto err_enable; + } + } + + drvdata->enabled = pdata->init_on; + platform_set_drvdata(pdev, drvdata); + + return 0; + +err_enable: + sysfs_remove_group(&pdev->dev.kobj, &attr_group); + + return ret; +} + +static int regulator_userspace_consumer_remove(struct platform_device *pdev) +{ + struct userspace_consumer_data *data = platform_get_drvdata(pdev); + + sysfs_remove_group(&pdev->dev.kobj, &attr_group); + + if (data->enabled) + regulator_bulk_disable(data->num_supplies, data->supplies); + + return 0; +} + +static struct platform_driver regulator_userspace_consumer_driver = { + .probe = regulator_userspace_consumer_probe, + .remove = regulator_userspace_consumer_remove, + .driver = { + .name = "reg-userspace-consumer", + }, +}; + +module_platform_driver(regulator_userspace_consumer_driver); + +MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); +MODULE_DESCRIPTION("Userspace consumer for voltage and current regulators"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/vctrl-regulator.c b/drivers/regulator/vctrl-regulator.c new file mode 100644 index 000000000..78de00203 --- /dev/null +++ b/drivers/regulator/vctrl-regulator.c @@ -0,0 +1,546 @@ +/* + * Driver for voltage controller regulators + * + * Copyright (C) 2017 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/sort.h> + +struct vctrl_voltage_range { + int min_uV; + int max_uV; +}; + +struct vctrl_voltage_ranges { + struct vctrl_voltage_range ctrl; + struct vctrl_voltage_range out; +}; + +struct vctrl_voltage_table { + int ctrl; + int out; + int ovp_min_sel; +}; + +struct vctrl_data { + struct regulator_dev *rdev; + struct regulator_desc desc; + struct regulator *ctrl_reg; + bool enabled; + unsigned int min_slew_down_rate; + unsigned int ovp_threshold; + struct vctrl_voltage_ranges vrange; + struct vctrl_voltage_table *vtable; + unsigned int sel; +}; + +static int vctrl_calc_ctrl_voltage(struct vctrl_data *vctrl, int out_uV) +{ + struct vctrl_voltage_range *ctrl = &vctrl->vrange.ctrl; + struct vctrl_voltage_range *out = &vctrl->vrange.out; + + return ctrl->min_uV + + DIV_ROUND_CLOSEST_ULL((s64)(out_uV - out->min_uV) * + (ctrl->max_uV - ctrl->min_uV), + out->max_uV - out->min_uV); +} + +static int vctrl_calc_output_voltage(struct vctrl_data *vctrl, int ctrl_uV) +{ + struct vctrl_voltage_range *ctrl = &vctrl->vrange.ctrl; + struct vctrl_voltage_range *out = &vctrl->vrange.out; + + if (ctrl_uV < 0) { + pr_err("vctrl: failed to get control voltage\n"); + return ctrl_uV; + } + + if (ctrl_uV < ctrl->min_uV) + return out->min_uV; + + if (ctrl_uV > ctrl->max_uV) + return out->max_uV; + + return out->min_uV + + DIV_ROUND_CLOSEST_ULL((s64)(ctrl_uV - ctrl->min_uV) * + (out->max_uV - out->min_uV), + ctrl->max_uV - ctrl->min_uV); +} + +static int vctrl_get_voltage(struct regulator_dev *rdev) +{ + struct vctrl_data *vctrl = rdev_get_drvdata(rdev); + int ctrl_uV = regulator_get_voltage(vctrl->ctrl_reg); + + return vctrl_calc_output_voltage(vctrl, ctrl_uV); +} + +static int vctrl_set_voltage(struct regulator_dev *rdev, + int req_min_uV, int req_max_uV, + unsigned int *selector) +{ + struct vctrl_data *vctrl = rdev_get_drvdata(rdev); + struct regulator *ctrl_reg = vctrl->ctrl_reg; + int orig_ctrl_uV = regulator_get_voltage(ctrl_reg); + int uV = vctrl_calc_output_voltage(vctrl, orig_ctrl_uV); + int ret; + + if (req_min_uV >= uV || !vctrl->ovp_threshold) + /* voltage rising or no OVP */ + return regulator_set_voltage( + ctrl_reg, + vctrl_calc_ctrl_voltage(vctrl, req_min_uV), + vctrl_calc_ctrl_voltage(vctrl, req_max_uV)); + + while (uV > req_min_uV) { + int max_drop_uV = (uV * vctrl->ovp_threshold) / 100; + int next_uV; + int next_ctrl_uV; + int delay; + + /* Make sure no infinite loop even in crazy cases */ + if (max_drop_uV == 0) + max_drop_uV = 1; + + next_uV = max_t(int, req_min_uV, uV - max_drop_uV); + next_ctrl_uV = vctrl_calc_ctrl_voltage(vctrl, next_uV); + + ret = regulator_set_voltage(ctrl_reg, + next_ctrl_uV, + next_ctrl_uV); + if (ret) + goto err; + + delay = DIV_ROUND_UP(uV - next_uV, vctrl->min_slew_down_rate); + usleep_range(delay, delay + DIV_ROUND_UP(delay, 10)); + + uV = next_uV; + } + + return 0; + +err: + /* Try to go back to original voltage */ + regulator_set_voltage(ctrl_reg, orig_ctrl_uV, orig_ctrl_uV); + + return ret; +} + +static int vctrl_get_voltage_sel(struct regulator_dev *rdev) +{ + struct vctrl_data *vctrl = rdev_get_drvdata(rdev); + + return vctrl->sel; +} + +static int vctrl_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct vctrl_data *vctrl = rdev_get_drvdata(rdev); + struct regulator *ctrl_reg = vctrl->ctrl_reg; + unsigned int orig_sel = vctrl->sel; + int ret; + + if (selector >= rdev->desc->n_voltages) + return -EINVAL; + + if (selector >= vctrl->sel || !vctrl->ovp_threshold) { + /* voltage rising or no OVP */ + ret = regulator_set_voltage(ctrl_reg, + vctrl->vtable[selector].ctrl, + vctrl->vtable[selector].ctrl); + if (!ret) + vctrl->sel = selector; + + return ret; + } + + while (vctrl->sel != selector) { + unsigned int next_sel; + int delay; + + if (selector >= vctrl->vtable[vctrl->sel].ovp_min_sel) + next_sel = selector; + else + next_sel = vctrl->vtable[vctrl->sel].ovp_min_sel; + + ret = regulator_set_voltage(ctrl_reg, + vctrl->vtable[next_sel].ctrl, + vctrl->vtable[next_sel].ctrl); + if (ret) { + dev_err(&rdev->dev, + "failed to set control voltage to %duV\n", + vctrl->vtable[next_sel].ctrl); + goto err; + } + vctrl->sel = next_sel; + + delay = DIV_ROUND_UP(vctrl->vtable[vctrl->sel].out - + vctrl->vtable[next_sel].out, + vctrl->min_slew_down_rate); + usleep_range(delay, delay + DIV_ROUND_UP(delay, 10)); + } + + return 0; + +err: + if (vctrl->sel != orig_sel) { + /* Try to go back to original voltage */ + if (!regulator_set_voltage(ctrl_reg, + vctrl->vtable[orig_sel].ctrl, + vctrl->vtable[orig_sel].ctrl)) + vctrl->sel = orig_sel; + else + dev_warn(&rdev->dev, + "failed to restore original voltage\n"); + } + + return ret; +} + +static int vctrl_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + struct vctrl_data *vctrl = rdev_get_drvdata(rdev); + + if (selector >= rdev->desc->n_voltages) + return -EINVAL; + + return vctrl->vtable[selector].out; +} + +static int vctrl_parse_dt(struct platform_device *pdev, + struct vctrl_data *vctrl) +{ + int ret; + struct device_node *np = pdev->dev.of_node; + u32 pval; + u32 vrange_ctrl[2]; + + vctrl->ctrl_reg = devm_regulator_get(&pdev->dev, "ctrl"); + if (IS_ERR(vctrl->ctrl_reg)) + return PTR_ERR(vctrl->ctrl_reg); + + ret = of_property_read_u32(np, "ovp-threshold-percent", &pval); + if (!ret) { + vctrl->ovp_threshold = pval; + if (vctrl->ovp_threshold > 100) { + dev_err(&pdev->dev, + "ovp-threshold-percent (%u) > 100\n", + vctrl->ovp_threshold); + return -EINVAL; + } + } + + ret = of_property_read_u32(np, "min-slew-down-rate", &pval); + if (!ret) { + vctrl->min_slew_down_rate = pval; + + /* We use the value as int and as divider; sanity check */ + if (vctrl->min_slew_down_rate == 0) { + dev_err(&pdev->dev, + "min-slew-down-rate must not be 0\n"); + return -EINVAL; + } else if (vctrl->min_slew_down_rate > INT_MAX) { + dev_err(&pdev->dev, "min-slew-down-rate (%u) too big\n", + vctrl->min_slew_down_rate); + return -EINVAL; + } + } + + if (vctrl->ovp_threshold && !vctrl->min_slew_down_rate) { + dev_err(&pdev->dev, + "ovp-threshold-percent requires min-slew-down-rate\n"); + return -EINVAL; + } + + ret = of_property_read_u32(np, "regulator-min-microvolt", &pval); + if (ret) { + dev_err(&pdev->dev, + "failed to read regulator-min-microvolt: %d\n", ret); + return ret; + } + vctrl->vrange.out.min_uV = pval; + + ret = of_property_read_u32(np, "regulator-max-microvolt", &pval); + if (ret) { + dev_err(&pdev->dev, + "failed to read regulator-max-microvolt: %d\n", ret); + return ret; + } + vctrl->vrange.out.max_uV = pval; + + ret = of_property_read_u32_array(np, "ctrl-voltage-range", vrange_ctrl, + 2); + if (ret) { + dev_err(&pdev->dev, "failed to read ctrl-voltage-range: %d\n", + ret); + return ret; + } + + if (vrange_ctrl[0] >= vrange_ctrl[1]) { + dev_err(&pdev->dev, "ctrl-voltage-range is invalid: %d-%d\n", + vrange_ctrl[0], vrange_ctrl[1]); + return -EINVAL; + } + + vctrl->vrange.ctrl.min_uV = vrange_ctrl[0]; + vctrl->vrange.ctrl.max_uV = vrange_ctrl[1]; + + return 0; +} + +static int vctrl_cmp_ctrl_uV(const void *a, const void *b) +{ + const struct vctrl_voltage_table *at = a; + const struct vctrl_voltage_table *bt = b; + + return at->ctrl - bt->ctrl; +} + +static int vctrl_init_vtable(struct platform_device *pdev) +{ + struct vctrl_data *vctrl = platform_get_drvdata(pdev); + struct regulator_desc *rdesc = &vctrl->desc; + struct regulator *ctrl_reg = vctrl->ctrl_reg; + struct vctrl_voltage_range *vrange_ctrl = &vctrl->vrange.ctrl; + int n_voltages; + int ctrl_uV; + int i, idx_vt; + + n_voltages = regulator_count_voltages(ctrl_reg); + + rdesc->n_voltages = n_voltages; + + /* determine number of steps within the range of the vctrl regulator */ + for (i = 0; i < n_voltages; i++) { + ctrl_uV = regulator_list_voltage(ctrl_reg, i); + + if (ctrl_uV < vrange_ctrl->min_uV || + ctrl_uV > vrange_ctrl->max_uV) { + rdesc->n_voltages--; + continue; + } + } + + if (rdesc->n_voltages == 0) { + dev_err(&pdev->dev, "invalid configuration\n"); + return -EINVAL; + } + + vctrl->vtable = devm_kcalloc(&pdev->dev, rdesc->n_voltages, + sizeof(struct vctrl_voltage_table), + GFP_KERNEL); + if (!vctrl->vtable) + return -ENOMEM; + + /* create mapping control <=> output voltage */ + for (i = 0, idx_vt = 0; i < n_voltages; i++) { + ctrl_uV = regulator_list_voltage(ctrl_reg, i); + + if (ctrl_uV < vrange_ctrl->min_uV || + ctrl_uV > vrange_ctrl->max_uV) + continue; + + vctrl->vtable[idx_vt].ctrl = ctrl_uV; + vctrl->vtable[idx_vt].out = + vctrl_calc_output_voltage(vctrl, ctrl_uV); + idx_vt++; + } + + /* we rely on the table to be ordered by ascending voltage */ + sort(vctrl->vtable, rdesc->n_voltages, + sizeof(struct vctrl_voltage_table), vctrl_cmp_ctrl_uV, + NULL); + + /* pre-calculate OVP-safe downward transitions */ + for (i = rdesc->n_voltages - 1; i > 0; i--) { + int j; + int ovp_min_uV = (vctrl->vtable[i].out * + (100 - vctrl->ovp_threshold)) / 100; + + for (j = 0; j < i; j++) { + if (vctrl->vtable[j].out >= ovp_min_uV) { + vctrl->vtable[i].ovp_min_sel = j; + break; + } + } + + if (j == i) { + dev_warn(&pdev->dev, "switching down from %duV may cause OVP shutdown\n", + vctrl->vtable[i].out); + /* use next lowest voltage */ + vctrl->vtable[i].ovp_min_sel = i - 1; + } + } + + return 0; +} + +static int vctrl_enable(struct regulator_dev *rdev) +{ + struct vctrl_data *vctrl = rdev_get_drvdata(rdev); + int ret = regulator_enable(vctrl->ctrl_reg); + + if (!ret) + vctrl->enabled = true; + + return ret; +} + +static int vctrl_disable(struct regulator_dev *rdev) +{ + struct vctrl_data *vctrl = rdev_get_drvdata(rdev); + int ret = regulator_disable(vctrl->ctrl_reg); + + if (!ret) + vctrl->enabled = false; + + return ret; +} + +static int vctrl_is_enabled(struct regulator_dev *rdev) +{ + struct vctrl_data *vctrl = rdev_get_drvdata(rdev); + + return vctrl->enabled; +} + +static const struct regulator_ops vctrl_ops_cont = { + .enable = vctrl_enable, + .disable = vctrl_disable, + .is_enabled = vctrl_is_enabled, + .get_voltage = vctrl_get_voltage, + .set_voltage = vctrl_set_voltage, +}; + +static const struct regulator_ops vctrl_ops_non_cont = { + .enable = vctrl_enable, + .disable = vctrl_disable, + .is_enabled = vctrl_is_enabled, + .set_voltage_sel = vctrl_set_voltage_sel, + .get_voltage_sel = vctrl_get_voltage_sel, + .list_voltage = vctrl_list_voltage, + .map_voltage = regulator_map_voltage_iterate, +}; + +static int vctrl_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct vctrl_data *vctrl; + const struct regulator_init_data *init_data; + struct regulator_desc *rdesc; + struct regulator_config cfg = { }; + struct vctrl_voltage_range *vrange_ctrl; + int ctrl_uV; + int ret; + + vctrl = devm_kzalloc(&pdev->dev, sizeof(struct vctrl_data), + GFP_KERNEL); + if (!vctrl) + return -ENOMEM; + + platform_set_drvdata(pdev, vctrl); + + ret = vctrl_parse_dt(pdev, vctrl); + if (ret) + return ret; + + vrange_ctrl = &vctrl->vrange.ctrl; + + rdesc = &vctrl->desc; + rdesc->name = "vctrl"; + rdesc->type = REGULATOR_VOLTAGE; + rdesc->owner = THIS_MODULE; + + if ((regulator_get_linear_step(vctrl->ctrl_reg) == 1) || + (regulator_count_voltages(vctrl->ctrl_reg) == -EINVAL)) { + rdesc->continuous_voltage_range = true; + rdesc->ops = &vctrl_ops_cont; + } else { + rdesc->ops = &vctrl_ops_non_cont; + } + + init_data = of_get_regulator_init_data(&pdev->dev, np, rdesc); + if (!init_data) + return -ENOMEM; + + cfg.of_node = np; + cfg.dev = &pdev->dev; + cfg.driver_data = vctrl; + cfg.init_data = init_data; + + if (!rdesc->continuous_voltage_range) { + ret = vctrl_init_vtable(pdev); + if (ret) + return ret; + + ctrl_uV = regulator_get_voltage(vctrl->ctrl_reg); + if (ctrl_uV < 0) { + dev_err(&pdev->dev, "failed to get control voltage\n"); + return ctrl_uV; + } + + /* determine current voltage selector from control voltage */ + if (ctrl_uV < vrange_ctrl->min_uV) { + vctrl->sel = 0; + } else if (ctrl_uV > vrange_ctrl->max_uV) { + vctrl->sel = rdesc->n_voltages - 1; + } else { + int i; + + for (i = 0; i < rdesc->n_voltages; i++) { + if (ctrl_uV == vctrl->vtable[i].ctrl) { + vctrl->sel = i; + break; + } + } + } + } + + vctrl->rdev = devm_regulator_register(&pdev->dev, rdesc, &cfg); + if (IS_ERR(vctrl->rdev)) { + ret = PTR_ERR(vctrl->rdev); + dev_err(&pdev->dev, "failed to register regulator: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct of_device_id vctrl_of_match[] = { + { .compatible = "vctrl-regulator", }, + {}, +}; +MODULE_DEVICE_TABLE(of, vctrl_of_match); + +static struct platform_driver vctrl_driver = { + .probe = vctrl_probe, + .driver = { + .name = "vctrl-regulator", + .of_match_table = of_match_ptr(vctrl_of_match), + }, +}; + +module_platform_driver(vctrl_driver); + +MODULE_DESCRIPTION("Voltage Controlled Regulator Driver"); +MODULE_AUTHOR("Matthias Kaehlcke <mka@chromium.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/vexpress-regulator.c b/drivers/regulator/vexpress-regulator.c new file mode 100644 index 000000000..c810cbbd4 --- /dev/null +++ b/drivers/regulator/vexpress-regulator.c @@ -0,0 +1,121 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2012 ARM Limited + */ + +#define DRVNAME "vexpress-regulator" +#define pr_fmt(fmt) DRVNAME ": " fmt + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/vexpress.h> + +struct vexpress_regulator { + struct regulator_desc desc; + struct regulator_dev *regdev; + struct regmap *regmap; +}; + +static int vexpress_regulator_get_voltage(struct regulator_dev *regdev) +{ + struct vexpress_regulator *reg = rdev_get_drvdata(regdev); + u32 uV; + int err = regmap_read(reg->regmap, 0, &uV); + + return err ? err : uV; +} + +static int vexpress_regulator_set_voltage(struct regulator_dev *regdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct vexpress_regulator *reg = rdev_get_drvdata(regdev); + + return regmap_write(reg->regmap, 0, min_uV); +} + +static struct regulator_ops vexpress_regulator_ops_ro = { + .get_voltage = vexpress_regulator_get_voltage, +}; + +static struct regulator_ops vexpress_regulator_ops = { + .get_voltage = vexpress_regulator_get_voltage, + .set_voltage = vexpress_regulator_set_voltage, +}; + +static int vexpress_regulator_probe(struct platform_device *pdev) +{ + struct vexpress_regulator *reg; + struct regulator_init_data *init_data; + struct regulator_config config = { }; + + reg = devm_kzalloc(&pdev->dev, sizeof(*reg), GFP_KERNEL); + if (!reg) + return -ENOMEM; + + reg->regmap = devm_regmap_init_vexpress_config(&pdev->dev); + if (IS_ERR(reg->regmap)) + return PTR_ERR(reg->regmap); + + reg->desc.name = dev_name(&pdev->dev); + reg->desc.type = REGULATOR_VOLTAGE; + reg->desc.owner = THIS_MODULE; + reg->desc.continuous_voltage_range = true; + + init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node, + ®->desc); + if (!init_data) + return -EINVAL; + + init_data->constraints.apply_uV = 0; + if (init_data->constraints.min_uV && init_data->constraints.max_uV) + reg->desc.ops = &vexpress_regulator_ops; + else + reg->desc.ops = &vexpress_regulator_ops_ro; + + config.dev = &pdev->dev; + config.init_data = init_data; + config.driver_data = reg; + config.of_node = pdev->dev.of_node; + + reg->regdev = devm_regulator_register(&pdev->dev, ®->desc, &config); + if (IS_ERR(reg->regdev)) + return PTR_ERR(reg->regdev); + + platform_set_drvdata(pdev, reg); + + return 0; +} + +static const struct of_device_id vexpress_regulator_of_match[] = { + { .compatible = "arm,vexpress-volt", }, + { } +}; +MODULE_DEVICE_TABLE(of, vexpress_regulator_of_match); + +static struct platform_driver vexpress_regulator_driver = { + .probe = vexpress_regulator_probe, + .driver = { + .name = DRVNAME, + .of_match_table = vexpress_regulator_of_match, + }, +}; + +module_platform_driver(vexpress_regulator_driver); + +MODULE_AUTHOR("Pawel Moll <pawel.moll@arm.com>"); +MODULE_DESCRIPTION("Versatile Express regulator"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:vexpress-regulator"); diff --git a/drivers/regulator/virtual.c b/drivers/regulator/virtual.c new file mode 100644 index 000000000..a6f1c7a99 --- /dev/null +++ b/drivers/regulator/virtual.c @@ -0,0 +1,349 @@ +/* + * reg-virtual-consumer.c + * + * Copyright 2008 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the + * License, or (at your option) any later version. + */ + +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/module.h> + +struct virtual_consumer_data { + struct mutex lock; + struct regulator *regulator; + bool enabled; + int min_uV; + int max_uV; + int min_uA; + int max_uA; + unsigned int mode; +}; + +static void update_voltage_constraints(struct device *dev, + struct virtual_consumer_data *data) +{ + int ret; + + if (data->min_uV && data->max_uV + && data->min_uV <= data->max_uV) { + dev_dbg(dev, "Requesting %d-%duV\n", + data->min_uV, data->max_uV); + ret = regulator_set_voltage(data->regulator, + data->min_uV, data->max_uV); + if (ret != 0) { + dev_err(dev, + "regulator_set_voltage() failed: %d\n", ret); + return; + } + } + + if (data->min_uV && data->max_uV && !data->enabled) { + dev_dbg(dev, "Enabling regulator\n"); + ret = regulator_enable(data->regulator); + if (ret == 0) + data->enabled = true; + else + dev_err(dev, "regulator_enable() failed: %d\n", + ret); + } + + if (!(data->min_uV && data->max_uV) && data->enabled) { + dev_dbg(dev, "Disabling regulator\n"); + ret = regulator_disable(data->regulator); + if (ret == 0) + data->enabled = false; + else + dev_err(dev, "regulator_disable() failed: %d\n", + ret); + } +} + +static void update_current_limit_constraints(struct device *dev, + struct virtual_consumer_data *data) +{ + int ret; + + if (data->max_uA + && data->min_uA <= data->max_uA) { + dev_dbg(dev, "Requesting %d-%duA\n", + data->min_uA, data->max_uA); + ret = regulator_set_current_limit(data->regulator, + data->min_uA, data->max_uA); + if (ret != 0) { + dev_err(dev, + "regulator_set_current_limit() failed: %d\n", + ret); + return; + } + } + + if (data->max_uA && !data->enabled) { + dev_dbg(dev, "Enabling regulator\n"); + ret = regulator_enable(data->regulator); + if (ret == 0) + data->enabled = true; + else + dev_err(dev, "regulator_enable() failed: %d\n", + ret); + } + + if (!(data->min_uA && data->max_uA) && data->enabled) { + dev_dbg(dev, "Disabling regulator\n"); + ret = regulator_disable(data->regulator); + if (ret == 0) + data->enabled = false; + else + dev_err(dev, "regulator_disable() failed: %d\n", + ret); + } +} + +static ssize_t show_min_uV(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct virtual_consumer_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->min_uV); +} + +static ssize_t set_min_uV(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct virtual_consumer_data *data = dev_get_drvdata(dev); + long val; + + if (kstrtol(buf, 10, &val) != 0) + return count; + + mutex_lock(&data->lock); + + data->min_uV = val; + update_voltage_constraints(dev, data); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_max_uV(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct virtual_consumer_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->max_uV); +} + +static ssize_t set_max_uV(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct virtual_consumer_data *data = dev_get_drvdata(dev); + long val; + + if (kstrtol(buf, 10, &val) != 0) + return count; + + mutex_lock(&data->lock); + + data->max_uV = val; + update_voltage_constraints(dev, data); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_min_uA(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct virtual_consumer_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->min_uA); +} + +static ssize_t set_min_uA(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct virtual_consumer_data *data = dev_get_drvdata(dev); + long val; + + if (kstrtol(buf, 10, &val) != 0) + return count; + + mutex_lock(&data->lock); + + data->min_uA = val; + update_current_limit_constraints(dev, data); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_max_uA(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct virtual_consumer_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->max_uA); +} + +static ssize_t set_max_uA(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct virtual_consumer_data *data = dev_get_drvdata(dev); + long val; + + if (kstrtol(buf, 10, &val) != 0) + return count; + + mutex_lock(&data->lock); + + data->max_uA = val; + update_current_limit_constraints(dev, data); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct virtual_consumer_data *data = dev_get_drvdata(dev); + + switch (data->mode) { + case REGULATOR_MODE_FAST: + return sprintf(buf, "fast\n"); + case REGULATOR_MODE_NORMAL: + return sprintf(buf, "normal\n"); + case REGULATOR_MODE_IDLE: + return sprintf(buf, "idle\n"); + case REGULATOR_MODE_STANDBY: + return sprintf(buf, "standby\n"); + default: + return sprintf(buf, "unknown\n"); + } +} + +static ssize_t set_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct virtual_consumer_data *data = dev_get_drvdata(dev); + unsigned int mode; + int ret; + + /* + * sysfs_streq() doesn't need the \n's, but we add them so the strings + * will be shared with show_mode(), above. + */ + if (sysfs_streq(buf, "fast\n")) + mode = REGULATOR_MODE_FAST; + else if (sysfs_streq(buf, "normal\n")) + mode = REGULATOR_MODE_NORMAL; + else if (sysfs_streq(buf, "idle\n")) + mode = REGULATOR_MODE_IDLE; + else if (sysfs_streq(buf, "standby\n")) + mode = REGULATOR_MODE_STANDBY; + else { + dev_err(dev, "Configuring invalid mode\n"); + return count; + } + + mutex_lock(&data->lock); + ret = regulator_set_mode(data->regulator, mode); + if (ret == 0) + data->mode = mode; + else + dev_err(dev, "Failed to configure mode: %d\n", ret); + mutex_unlock(&data->lock); + + return count; +} + +static DEVICE_ATTR(min_microvolts, 0664, show_min_uV, set_min_uV); +static DEVICE_ATTR(max_microvolts, 0664, show_max_uV, set_max_uV); +static DEVICE_ATTR(min_microamps, 0664, show_min_uA, set_min_uA); +static DEVICE_ATTR(max_microamps, 0664, show_max_uA, set_max_uA); +static DEVICE_ATTR(mode, 0664, show_mode, set_mode); + +static struct attribute *regulator_virtual_attributes[] = { + &dev_attr_min_microvolts.attr, + &dev_attr_max_microvolts.attr, + &dev_attr_min_microamps.attr, + &dev_attr_max_microamps.attr, + &dev_attr_mode.attr, + NULL +}; + +static const struct attribute_group regulator_virtual_attr_group = { + .attrs = regulator_virtual_attributes, +}; + +static int regulator_virtual_probe(struct platform_device *pdev) +{ + char *reg_id = dev_get_platdata(&pdev->dev); + struct virtual_consumer_data *drvdata; + int ret; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct virtual_consumer_data), + GFP_KERNEL); + if (drvdata == NULL) + return -ENOMEM; + + mutex_init(&drvdata->lock); + + drvdata->regulator = devm_regulator_get(&pdev->dev, reg_id); + if (IS_ERR(drvdata->regulator)) { + ret = PTR_ERR(drvdata->regulator); + dev_err(&pdev->dev, "Failed to obtain supply '%s': %d\n", + reg_id, ret); + return ret; + } + + ret = sysfs_create_group(&pdev->dev.kobj, + ®ulator_virtual_attr_group); + if (ret != 0) { + dev_err(&pdev->dev, + "Failed to create attribute group: %d\n", ret); + return ret; + } + + drvdata->mode = regulator_get_mode(drvdata->regulator); + + platform_set_drvdata(pdev, drvdata); + + return 0; +} + +static int regulator_virtual_remove(struct platform_device *pdev) +{ + struct virtual_consumer_data *drvdata = platform_get_drvdata(pdev); + + sysfs_remove_group(&pdev->dev.kobj, ®ulator_virtual_attr_group); + + if (drvdata->enabled) + regulator_disable(drvdata->regulator); + + return 0; +} + +static struct platform_driver regulator_virtual_consumer_driver = { + .probe = regulator_virtual_probe, + .remove = regulator_virtual_remove, + .driver = { + .name = "reg-virt-consumer", + }, +}; + +module_platform_driver(regulator_virtual_consumer_driver); + +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("Virtual regulator consumer"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:reg-virt-consumer"); diff --git a/drivers/regulator/wm831x-dcdc.c b/drivers/regulator/wm831x-dcdc.c new file mode 100644 index 000000000..df591435d --- /dev/null +++ b/drivers/regulator/wm831x-dcdc.c @@ -0,0 +1,913 @@ +/* + * wm831x-dcdc.c -- DC-DC buck convertor driver for the WM831x series + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/gpio.h> +#include <linux/slab.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/regulator.h> +#include <linux/mfd/wm831x/pdata.h> + +#define WM831X_BUCKV_MAX_SELECTOR 0x68 +#define WM831X_BUCKP_MAX_SELECTOR 0x66 + +#define WM831X_DCDC_MODE_FAST 0 +#define WM831X_DCDC_MODE_NORMAL 1 +#define WM831X_DCDC_MODE_IDLE 2 +#define WM831X_DCDC_MODE_STANDBY 3 + +#define WM831X_DCDC_MAX_NAME 9 + +/* Register offsets in control block */ +#define WM831X_DCDC_CONTROL_1 0 +#define WM831X_DCDC_CONTROL_2 1 +#define WM831X_DCDC_ON_CONFIG 2 +#define WM831X_DCDC_SLEEP_CONTROL 3 +#define WM831X_DCDC_DVS_CONTROL 4 + +/* + * Shared + */ + +struct wm831x_dcdc { + char name[WM831X_DCDC_MAX_NAME]; + char supply_name[WM831X_DCDC_MAX_NAME]; + struct regulator_desc desc; + int base; + struct wm831x *wm831x; + struct regulator_dev *regulator; + int dvs_gpio; + int dvs_gpio_state; + int on_vsel; + int dvs_vsel; +}; + +static unsigned int wm831x_dcdc_get_mode(struct regulator_dev *rdev) + +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + int val; + + val = wm831x_reg_read(wm831x, reg); + if (val < 0) + return val; + + val = (val & WM831X_DC1_ON_MODE_MASK) >> WM831X_DC1_ON_MODE_SHIFT; + + switch (val) { + case WM831X_DCDC_MODE_FAST: + return REGULATOR_MODE_FAST; + case WM831X_DCDC_MODE_NORMAL: + return REGULATOR_MODE_NORMAL; + case WM831X_DCDC_MODE_STANDBY: + return REGULATOR_MODE_STANDBY; + case WM831X_DCDC_MODE_IDLE: + return REGULATOR_MODE_IDLE; + default: + BUG(); + return -EINVAL; + } +} + +static int wm831x_dcdc_set_mode_int(struct wm831x *wm831x, int reg, + unsigned int mode) +{ + int val; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = WM831X_DCDC_MODE_FAST; + break; + case REGULATOR_MODE_NORMAL: + val = WM831X_DCDC_MODE_NORMAL; + break; + case REGULATOR_MODE_STANDBY: + val = WM831X_DCDC_MODE_STANDBY; + break; + case REGULATOR_MODE_IDLE: + val = WM831X_DCDC_MODE_IDLE; + break; + default: + return -EINVAL; + } + + return wm831x_set_bits(wm831x, reg, WM831X_DC1_ON_MODE_MASK, + val << WM831X_DC1_ON_MODE_SHIFT); +} + +static int wm831x_dcdc_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + + return wm831x_dcdc_set_mode_int(wm831x, reg, mode); +} + +static int wm831x_dcdc_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL; + + return wm831x_dcdc_set_mode_int(wm831x, reg, mode); +} + +static int wm831x_dcdc_get_status(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int ret; + + /* First, check for errors */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_UV_STATUS); + if (ret < 0) + return ret; + + if (ret & (1 << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d under voltage\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + + /* DCDC1 and DCDC2 can additionally detect high voltage/current */ + if (rdev_get_id(rdev) < 2) { + if (ret & (WM831X_DC1_OV_STS << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d over voltage\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + + if (ret & (WM831X_DC1_HC_STS << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d over current\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + } + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_STATUS); + if (ret < 0) + return ret; + if (!(ret & (1 << rdev_get_id(rdev)))) + return REGULATOR_STATUS_OFF; + + /* TODO: When we handle hardware control modes so we can report the + * current mode. */ + return REGULATOR_STATUS_ON; +} + +static irqreturn_t wm831x_dcdc_uv_irq(int irq, void *data) +{ + struct wm831x_dcdc *dcdc = data; + + regulator_notifier_call_chain(dcdc->regulator, + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + + return IRQ_HANDLED; +} + +static irqreturn_t wm831x_dcdc_oc_irq(int irq, void *data) +{ + struct wm831x_dcdc *dcdc = data; + + regulator_notifier_call_chain(dcdc->regulator, + REGULATOR_EVENT_OVER_CURRENT, + NULL); + + return IRQ_HANDLED; +} + +/* + * BUCKV specifics + */ + +static int wm831x_buckv_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + if (selector <= 0x8) + return 600000; + if (selector <= WM831X_BUCKV_MAX_SELECTOR) + return 600000 + ((selector - 0x8) * 12500); + return -EINVAL; +} + +static int wm831x_buckv_map_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + u16 vsel; + + if (min_uV < 600000) + vsel = 0; + else if (min_uV <= 1800000) + vsel = DIV_ROUND_UP(min_uV - 600000, 12500) + 8; + else + return -EINVAL; + + if (wm831x_buckv_list_voltage(rdev, vsel) > max_uV) + return -EINVAL; + + return vsel; +} + +static int wm831x_buckv_set_dvs(struct regulator_dev *rdev, int state) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + + if (state == dcdc->dvs_gpio_state) + return 0; + + dcdc->dvs_gpio_state = state; + gpio_set_value(dcdc->dvs_gpio, state); + + /* Should wait for DVS state change to be asserted if we have + * a GPIO for it, for now assume the device is configured + * for the fastest possible transition. + */ + + return 0; +} + +static int wm831x_buckv_set_voltage_sel(struct regulator_dev *rdev, + unsigned vsel) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int on_reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + int dvs_reg = dcdc->base + WM831X_DCDC_DVS_CONTROL; + int ret; + + /* If this value is already set then do a GPIO update if we can */ + if (dcdc->dvs_gpio && dcdc->on_vsel == vsel) + return wm831x_buckv_set_dvs(rdev, 0); + + if (dcdc->dvs_gpio && dcdc->dvs_vsel == vsel) + return wm831x_buckv_set_dvs(rdev, 1); + + /* Always set the ON status to the minimum voltage */ + ret = wm831x_set_bits(wm831x, on_reg, WM831X_DC1_ON_VSEL_MASK, vsel); + if (ret < 0) + return ret; + dcdc->on_vsel = vsel; + + if (!dcdc->dvs_gpio) + return ret; + + /* Kick the voltage transition now */ + ret = wm831x_buckv_set_dvs(rdev, 0); + if (ret < 0) + return ret; + + /* + * If this VSEL is higher than the last one we've seen then + * remember it as the DVS VSEL. This is optimised for CPUfreq + * usage where we want to get to the highest voltage very + * quickly. + */ + if (vsel > dcdc->dvs_vsel) { + ret = wm831x_set_bits(wm831x, dvs_reg, + WM831X_DC1_DVS_VSEL_MASK, + vsel); + if (ret == 0) + dcdc->dvs_vsel = vsel; + else + dev_warn(wm831x->dev, + "Failed to set DCDC DVS VSEL: %d\n", ret); + } + + return 0; +} + +static int wm831x_buckv_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL; + int vsel; + + vsel = wm831x_buckv_map_voltage(rdev, uV, uV); + if (vsel < 0) + return vsel; + + return wm831x_set_bits(wm831x, reg, WM831X_DC1_SLP_VSEL_MASK, vsel); +} + +static int wm831x_buckv_get_voltage_sel(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + + if (dcdc->dvs_gpio && dcdc->dvs_gpio_state) + return dcdc->dvs_vsel; + else + return dcdc->on_vsel; +} + +/* Current limit options */ +static const unsigned int wm831x_dcdc_ilim[] = { + 125000, 250000, 375000, 500000, 625000, 750000, 875000, 1000000 +}; + +static int wm831x_buckv_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_CONTROL_2; + int i; + + for (i = ARRAY_SIZE(wm831x_dcdc_ilim) - 1; i >= 0; i--) { + if ((min_uA <= wm831x_dcdc_ilim[i]) && + (wm831x_dcdc_ilim[i] <= max_uA)) + return wm831x_set_bits(wm831x, reg, + WM831X_DC1_HC_THR_MASK, + i << WM831X_DC1_HC_THR_SHIFT); + } + + return -EINVAL; +} + +static int wm831x_buckv_get_current_limit(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_CONTROL_2; + int val; + + val = wm831x_reg_read(wm831x, reg); + if (val < 0) + return val; + + val = (val & WM831X_DC1_HC_THR_MASK) >> WM831X_DC1_HC_THR_SHIFT; + return wm831x_dcdc_ilim[val]; +} + +static const struct regulator_ops wm831x_buckv_ops = { + .set_voltage_sel = wm831x_buckv_set_voltage_sel, + .get_voltage_sel = wm831x_buckv_get_voltage_sel, + .list_voltage = wm831x_buckv_list_voltage, + .map_voltage = wm831x_buckv_map_voltage, + .set_suspend_voltage = wm831x_buckv_set_suspend_voltage, + .set_current_limit = wm831x_buckv_set_current_limit, + .get_current_limit = wm831x_buckv_get_current_limit, + + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_status = wm831x_dcdc_get_status, + .get_mode = wm831x_dcdc_get_mode, + .set_mode = wm831x_dcdc_set_mode, + .set_suspend_mode = wm831x_dcdc_set_suspend_mode, +}; + +/* + * Set up DVS control. We just log errors since we can still run + * (with reduced performance) if we fail. + */ +static void wm831x_buckv_dvs_init(struct platform_device *pdev, + struct wm831x_dcdc *dcdc, + struct wm831x_buckv_pdata *pdata) +{ + struct wm831x *wm831x = dcdc->wm831x; + int ret; + u16 ctrl; + + if (!pdata || !pdata->dvs_gpio) + return; + + /* gpiolib won't let us read the GPIO status so pick the higher + * of the two existing voltages so we take it as platform data. + */ + dcdc->dvs_gpio_state = pdata->dvs_init_state; + + ret = devm_gpio_request_one(&pdev->dev, pdata->dvs_gpio, + dcdc->dvs_gpio_state ? GPIOF_INIT_HIGH : 0, + "DCDC DVS"); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to get %s DVS GPIO: %d\n", + dcdc->name, ret); + return; + } + + dcdc->dvs_gpio = pdata->dvs_gpio; + + switch (pdata->dvs_control_src) { + case 1: + ctrl = 2 << WM831X_DC1_DVS_SRC_SHIFT; + break; + case 2: + ctrl = 3 << WM831X_DC1_DVS_SRC_SHIFT; + break; + default: + dev_err(wm831x->dev, "Invalid DVS control source %d for %s\n", + pdata->dvs_control_src, dcdc->name); + return; + } + + /* If DVS_VSEL is set to the minimum value then raise it to ON_VSEL + * to make bootstrapping a bit smoother. + */ + if (!dcdc->dvs_vsel) { + ret = wm831x_set_bits(wm831x, + dcdc->base + WM831X_DCDC_DVS_CONTROL, + WM831X_DC1_DVS_VSEL_MASK, dcdc->on_vsel); + if (ret == 0) + dcdc->dvs_vsel = dcdc->on_vsel; + else + dev_warn(wm831x->dev, "Failed to set DVS_VSEL: %d\n", + ret); + } + + ret = wm831x_set_bits(wm831x, dcdc->base + WM831X_DCDC_DVS_CONTROL, + WM831X_DC1_DVS_SRC_MASK, ctrl); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to set %s DVS source: %d\n", + dcdc->name, ret); + } +} + +static int wm831x_buckv_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + struct regulator_config config = { }; + int id; + struct wm831x_dcdc *dcdc; + struct resource *res; + int ret, irq; + + if (pdata && pdata->wm831x_num) + id = (pdata->wm831x_num * 10) + 1; + else + id = 0; + id = pdev->id - id; + + dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1); + + dcdc = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_dcdc), + GFP_KERNEL); + if (!dcdc) + return -ENOMEM; + + dcdc->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_REG, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No REG resource\n"); + ret = -EINVAL; + goto err; + } + dcdc->base = res->start; + + snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1); + dcdc->desc.name = dcdc->name; + + snprintf(dcdc->supply_name, sizeof(dcdc->supply_name), + "DC%dVDD", id + 1); + dcdc->desc.supply_name = dcdc->supply_name; + + dcdc->desc.id = id; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.n_voltages = WM831X_BUCKV_MAX_SELECTOR + 1; + dcdc->desc.ops = &wm831x_buckv_ops; + dcdc->desc.owner = THIS_MODULE; + dcdc->desc.enable_reg = WM831X_DCDC_ENABLE; + dcdc->desc.enable_mask = 1 << id; + + ret = wm831x_reg_read(wm831x, dcdc->base + WM831X_DCDC_ON_CONFIG); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read ON VSEL: %d\n", ret); + goto err; + } + dcdc->on_vsel = ret & WM831X_DC1_ON_VSEL_MASK; + + ret = wm831x_reg_read(wm831x, dcdc->base + WM831X_DCDC_DVS_CONTROL); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read DVS VSEL: %d\n", ret); + goto err; + } + dcdc->dvs_vsel = ret & WM831X_DC1_DVS_VSEL_MASK; + + if (pdata && pdata->dcdc[id]) + wm831x_buckv_dvs_init(pdev, dcdc, + pdata->dcdc[id]->driver_data); + + config.dev = pdev->dev.parent; + if (pdata) + config.init_data = pdata->dcdc[id]; + config.driver_data = dcdc; + config.regmap = wm831x->regmap; + + dcdc->regulator = devm_regulator_register(&pdev->dev, &dcdc->desc, + &config); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n", + id + 1, ret); + goto err; + } + + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "UV")); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + wm831x_dcdc_uv_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + dcdc->name, dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err; + } + + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "HC")); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + wm831x_dcdc_oc_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + dcdc->name, dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request HC IRQ %d: %d\n", + irq, ret); + goto err; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err: + return ret; +} + +static struct platform_driver wm831x_buckv_driver = { + .probe = wm831x_buckv_probe, + .driver = { + .name = "wm831x-buckv", + }, +}; + +/* + * BUCKP specifics + */ + +static int wm831x_buckp_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL; + int sel; + + sel = regulator_map_voltage_linear(rdev, uV, uV); + if (sel < 0) + return sel; + + return wm831x_set_bits(wm831x, reg, WM831X_DC3_ON_VSEL_MASK, sel); +} + +static const struct regulator_ops wm831x_buckp_ops = { + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_suspend_voltage = wm831x_buckp_set_suspend_voltage, + + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_status = wm831x_dcdc_get_status, + .get_mode = wm831x_dcdc_get_mode, + .set_mode = wm831x_dcdc_set_mode, + .set_suspend_mode = wm831x_dcdc_set_suspend_mode, +}; + +static int wm831x_buckp_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + struct regulator_config config = { }; + int id; + struct wm831x_dcdc *dcdc; + struct resource *res; + int ret, irq; + + if (pdata && pdata->wm831x_num) + id = (pdata->wm831x_num * 10) + 1; + else + id = 0; + id = pdev->id - id; + + dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1); + + dcdc = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_dcdc), + GFP_KERNEL); + if (!dcdc) + return -ENOMEM; + + dcdc->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_REG, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No REG resource\n"); + ret = -EINVAL; + goto err; + } + dcdc->base = res->start; + + snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1); + dcdc->desc.name = dcdc->name; + + snprintf(dcdc->supply_name, sizeof(dcdc->supply_name), + "DC%dVDD", id + 1); + dcdc->desc.supply_name = dcdc->supply_name; + + dcdc->desc.id = id; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.n_voltages = WM831X_BUCKP_MAX_SELECTOR + 1; + dcdc->desc.ops = &wm831x_buckp_ops; + dcdc->desc.owner = THIS_MODULE; + dcdc->desc.vsel_reg = dcdc->base + WM831X_DCDC_ON_CONFIG; + dcdc->desc.vsel_mask = WM831X_DC3_ON_VSEL_MASK; + dcdc->desc.enable_reg = WM831X_DCDC_ENABLE; + dcdc->desc.enable_mask = 1 << id; + dcdc->desc.min_uV = 850000; + dcdc->desc.uV_step = 25000; + + config.dev = pdev->dev.parent; + if (pdata) + config.init_data = pdata->dcdc[id]; + config.driver_data = dcdc; + config.regmap = wm831x->regmap; + + dcdc->regulator = devm_regulator_register(&pdev->dev, &dcdc->desc, + &config); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n", + id + 1, ret); + goto err; + } + + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "UV")); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + wm831x_dcdc_uv_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + dcdc->name, dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err: + return ret; +} + +static struct platform_driver wm831x_buckp_driver = { + .probe = wm831x_buckp_probe, + .driver = { + .name = "wm831x-buckp", + }, +}; + +/* + * DCDC boost convertors + */ + +static int wm831x_boostp_get_status(struct regulator_dev *rdev) +{ + struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev); + struct wm831x *wm831x = dcdc->wm831x; + int ret; + + /* First, check for errors */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_UV_STATUS); + if (ret < 0) + return ret; + + if (ret & (1 << rdev_get_id(rdev))) { + dev_dbg(wm831x->dev, "DCDC%d under voltage\n", + rdev_get_id(rdev) + 1); + return REGULATOR_STATUS_ERROR; + } + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_DCDC_STATUS); + if (ret < 0) + return ret; + if (ret & (1 << rdev_get_id(rdev))) + return REGULATOR_STATUS_ON; + else + return REGULATOR_STATUS_OFF; +} + +static const struct regulator_ops wm831x_boostp_ops = { + .get_status = wm831x_boostp_get_status, + + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static int wm831x_boostp_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + struct regulator_config config = { }; + int id = pdev->id % ARRAY_SIZE(pdata->dcdc); + struct wm831x_dcdc *dcdc; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1); + + if (pdata == NULL || pdata->dcdc[id] == NULL) + return -ENODEV; + + dcdc = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_dcdc), GFP_KERNEL); + if (!dcdc) + return -ENOMEM; + + dcdc->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_REG, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No REG resource\n"); + return -EINVAL; + } + dcdc->base = res->start; + + snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1); + dcdc->desc.name = dcdc->name; + dcdc->desc.id = id; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.ops = &wm831x_boostp_ops; + dcdc->desc.owner = THIS_MODULE; + dcdc->desc.enable_reg = WM831X_DCDC_ENABLE; + dcdc->desc.enable_mask = 1 << id; + + config.dev = pdev->dev.parent; + if (pdata) + config.init_data = pdata->dcdc[id]; + config.driver_data = dcdc; + config.regmap = wm831x->regmap; + + dcdc->regulator = devm_regulator_register(&pdev->dev, &dcdc->desc, + &config); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n", + id + 1, ret); + return ret; + } + + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "UV")); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + wm831x_dcdc_uv_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + dcdc->name, + dcdc); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + return ret; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; +} + +static struct platform_driver wm831x_boostp_driver = { + .probe = wm831x_boostp_probe, + .driver = { + .name = "wm831x-boostp", + }, +}; + +/* + * External Power Enable + * + * These aren't actually DCDCs but look like them in hardware so share + * code. + */ + +#define WM831X_EPE_BASE 6 + +static const struct regulator_ops wm831x_epe_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_status = wm831x_dcdc_get_status, +}; + +static int wm831x_epe_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + struct regulator_config config = { }; + int id = pdev->id % ARRAY_SIZE(pdata->epe); + struct wm831x_dcdc *dcdc; + int ret; + + dev_dbg(&pdev->dev, "Probing EPE%d\n", id + 1); + + dcdc = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_dcdc), GFP_KERNEL); + if (!dcdc) + return -ENOMEM; + + dcdc->wm831x = wm831x; + + /* For current parts this is correct; probably need to revisit + * in future. + */ + snprintf(dcdc->name, sizeof(dcdc->name), "EPE%d", id + 1); + dcdc->desc.name = dcdc->name; + dcdc->desc.id = id + WM831X_EPE_BASE; /* Offset in DCDC registers */ + dcdc->desc.ops = &wm831x_epe_ops; + dcdc->desc.type = REGULATOR_VOLTAGE; + dcdc->desc.owner = THIS_MODULE; + dcdc->desc.enable_reg = WM831X_DCDC_ENABLE; + dcdc->desc.enable_mask = 1 << dcdc->desc.id; + + config.dev = pdev->dev.parent; + if (pdata) + config.init_data = pdata->epe[id]; + config.driver_data = dcdc; + config.regmap = wm831x->regmap; + + dcdc->regulator = devm_regulator_register(&pdev->dev, &dcdc->desc, + &config); + if (IS_ERR(dcdc->regulator)) { + ret = PTR_ERR(dcdc->regulator); + dev_err(wm831x->dev, "Failed to register EPE%d: %d\n", + id + 1, ret); + goto err; + } + + platform_set_drvdata(pdev, dcdc); + + return 0; + +err: + return ret; +} + +static struct platform_driver wm831x_epe_driver = { + .probe = wm831x_epe_probe, + .driver = { + .name = "wm831x-epe", + }, +}; + +static struct platform_driver * const drivers[] = { + &wm831x_buckv_driver, + &wm831x_buckp_driver, + &wm831x_boostp_driver, + &wm831x_epe_driver, +}; + +static int __init wm831x_dcdc_init(void) +{ + return platform_register_drivers(drivers, ARRAY_SIZE(drivers)); +} +subsys_initcall(wm831x_dcdc_init); + +static void __exit wm831x_dcdc_exit(void) +{ + platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); +} +module_exit(wm831x_dcdc_exit); + +/* Module information */ +MODULE_AUTHOR("Mark Brown"); +MODULE_DESCRIPTION("WM831x DC-DC convertor driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-buckv"); +MODULE_ALIAS("platform:wm831x-buckp"); +MODULE_ALIAS("platform:wm831x-boostp"); +MODULE_ALIAS("platform:wm831x-epe"); diff --git a/drivers/regulator/wm831x-isink.c b/drivers/regulator/wm831x-isink.c new file mode 100644 index 000000000..6dd891d7e --- /dev/null +++ b/drivers/regulator/wm831x-isink.c @@ -0,0 +1,252 @@ +/* + * wm831x-isink.c -- Current sink driver for the WM831x series + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/slab.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/regulator.h> +#include <linux/mfd/wm831x/pdata.h> + +#define WM831X_ISINK_MAX_NAME 7 + +struct wm831x_isink { + char name[WM831X_ISINK_MAX_NAME]; + struct regulator_desc desc; + int reg; + struct wm831x *wm831x; + struct regulator_dev *regulator; +}; + +static int wm831x_isink_enable(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + /* We have a two stage enable: first start the ISINK... */ + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, + WM831X_CS1_ENA); + if (ret != 0) + return ret; + + /* ...then enable drive */ + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_DRIVE, + WM831X_CS1_DRIVE); + if (ret != 0) + wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, 0); + + return ret; + +} + +static int wm831x_isink_disable(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_DRIVE, 0); + if (ret < 0) + return ret; + + ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, 0); + if (ret < 0) + return ret; + + return ret; + +} + +static int wm831x_isink_is_enabled(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + ret = wm831x_reg_read(wm831x, isink->reg); + if (ret < 0) + return ret; + + if ((ret & (WM831X_CS1_ENA | WM831X_CS1_DRIVE)) == + (WM831X_CS1_ENA | WM831X_CS1_DRIVE)) + return 1; + else + return 0; +} + +static int wm831x_isink_set_current(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret, i; + + for (i = 0; i < ARRAY_SIZE(wm831x_isinkv_values); i++) { + int val = wm831x_isinkv_values[i]; + if (min_uA <= val && val <= max_uA) { + ret = wm831x_set_bits(wm831x, isink->reg, + WM831X_CS1_ISEL_MASK, i); + return ret; + } + } + + return -EINVAL; +} + +static int wm831x_isink_get_current(struct regulator_dev *rdev) +{ + struct wm831x_isink *isink = rdev_get_drvdata(rdev); + struct wm831x *wm831x = isink->wm831x; + int ret; + + ret = wm831x_reg_read(wm831x, isink->reg); + if (ret < 0) + return ret; + + ret &= WM831X_CS1_ISEL_MASK; + if (ret > WM831X_ISINK_MAX_ISEL) + ret = WM831X_ISINK_MAX_ISEL; + + return wm831x_isinkv_values[ret]; +} + +static const struct regulator_ops wm831x_isink_ops = { + .is_enabled = wm831x_isink_is_enabled, + .enable = wm831x_isink_enable, + .disable = wm831x_isink_disable, + .set_current_limit = wm831x_isink_set_current, + .get_current_limit = wm831x_isink_get_current, +}; + +static irqreturn_t wm831x_isink_irq(int irq, void *data) +{ + struct wm831x_isink *isink = data; + + regulator_notifier_call_chain(isink->regulator, + REGULATOR_EVENT_OVER_CURRENT, + NULL); + + return IRQ_HANDLED; +} + + +static int wm831x_isink_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + struct wm831x_isink *isink; + int id = pdev->id % ARRAY_SIZE(pdata->isink); + struct regulator_config config = { }; + struct resource *res; + int ret, irq; + + dev_dbg(&pdev->dev, "Probing ISINK%d\n", id + 1); + + if (pdata == NULL || pdata->isink[id] == NULL) + return -ENODEV; + + isink = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_isink), + GFP_KERNEL); + if (!isink) + return -ENOMEM; + + isink->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_REG, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No REG resource\n"); + ret = -EINVAL; + goto err; + } + isink->reg = res->start; + + /* For current parts this is correct; probably need to revisit + * in future. + */ + snprintf(isink->name, sizeof(isink->name), "ISINK%d", id + 1); + isink->desc.name = isink->name; + isink->desc.id = id; + isink->desc.ops = &wm831x_isink_ops; + isink->desc.type = REGULATOR_CURRENT; + isink->desc.owner = THIS_MODULE; + + config.dev = pdev->dev.parent; + config.init_data = pdata->isink[id]; + config.driver_data = isink; + + isink->regulator = devm_regulator_register(&pdev->dev, &isink->desc, + &config); + if (IS_ERR(isink->regulator)) { + ret = PTR_ERR(isink->regulator); + dev_err(wm831x->dev, "Failed to register ISINK%d: %d\n", + id + 1, ret); + goto err; + } + + irq = wm831x_irq(wm831x, platform_get_irq(pdev, 0)); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + wm831x_isink_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + isink->name, + isink); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request ISINK IRQ %d: %d\n", + irq, ret); + goto err; + } + + platform_set_drvdata(pdev, isink); + + return 0; + +err: + return ret; +} + +static struct platform_driver wm831x_isink_driver = { + .probe = wm831x_isink_probe, + .driver = { + .name = "wm831x-isink", + }, +}; + +static int __init wm831x_isink_init(void) +{ + int ret; + ret = platform_driver_register(&wm831x_isink_driver); + if (ret != 0) + pr_err("Failed to register WM831x ISINK driver: %d\n", ret); + + return ret; +} +subsys_initcall(wm831x_isink_init); + +static void __exit wm831x_isink_exit(void) +{ + platform_driver_unregister(&wm831x_isink_driver); +} +module_exit(wm831x_isink_exit); + +/* Module information */ +MODULE_AUTHOR("Mark Brown"); +MODULE_DESCRIPTION("WM831x current sink driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-isink"); diff --git a/drivers/regulator/wm831x-ldo.c b/drivers/regulator/wm831x-ldo.c new file mode 100644 index 000000000..e4a6f8884 --- /dev/null +++ b/drivers/regulator/wm831x-ldo.c @@ -0,0 +1,680 @@ +/* + * wm831x-ldo.c -- LDO driver for the WM831x series + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/slab.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/regulator.h> +#include <linux/mfd/wm831x/pdata.h> + +#define WM831X_LDO_MAX_NAME 9 + +#define WM831X_LDO_CONTROL 0 +#define WM831X_LDO_ON_CONTROL 1 +#define WM831X_LDO_SLEEP_CONTROL 2 + +#define WM831X_ALIVE_LDO_ON_CONTROL 0 +#define WM831X_ALIVE_LDO_SLEEP_CONTROL 1 + +struct wm831x_ldo { + char name[WM831X_LDO_MAX_NAME]; + char supply_name[WM831X_LDO_MAX_NAME]; + struct regulator_desc desc; + int base; + struct wm831x *wm831x; + struct regulator_dev *regulator; +}; + +/* + * Shared + */ + +static irqreturn_t wm831x_ldo_uv_irq(int irq, void *data) +{ + struct wm831x_ldo *ldo = data; + + regulator_notifier_call_chain(ldo->regulator, + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + + return IRQ_HANDLED; +} + +/* + * General purpose LDOs + */ + +static const struct regulator_linear_range wm831x_gp_ldo_ranges[] = { + REGULATOR_LINEAR_RANGE(900000, 0, 14, 50000), + REGULATOR_LINEAR_RANGE(1700000, 15, 31, 100000), +}; + +static int wm831x_gp_ldo_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int sel, reg = ldo->base + WM831X_LDO_SLEEP_CONTROL; + + sel = regulator_map_voltage_linear_range(rdev, uV, uV); + if (sel < 0) + return sel; + + return wm831x_set_bits(wm831x, reg, WM831X_LDO1_ON_VSEL_MASK, sel); +} + +static unsigned int wm831x_gp_ldo_get_mode(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int ctrl_reg = ldo->base + WM831X_LDO_CONTROL; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + ret = wm831x_reg_read(wm831x, on_reg); + if (ret < 0) + return ret; + + if (!(ret & WM831X_LDO1_ON_MODE)) + return REGULATOR_MODE_NORMAL; + + ret = wm831x_reg_read(wm831x, ctrl_reg); + if (ret < 0) + return ret; + + if (ret & WM831X_LDO1_LP_MODE) + return REGULATOR_MODE_STANDBY; + else + return REGULATOR_MODE_IDLE; +} + +static int wm831x_gp_ldo_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int ctrl_reg = ldo->base + WM831X_LDO_CONTROL; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + + switch (mode) { + case REGULATOR_MODE_NORMAL: + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO1_ON_MODE, 0); + if (ret < 0) + return ret; + break; + + case REGULATOR_MODE_IDLE: + ret = wm831x_set_bits(wm831x, ctrl_reg, + WM831X_LDO1_LP_MODE, 0); + if (ret < 0) + return ret; + + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO1_ON_MODE, + WM831X_LDO1_ON_MODE); + if (ret < 0) + return ret; + break; + + case REGULATOR_MODE_STANDBY: + ret = wm831x_set_bits(wm831x, ctrl_reg, + WM831X_LDO1_LP_MODE, + WM831X_LDO1_LP_MODE); + if (ret < 0) + return ret; + + ret = wm831x_set_bits(wm831x, on_reg, + WM831X_LDO1_ON_MODE, + WM831X_LDO1_ON_MODE); + if (ret < 0) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm831x_gp_ldo_get_status(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int ret; + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS); + if (ret < 0) + return ret; + if (!(ret & mask)) + return REGULATOR_STATUS_OFF; + + /* Is it reporting under voltage? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_UV_STATUS); + if (ret < 0) + return ret; + if (ret & mask) + return REGULATOR_STATUS_ERROR; + + ret = wm831x_gp_ldo_get_mode(rdev); + if (ret < 0) + return ret; + else + return regulator_mode_to_status(ret); +} + +static unsigned int wm831x_gp_ldo_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, + int output_uV, int load_uA) +{ + if (load_uA < 20000) + return REGULATOR_MODE_STANDBY; + if (load_uA < 50000) + return REGULATOR_MODE_IDLE; + return REGULATOR_MODE_NORMAL; +} + + +static const struct regulator_ops wm831x_gp_ldo_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_suspend_voltage = wm831x_gp_ldo_set_suspend_voltage, + .get_mode = wm831x_gp_ldo_get_mode, + .set_mode = wm831x_gp_ldo_set_mode, + .get_status = wm831x_gp_ldo_get_status, + .get_optimum_mode = wm831x_gp_ldo_get_optimum_mode, + .get_bypass = regulator_get_bypass_regmap, + .set_bypass = regulator_set_bypass_regmap, + + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static int wm831x_gp_ldo_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + struct regulator_config config = { }; + int id; + struct wm831x_ldo *ldo; + struct resource *res; + int ret, irq; + + if (pdata && pdata->wm831x_num) + id = (pdata->wm831x_num * 10) + 1; + else + id = 0; + id = pdev->id - id; + + dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); + + ldo = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_ldo), GFP_KERNEL); + if (!ldo) + return -ENOMEM; + + ldo->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_REG, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No REG resource\n"); + ret = -EINVAL; + goto err; + } + ldo->base = res->start; + + snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1); + ldo->desc.name = ldo->name; + + snprintf(ldo->supply_name, sizeof(ldo->supply_name), + "LDO%dVDD", id + 1); + ldo->desc.supply_name = ldo->supply_name; + + ldo->desc.id = id; + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.n_voltages = 32; + ldo->desc.ops = &wm831x_gp_ldo_ops; + ldo->desc.owner = THIS_MODULE; + ldo->desc.vsel_reg = ldo->base + WM831X_LDO_ON_CONTROL; + ldo->desc.vsel_mask = WM831X_LDO1_ON_VSEL_MASK; + ldo->desc.enable_reg = WM831X_LDO_ENABLE; + ldo->desc.enable_mask = 1 << id; + ldo->desc.bypass_reg = ldo->base; + ldo->desc.bypass_mask = WM831X_LDO1_SWI; + ldo->desc.linear_ranges = wm831x_gp_ldo_ranges; + ldo->desc.n_linear_ranges = ARRAY_SIZE(wm831x_gp_ldo_ranges); + + config.dev = pdev->dev.parent; + if (pdata) + config.init_data = pdata->ldo[id]; + config.driver_data = ldo; + config.regmap = wm831x->regmap; + + ldo->regulator = devm_regulator_register(&pdev->dev, &ldo->desc, + &config); + if (IS_ERR(ldo->regulator)) { + ret = PTR_ERR(ldo->regulator); + dev_err(wm831x->dev, "Failed to register LDO%d: %d\n", + id + 1, ret); + goto err; + } + + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "UV")); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + wm831x_ldo_uv_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + ldo->name, + ldo); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err; + } + + platform_set_drvdata(pdev, ldo); + + return 0; + +err: + return ret; +} + +static struct platform_driver wm831x_gp_ldo_driver = { + .probe = wm831x_gp_ldo_probe, + .driver = { + .name = "wm831x-ldo", + }, +}; + +/* + * Analogue LDOs + */ + +static const struct regulator_linear_range wm831x_aldo_ranges[] = { + REGULATOR_LINEAR_RANGE(1000000, 0, 12, 50000), + REGULATOR_LINEAR_RANGE(1700000, 13, 31, 100000), +}; + +static int wm831x_aldo_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int sel, reg = ldo->base + WM831X_LDO_SLEEP_CONTROL; + + sel = regulator_map_voltage_linear_range(rdev, uV, uV); + if (sel < 0) + return sel; + + return wm831x_set_bits(wm831x, reg, WM831X_LDO7_ON_VSEL_MASK, sel); +} + +static unsigned int wm831x_aldo_get_mode(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + ret = wm831x_reg_read(wm831x, on_reg); + if (ret < 0) + return 0; + + if (ret & WM831X_LDO7_ON_MODE) + return REGULATOR_MODE_IDLE; + else + return REGULATOR_MODE_NORMAL; +} + +static int wm831x_aldo_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int on_reg = ldo->base + WM831X_LDO_ON_CONTROL; + int ret; + + + switch (mode) { + case REGULATOR_MODE_NORMAL: + ret = wm831x_set_bits(wm831x, on_reg, WM831X_LDO7_ON_MODE, 0); + if (ret < 0) + return ret; + break; + + case REGULATOR_MODE_IDLE: + ret = wm831x_set_bits(wm831x, on_reg, WM831X_LDO7_ON_MODE, + WM831X_LDO7_ON_MODE); + if (ret < 0) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm831x_aldo_get_status(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int ret; + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS); + if (ret < 0) + return ret; + if (!(ret & mask)) + return REGULATOR_STATUS_OFF; + + /* Is it reporting under voltage? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_UV_STATUS); + if (ret < 0) + return ret; + if (ret & mask) + return REGULATOR_STATUS_ERROR; + + ret = wm831x_aldo_get_mode(rdev); + if (ret < 0) + return ret; + else + return regulator_mode_to_status(ret); +} + +static const struct regulator_ops wm831x_aldo_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_suspend_voltage = wm831x_aldo_set_suspend_voltage, + .get_mode = wm831x_aldo_get_mode, + .set_mode = wm831x_aldo_set_mode, + .get_status = wm831x_aldo_get_status, + .set_bypass = regulator_set_bypass_regmap, + .get_bypass = regulator_get_bypass_regmap, + + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static int wm831x_aldo_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + struct regulator_config config = { }; + int id; + struct wm831x_ldo *ldo; + struct resource *res; + int ret, irq; + + if (pdata && pdata->wm831x_num) + id = (pdata->wm831x_num * 10) + 1; + else + id = 0; + id = pdev->id - id; + + dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); + + ldo = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_ldo), GFP_KERNEL); + if (!ldo) + return -ENOMEM; + + ldo->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_REG, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No REG resource\n"); + ret = -EINVAL; + goto err; + } + ldo->base = res->start; + + snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1); + ldo->desc.name = ldo->name; + + snprintf(ldo->supply_name, sizeof(ldo->supply_name), + "LDO%dVDD", id + 1); + ldo->desc.supply_name = ldo->supply_name; + + ldo->desc.id = id; + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.n_voltages = 32; + ldo->desc.linear_ranges = wm831x_aldo_ranges; + ldo->desc.n_linear_ranges = ARRAY_SIZE(wm831x_aldo_ranges); + ldo->desc.ops = &wm831x_aldo_ops; + ldo->desc.owner = THIS_MODULE; + ldo->desc.vsel_reg = ldo->base + WM831X_LDO_ON_CONTROL; + ldo->desc.vsel_mask = WM831X_LDO7_ON_VSEL_MASK; + ldo->desc.enable_reg = WM831X_LDO_ENABLE; + ldo->desc.enable_mask = 1 << id; + ldo->desc.bypass_reg = ldo->base; + ldo->desc.bypass_mask = WM831X_LDO7_SWI; + + config.dev = pdev->dev.parent; + if (pdata) + config.init_data = pdata->ldo[id]; + config.driver_data = ldo; + config.regmap = wm831x->regmap; + + ldo->regulator = devm_regulator_register(&pdev->dev, &ldo->desc, + &config); + if (IS_ERR(ldo->regulator)) { + ret = PTR_ERR(ldo->regulator); + dev_err(wm831x->dev, "Failed to register LDO%d: %d\n", + id + 1, ret); + goto err; + } + + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "UV")); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + wm831x_ldo_uv_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + ldo->name, ldo); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n", + irq, ret); + goto err; + } + + platform_set_drvdata(pdev, ldo); + + return 0; + +err: + return ret; +} + +static struct platform_driver wm831x_aldo_driver = { + .probe = wm831x_aldo_probe, + .driver = { + .name = "wm831x-aldo", + }, +}; + +/* + * Alive LDO + */ + +#define WM831X_ALIVE_LDO_MAX_SELECTOR 0xf + +static int wm831x_alive_ldo_set_suspend_voltage(struct regulator_dev *rdev, + int uV) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int sel, reg = ldo->base + WM831X_ALIVE_LDO_SLEEP_CONTROL; + + sel = regulator_map_voltage_linear(rdev, uV, uV); + if (sel < 0) + return sel; + + return wm831x_set_bits(wm831x, reg, WM831X_LDO11_ON_VSEL_MASK, sel); +} + +static int wm831x_alive_ldo_get_status(struct regulator_dev *rdev) +{ + struct wm831x_ldo *ldo = rdev_get_drvdata(rdev); + struct wm831x *wm831x = ldo->wm831x; + int mask = 1 << rdev_get_id(rdev); + int ret; + + /* Is the regulator on? */ + ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS); + if (ret < 0) + return ret; + if (ret & mask) + return REGULATOR_STATUS_ON; + else + return REGULATOR_STATUS_OFF; +} + +static const struct regulator_ops wm831x_alive_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_suspend_voltage = wm831x_alive_ldo_set_suspend_voltage, + .get_status = wm831x_alive_ldo_get_status, + + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static int wm831x_alive_ldo_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + struct regulator_config config = { }; + int id; + struct wm831x_ldo *ldo; + struct resource *res; + int ret; + + if (pdata && pdata->wm831x_num) + id = (pdata->wm831x_num * 10) + 1; + else + id = 0; + id = pdev->id - id; + + + dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); + + ldo = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_ldo), GFP_KERNEL); + if (!ldo) + return -ENOMEM; + + ldo->wm831x = wm831x; + + res = platform_get_resource(pdev, IORESOURCE_REG, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No REG resource\n"); + ret = -EINVAL; + goto err; + } + ldo->base = res->start; + + snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1); + ldo->desc.name = ldo->name; + + snprintf(ldo->supply_name, sizeof(ldo->supply_name), + "LDO%dVDD", id + 1); + ldo->desc.supply_name = ldo->supply_name; + + ldo->desc.id = id; + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.n_voltages = WM831X_ALIVE_LDO_MAX_SELECTOR + 1; + ldo->desc.ops = &wm831x_alive_ldo_ops; + ldo->desc.owner = THIS_MODULE; + ldo->desc.vsel_reg = ldo->base + WM831X_ALIVE_LDO_ON_CONTROL; + ldo->desc.vsel_mask = WM831X_LDO11_ON_VSEL_MASK; + ldo->desc.enable_reg = WM831X_LDO_ENABLE; + ldo->desc.enable_mask = 1 << id; + ldo->desc.min_uV = 800000; + ldo->desc.uV_step = 50000; + ldo->desc.enable_time = 1000; + + config.dev = pdev->dev.parent; + if (pdata) + config.init_data = pdata->ldo[id]; + config.driver_data = ldo; + config.regmap = wm831x->regmap; + + ldo->regulator = devm_regulator_register(&pdev->dev, &ldo->desc, + &config); + if (IS_ERR(ldo->regulator)) { + ret = PTR_ERR(ldo->regulator); + dev_err(wm831x->dev, "Failed to register LDO%d: %d\n", + id + 1, ret); + goto err; + } + + platform_set_drvdata(pdev, ldo); + + return 0; + +err: + return ret; +} + +static struct platform_driver wm831x_alive_ldo_driver = { + .probe = wm831x_alive_ldo_probe, + .driver = { + .name = "wm831x-alive-ldo", + }, +}; + +static struct platform_driver * const drivers[] = { + &wm831x_gp_ldo_driver, + &wm831x_aldo_driver, + &wm831x_alive_ldo_driver, +}; + +static int __init wm831x_ldo_init(void) +{ + return platform_register_drivers(drivers, ARRAY_SIZE(drivers)); +} +subsys_initcall(wm831x_ldo_init); + +static void __exit wm831x_ldo_exit(void) +{ + platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); +} +module_exit(wm831x_ldo_exit); + +/* Module information */ +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("WM831x LDO driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-ldo"); +MODULE_ALIAS("platform:wm831x-aldo"); +MODULE_ALIAS("platform:wm831x-aliveldo"); diff --git a/drivers/regulator/wm8350-regulator.c b/drivers/regulator/wm8350-regulator.c new file mode 100644 index 000000000..8ad11b074 --- /dev/null +++ b/drivers/regulator/wm8350-regulator.c @@ -0,0 +1,1397 @@ +/* + * wm8350.c -- Voltage and current regulation for the Wolfson WM8350 PMIC + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * linux@wolfsonmicro.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/pmic.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> + +/* Maximum value possible for VSEL */ +#define WM8350_DCDC_MAX_VSEL 0x66 + +/* Microamps */ +static const int isink_cur[] = { + 4, + 5, + 6, + 7, + 8, + 10, + 11, + 14, + 16, + 19, + 23, + 27, + 32, + 39, + 46, + 54, + 65, + 77, + 92, + 109, + 130, + 154, + 183, + 218, + 259, + 308, + 367, + 436, + 518, + 616, + 733, + 872, + 1037, + 1233, + 1466, + 1744, + 2073, + 2466, + 2933, + 3487, + 4147, + 4932, + 5865, + 6975, + 8294, + 9864, + 11730, + 13949, + 16589, + 19728, + 23460, + 27899, + 33178, + 39455, + 46920, + 55798, + 66355, + 78910, + 93840, + 111596, + 132710, + 157820, + 187681, + 223191 +}; + +static int get_isink_val(int min_uA, int max_uA, u16 *setting) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(isink_cur); i++) { + if (min_uA <= isink_cur[i] && max_uA >= isink_cur[i]) { + *setting = i; + return 0; + } + } + return -EINVAL; +} + +static int wm8350_isink_set_current(struct regulator_dev *rdev, int min_uA, + int max_uA) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int isink = rdev_get_id(rdev); + u16 val, setting; + int ret; + + ret = get_isink_val(min_uA, max_uA, &setting); + if (ret != 0) + return ret; + + switch (isink) { + case WM8350_ISINK_A: + val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_A) & + ~WM8350_CS1_ISEL_MASK; + wm8350_reg_write(wm8350, WM8350_CURRENT_SINK_DRIVER_A, + val | setting); + break; + case WM8350_ISINK_B: + val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_B) & + ~WM8350_CS1_ISEL_MASK; + wm8350_reg_write(wm8350, WM8350_CURRENT_SINK_DRIVER_B, + val | setting); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8350_isink_get_current(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int isink = rdev_get_id(rdev); + u16 val; + + switch (isink) { + case WM8350_ISINK_A: + val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_A) & + WM8350_CS1_ISEL_MASK; + break; + case WM8350_ISINK_B: + val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_B) & + WM8350_CS1_ISEL_MASK; + break; + default: + return 0; + } + + return isink_cur[val]; +} + +/* turn on ISINK followed by DCDC */ +static int wm8350_isink_enable(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int isink = rdev_get_id(rdev); + + switch (isink) { + case WM8350_ISINK_A: + switch (wm8350->pmic.isink_A_dcdc) { + case WM8350_DCDC_2: + case WM8350_DCDC_5: + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_7, + WM8350_CS1_ENA); + wm8350_set_bits(wm8350, WM8350_CSA_FLASH_CONTROL, + WM8350_CS1_DRIVE); + wm8350_set_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, + 1 << (wm8350->pmic.isink_A_dcdc - + WM8350_DCDC_1)); + break; + default: + return -EINVAL; + } + break; + case WM8350_ISINK_B: + switch (wm8350->pmic.isink_B_dcdc) { + case WM8350_DCDC_2: + case WM8350_DCDC_5: + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_7, + WM8350_CS2_ENA); + wm8350_set_bits(wm8350, WM8350_CSB_FLASH_CONTROL, + WM8350_CS2_DRIVE); + wm8350_set_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, + 1 << (wm8350->pmic.isink_B_dcdc - + WM8350_DCDC_1)); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int wm8350_isink_disable(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int isink = rdev_get_id(rdev); + + switch (isink) { + case WM8350_ISINK_A: + switch (wm8350->pmic.isink_A_dcdc) { + case WM8350_DCDC_2: + case WM8350_DCDC_5: + wm8350_clear_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, + 1 << (wm8350->pmic.isink_A_dcdc - + WM8350_DCDC_1)); + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_7, + WM8350_CS1_ENA); + break; + default: + return -EINVAL; + } + break; + case WM8350_ISINK_B: + switch (wm8350->pmic.isink_B_dcdc) { + case WM8350_DCDC_2: + case WM8350_DCDC_5: + wm8350_clear_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, + 1 << (wm8350->pmic.isink_B_dcdc - + WM8350_DCDC_1)); + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_7, + WM8350_CS2_ENA); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int wm8350_isink_is_enabled(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int isink = rdev_get_id(rdev); + + switch (isink) { + case WM8350_ISINK_A: + return wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_A) & + 0x8000; + case WM8350_ISINK_B: + return wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_B) & + 0x8000; + } + return -EINVAL; +} + +static int wm8350_isink_enable_time(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int isink = rdev_get_id(rdev); + int reg; + + switch (isink) { + case WM8350_ISINK_A: + reg = wm8350_reg_read(wm8350, WM8350_CSA_FLASH_CONTROL); + break; + case WM8350_ISINK_B: + reg = wm8350_reg_read(wm8350, WM8350_CSB_FLASH_CONTROL); + break; + default: + return -EINVAL; + } + + if (reg & WM8350_CS1_FLASH_MODE) { + switch (reg & WM8350_CS1_ON_RAMP_MASK) { + case 0: + return 0; + case 1: + return 1950; + case 2: + return 3910; + case 3: + return 7800; + } + } else { + switch (reg & WM8350_CS1_ON_RAMP_MASK) { + case 0: + return 0; + case 1: + return 250000; + case 2: + return 500000; + case 3: + return 1000000; + } + } + + return -EINVAL; +} + + +int wm8350_isink_set_flash(struct wm8350 *wm8350, int isink, u16 mode, + u16 trigger, u16 duration, u16 on_ramp, u16 off_ramp, + u16 drive) +{ + switch (isink) { + case WM8350_ISINK_A: + wm8350_reg_write(wm8350, WM8350_CSA_FLASH_CONTROL, + (mode ? WM8350_CS1_FLASH_MODE : 0) | + (trigger ? WM8350_CS1_TRIGSRC : 0) | + duration | on_ramp | off_ramp | drive); + break; + case WM8350_ISINK_B: + wm8350_reg_write(wm8350, WM8350_CSB_FLASH_CONTROL, + (mode ? WM8350_CS2_FLASH_MODE : 0) | + (trigger ? WM8350_CS2_TRIGSRC : 0) | + duration | on_ramp | off_ramp | drive); + break; + default: + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_isink_set_flash); + +static int wm8350_dcdc_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int sel, volt_reg, dcdc = rdev_get_id(rdev); + u16 val; + + dev_dbg(wm8350->dev, "%s %d mV %d\n", __func__, dcdc, uV / 1000); + + switch (dcdc) { + case WM8350_DCDC_1: + volt_reg = WM8350_DCDC1_LOW_POWER; + break; + case WM8350_DCDC_3: + volt_reg = WM8350_DCDC3_LOW_POWER; + break; + case WM8350_DCDC_4: + volt_reg = WM8350_DCDC4_LOW_POWER; + break; + case WM8350_DCDC_6: + volt_reg = WM8350_DCDC6_LOW_POWER; + break; + case WM8350_DCDC_2: + case WM8350_DCDC_5: + default: + return -EINVAL; + } + + sel = regulator_map_voltage_linear(rdev, uV, uV); + if (sel < 0) + return sel; + + /* all DCDCs have same mV bits */ + val = wm8350_reg_read(wm8350, volt_reg) & ~WM8350_DC1_VSEL_MASK; + wm8350_reg_write(wm8350, volt_reg, val | sel); + return 0; +} + +static int wm8350_dcdc_set_suspend_enable(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int dcdc = rdev_get_id(rdev); + u16 val; + + switch (dcdc) { + case WM8350_DCDC_1: + val = wm8350_reg_read(wm8350, WM8350_DCDC1_LOW_POWER) + & ~WM8350_DCDC_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC1_LOW_POWER, + val | wm8350->pmic.dcdc1_hib_mode); + break; + case WM8350_DCDC_3: + val = wm8350_reg_read(wm8350, WM8350_DCDC3_LOW_POWER) + & ~WM8350_DCDC_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC3_LOW_POWER, + val | wm8350->pmic.dcdc3_hib_mode); + break; + case WM8350_DCDC_4: + val = wm8350_reg_read(wm8350, WM8350_DCDC4_LOW_POWER) + & ~WM8350_DCDC_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC4_LOW_POWER, + val | wm8350->pmic.dcdc4_hib_mode); + break; + case WM8350_DCDC_6: + val = wm8350_reg_read(wm8350, WM8350_DCDC6_LOW_POWER) + & ~WM8350_DCDC_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC6_LOW_POWER, + val | wm8350->pmic.dcdc6_hib_mode); + break; + case WM8350_DCDC_2: + case WM8350_DCDC_5: + default: + return -EINVAL; + } + + return 0; +} + +static int wm8350_dcdc_set_suspend_disable(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int dcdc = rdev_get_id(rdev); + u16 val; + + switch (dcdc) { + case WM8350_DCDC_1: + val = wm8350_reg_read(wm8350, WM8350_DCDC1_LOW_POWER); + wm8350->pmic.dcdc1_hib_mode = val & WM8350_DCDC_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC1_LOW_POWER, + val | WM8350_DCDC_HIB_MODE_DIS); + break; + case WM8350_DCDC_3: + val = wm8350_reg_read(wm8350, WM8350_DCDC3_LOW_POWER); + wm8350->pmic.dcdc3_hib_mode = val & WM8350_DCDC_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC3_LOW_POWER, + val | WM8350_DCDC_HIB_MODE_DIS); + break; + case WM8350_DCDC_4: + val = wm8350_reg_read(wm8350, WM8350_DCDC4_LOW_POWER); + wm8350->pmic.dcdc4_hib_mode = val & WM8350_DCDC_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC4_LOW_POWER, + val | WM8350_DCDC_HIB_MODE_DIS); + break; + case WM8350_DCDC_6: + val = wm8350_reg_read(wm8350, WM8350_DCDC6_LOW_POWER); + wm8350->pmic.dcdc6_hib_mode = val & WM8350_DCDC_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC6_LOW_POWER, + val | WM8350_DCDC_HIB_MODE_DIS); + break; + case WM8350_DCDC_2: + case WM8350_DCDC_5: + default: + return -EINVAL; + } + + return 0; +} + +static int wm8350_dcdc25_set_suspend_enable(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int dcdc = rdev_get_id(rdev); + u16 val; + + switch (dcdc) { + case WM8350_DCDC_2: + val = wm8350_reg_read(wm8350, WM8350_DCDC2_CONTROL) + & ~WM8350_DC2_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC2_CONTROL, val | + (WM8350_DC2_HIB_MODE_ACTIVE << WM8350_DC2_HIB_MODE_SHIFT)); + break; + case WM8350_DCDC_5: + val = wm8350_reg_read(wm8350, WM8350_DCDC5_CONTROL) + & ~WM8350_DC5_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC5_CONTROL, val | + (WM8350_DC5_HIB_MODE_ACTIVE << WM8350_DC5_HIB_MODE_SHIFT)); + break; + default: + return -EINVAL; + } + return 0; +} + +static int wm8350_dcdc25_set_suspend_disable(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int dcdc = rdev_get_id(rdev); + u16 val; + + switch (dcdc) { + case WM8350_DCDC_2: + val = wm8350_reg_read(wm8350, WM8350_DCDC2_CONTROL) + & ~WM8350_DC2_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC2_CONTROL, val | + (WM8350_DC2_HIB_MODE_DISABLE << WM8350_DC2_HIB_MODE_SHIFT)); + break; + case WM8350_DCDC_5: + val = wm8350_reg_read(wm8350, WM8350_DCDC5_CONTROL) + & ~WM8350_DC5_HIB_MODE_MASK; + wm8350_reg_write(wm8350, WM8350_DCDC5_CONTROL, val | + (WM8350_DC5_HIB_MODE_DISABLE << WM8350_DC5_HIB_MODE_SHIFT)); + break; + default: + return -EINVAL; + } + return 0; +} + +static int wm8350_dcdc_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int dcdc = rdev_get_id(rdev); + u16 *hib_mode; + + switch (dcdc) { + case WM8350_DCDC_1: + hib_mode = &wm8350->pmic.dcdc1_hib_mode; + break; + case WM8350_DCDC_3: + hib_mode = &wm8350->pmic.dcdc3_hib_mode; + break; + case WM8350_DCDC_4: + hib_mode = &wm8350->pmic.dcdc4_hib_mode; + break; + case WM8350_DCDC_6: + hib_mode = &wm8350->pmic.dcdc6_hib_mode; + break; + case WM8350_DCDC_2: + case WM8350_DCDC_5: + default: + return -EINVAL; + } + + switch (mode) { + case REGULATOR_MODE_NORMAL: + *hib_mode = WM8350_DCDC_HIB_MODE_IMAGE; + break; + case REGULATOR_MODE_IDLE: + *hib_mode = WM8350_DCDC_HIB_MODE_STANDBY; + break; + case REGULATOR_MODE_STANDBY: + *hib_mode = WM8350_DCDC_HIB_MODE_LDO_IM; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct regulator_linear_range wm8350_ldo_ranges[] = { + REGULATOR_LINEAR_RANGE(900000, 0, 15, 50000), + REGULATOR_LINEAR_RANGE(1800000, 16, 31, 100000), +}; + +static int wm8350_ldo_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int sel, volt_reg, ldo = rdev_get_id(rdev); + u16 val; + + dev_dbg(wm8350->dev, "%s %d mV %d\n", __func__, ldo, uV / 1000); + + switch (ldo) { + case WM8350_LDO_1: + volt_reg = WM8350_LDO1_LOW_POWER; + break; + case WM8350_LDO_2: + volt_reg = WM8350_LDO2_LOW_POWER; + break; + case WM8350_LDO_3: + volt_reg = WM8350_LDO3_LOW_POWER; + break; + case WM8350_LDO_4: + volt_reg = WM8350_LDO4_LOW_POWER; + break; + default: + return -EINVAL; + } + + sel = regulator_map_voltage_linear_range(rdev, uV, uV); + if (sel < 0) + return sel; + + /* all LDOs have same mV bits */ + val = wm8350_reg_read(wm8350, volt_reg) & ~WM8350_LDO1_VSEL_MASK; + wm8350_reg_write(wm8350, volt_reg, val | sel); + return 0; +} + +static int wm8350_ldo_set_suspend_enable(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int volt_reg, ldo = rdev_get_id(rdev); + u16 val; + + switch (ldo) { + case WM8350_LDO_1: + volt_reg = WM8350_LDO1_LOW_POWER; + break; + case WM8350_LDO_2: + volt_reg = WM8350_LDO2_LOW_POWER; + break; + case WM8350_LDO_3: + volt_reg = WM8350_LDO3_LOW_POWER; + break; + case WM8350_LDO_4: + volt_reg = WM8350_LDO4_LOW_POWER; + break; + default: + return -EINVAL; + } + + /* all LDOs have same mV bits */ + val = wm8350_reg_read(wm8350, volt_reg) & ~WM8350_LDO1_HIB_MODE_MASK; + wm8350_reg_write(wm8350, volt_reg, val); + return 0; +} + +static int wm8350_ldo_set_suspend_disable(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int volt_reg, ldo = rdev_get_id(rdev); + u16 val; + + switch (ldo) { + case WM8350_LDO_1: + volt_reg = WM8350_LDO1_LOW_POWER; + break; + case WM8350_LDO_2: + volt_reg = WM8350_LDO2_LOW_POWER; + break; + case WM8350_LDO_3: + volt_reg = WM8350_LDO3_LOW_POWER; + break; + case WM8350_LDO_4: + volt_reg = WM8350_LDO4_LOW_POWER; + break; + default: + return -EINVAL; + } + + /* all LDOs have same mV bits */ + val = wm8350_reg_read(wm8350, volt_reg) & ~WM8350_LDO1_HIB_MODE_MASK; + wm8350_reg_write(wm8350, volt_reg, val | WM8350_LDO1_HIB_MODE_DIS); + return 0; +} + +int wm8350_dcdc_set_slot(struct wm8350 *wm8350, int dcdc, u16 start, + u16 stop, u16 fault) +{ + int slot_reg; + u16 val; + + dev_dbg(wm8350->dev, "%s %d start %d stop %d\n", + __func__, dcdc, start, stop); + + /* slot valid ? */ + if (start > 15 || stop > 15) + return -EINVAL; + + switch (dcdc) { + case WM8350_DCDC_1: + slot_reg = WM8350_DCDC1_TIMEOUTS; + break; + case WM8350_DCDC_2: + slot_reg = WM8350_DCDC2_TIMEOUTS; + break; + case WM8350_DCDC_3: + slot_reg = WM8350_DCDC3_TIMEOUTS; + break; + case WM8350_DCDC_4: + slot_reg = WM8350_DCDC4_TIMEOUTS; + break; + case WM8350_DCDC_5: + slot_reg = WM8350_DCDC5_TIMEOUTS; + break; + case WM8350_DCDC_6: + slot_reg = WM8350_DCDC6_TIMEOUTS; + break; + default: + return -EINVAL; + } + + val = wm8350_reg_read(wm8350, slot_reg) & + ~(WM8350_DC1_ENSLOT_MASK | WM8350_DC1_SDSLOT_MASK | + WM8350_DC1_ERRACT_MASK); + wm8350_reg_write(wm8350, slot_reg, + val | (start << WM8350_DC1_ENSLOT_SHIFT) | + (stop << WM8350_DC1_SDSLOT_SHIFT) | + (fault << WM8350_DC1_ERRACT_SHIFT)); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_dcdc_set_slot); + +int wm8350_ldo_set_slot(struct wm8350 *wm8350, int ldo, u16 start, u16 stop) +{ + int slot_reg; + u16 val; + + dev_dbg(wm8350->dev, "%s %d start %d stop %d\n", + __func__, ldo, start, stop); + + /* slot valid ? */ + if (start > 15 || stop > 15) + return -EINVAL; + + switch (ldo) { + case WM8350_LDO_1: + slot_reg = WM8350_LDO1_TIMEOUTS; + break; + case WM8350_LDO_2: + slot_reg = WM8350_LDO2_TIMEOUTS; + break; + case WM8350_LDO_3: + slot_reg = WM8350_LDO3_TIMEOUTS; + break; + case WM8350_LDO_4: + slot_reg = WM8350_LDO4_TIMEOUTS; + break; + default: + return -EINVAL; + } + + val = wm8350_reg_read(wm8350, slot_reg) & ~WM8350_LDO1_SDSLOT_MASK; + wm8350_reg_write(wm8350, slot_reg, val | ((start << 10) | (stop << 6))); + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_ldo_set_slot); + +int wm8350_dcdc25_set_mode(struct wm8350 *wm8350, int dcdc, u16 mode, + u16 ilim, u16 ramp, u16 feedback) +{ + u16 val; + + dev_dbg(wm8350->dev, "%s %d mode: %s %s\n", __func__, dcdc, + mode ? "normal" : "boost", ilim ? "low" : "normal"); + + switch (dcdc) { + case WM8350_DCDC_2: + val = wm8350_reg_read(wm8350, WM8350_DCDC2_CONTROL) + & ~(WM8350_DC2_MODE_MASK | WM8350_DC2_ILIM_MASK | + WM8350_DC2_RMP_MASK | WM8350_DC2_FBSRC_MASK); + wm8350_reg_write(wm8350, WM8350_DCDC2_CONTROL, val | + (mode << WM8350_DC2_MODE_SHIFT) | + (ilim << WM8350_DC2_ILIM_SHIFT) | + (ramp << WM8350_DC2_RMP_SHIFT) | + (feedback << WM8350_DC2_FBSRC_SHIFT)); + break; + case WM8350_DCDC_5: + val = wm8350_reg_read(wm8350, WM8350_DCDC5_CONTROL) + & ~(WM8350_DC5_MODE_MASK | WM8350_DC5_ILIM_MASK | + WM8350_DC5_RMP_MASK | WM8350_DC5_FBSRC_MASK); + wm8350_reg_write(wm8350, WM8350_DCDC5_CONTROL, val | + (mode << WM8350_DC5_MODE_SHIFT) | + (ilim << WM8350_DC5_ILIM_SHIFT) | + (ramp << WM8350_DC5_RMP_SHIFT) | + (feedback << WM8350_DC5_FBSRC_SHIFT)); + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_dcdc25_set_mode); + +static int force_continuous_enable(struct wm8350 *wm8350, int dcdc, int enable) +{ + int reg = 0, ret; + + switch (dcdc) { + case WM8350_DCDC_1: + reg = WM8350_DCDC1_FORCE_PWM; + break; + case WM8350_DCDC_3: + reg = WM8350_DCDC3_FORCE_PWM; + break; + case WM8350_DCDC_4: + reg = WM8350_DCDC4_FORCE_PWM; + break; + case WM8350_DCDC_6: + reg = WM8350_DCDC6_FORCE_PWM; + break; + default: + return -EINVAL; + } + + if (enable) + ret = wm8350_set_bits(wm8350, reg, + WM8350_DCDC1_FORCE_PWM_ENA); + else + ret = wm8350_clear_bits(wm8350, reg, + WM8350_DCDC1_FORCE_PWM_ENA); + return ret; +} + +static int wm8350_dcdc_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int dcdc = rdev_get_id(rdev); + u16 val; + + if (dcdc < WM8350_DCDC_1 || dcdc > WM8350_DCDC_6) + return -EINVAL; + + if (dcdc == WM8350_DCDC_2 || dcdc == WM8350_DCDC_5) + return -EINVAL; + + val = 1 << (dcdc - WM8350_DCDC_1); + + switch (mode) { + case REGULATOR_MODE_FAST: + /* force continuous mode */ + wm8350_set_bits(wm8350, WM8350_DCDC_ACTIVE_OPTIONS, val); + wm8350_clear_bits(wm8350, WM8350_DCDC_SLEEP_OPTIONS, val); + force_continuous_enable(wm8350, dcdc, 1); + break; + case REGULATOR_MODE_NORMAL: + /* active / pulse skipping */ + wm8350_set_bits(wm8350, WM8350_DCDC_ACTIVE_OPTIONS, val); + wm8350_clear_bits(wm8350, WM8350_DCDC_SLEEP_OPTIONS, val); + force_continuous_enable(wm8350, dcdc, 0); + break; + case REGULATOR_MODE_IDLE: + /* standby mode */ + force_continuous_enable(wm8350, dcdc, 0); + wm8350_clear_bits(wm8350, WM8350_DCDC_SLEEP_OPTIONS, val); + wm8350_clear_bits(wm8350, WM8350_DCDC_ACTIVE_OPTIONS, val); + break; + case REGULATOR_MODE_STANDBY: + /* LDO mode */ + force_continuous_enable(wm8350, dcdc, 0); + wm8350_set_bits(wm8350, WM8350_DCDC_SLEEP_OPTIONS, val); + break; + } + + return 0; +} + +static unsigned int wm8350_dcdc_get_mode(struct regulator_dev *rdev) +{ + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + int dcdc = rdev_get_id(rdev); + u16 mask, sleep, active, force; + int mode = REGULATOR_MODE_NORMAL; + int reg; + + switch (dcdc) { + case WM8350_DCDC_1: + reg = WM8350_DCDC1_FORCE_PWM; + break; + case WM8350_DCDC_3: + reg = WM8350_DCDC3_FORCE_PWM; + break; + case WM8350_DCDC_4: + reg = WM8350_DCDC4_FORCE_PWM; + break; + case WM8350_DCDC_6: + reg = WM8350_DCDC6_FORCE_PWM; + break; + default: + return -EINVAL; + } + + mask = 1 << (dcdc - WM8350_DCDC_1); + active = wm8350_reg_read(wm8350, WM8350_DCDC_ACTIVE_OPTIONS) & mask; + force = wm8350_reg_read(wm8350, reg) & WM8350_DCDC1_FORCE_PWM_ENA; + sleep = wm8350_reg_read(wm8350, WM8350_DCDC_SLEEP_OPTIONS) & mask; + + dev_dbg(wm8350->dev, "mask %x active %x sleep %x force %x", + mask, active, sleep, force); + + if (active && !sleep) { + if (force) + mode = REGULATOR_MODE_FAST; + else + mode = REGULATOR_MODE_NORMAL; + } else if (!active && !sleep) + mode = REGULATOR_MODE_IDLE; + else if (sleep) + mode = REGULATOR_MODE_STANDBY; + + return mode; +} + +static unsigned int wm8350_ldo_get_mode(struct regulator_dev *rdev) +{ + return REGULATOR_MODE_NORMAL; +} + +struct wm8350_dcdc_efficiency { + int uA_load_min; + int uA_load_max; + unsigned int mode; +}; + +static const struct wm8350_dcdc_efficiency dcdc1_6_efficiency[] = { + {0, 10000, REGULATOR_MODE_STANDBY}, /* 0 - 10mA - LDO */ + {10000, 100000, REGULATOR_MODE_IDLE}, /* 10mA - 100mA - Standby */ + {100000, 1000000, REGULATOR_MODE_NORMAL}, /* > 100mA - Active */ + {-1, -1, REGULATOR_MODE_NORMAL}, +}; + +static const struct wm8350_dcdc_efficiency dcdc3_4_efficiency[] = { + {0, 10000, REGULATOR_MODE_STANDBY}, /* 0 - 10mA - LDO */ + {10000, 100000, REGULATOR_MODE_IDLE}, /* 10mA - 100mA - Standby */ + {100000, 800000, REGULATOR_MODE_NORMAL}, /* > 100mA - Active */ + {-1, -1, REGULATOR_MODE_NORMAL}, +}; + +static unsigned int get_mode(int uA, const struct wm8350_dcdc_efficiency *eff) +{ + int i = 0; + + while (eff[i].uA_load_min != -1) { + if (uA >= eff[i].uA_load_min && uA <= eff[i].uA_load_max) + return eff[i].mode; + i++; + } + return REGULATOR_MODE_NORMAL; +} + +/* Query the regulator for it's most efficient mode @ uV,uA + * WM8350 regulator efficiency is pretty similar over + * different input and output uV. + */ +static unsigned int wm8350_dcdc_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, + int output_uA) +{ + int dcdc = rdev_get_id(rdev), mode; + + switch (dcdc) { + case WM8350_DCDC_1: + case WM8350_DCDC_6: + mode = get_mode(output_uA, dcdc1_6_efficiency); + break; + case WM8350_DCDC_3: + case WM8350_DCDC_4: + mode = get_mode(output_uA, dcdc3_4_efficiency); + break; + default: + mode = REGULATOR_MODE_NORMAL; + break; + } + return mode; +} + +static const struct regulator_ops wm8350_dcdc_ops = { + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_mode = wm8350_dcdc_get_mode, + .set_mode = wm8350_dcdc_set_mode, + .get_optimum_mode = wm8350_dcdc_get_optimum_mode, + .set_suspend_voltage = wm8350_dcdc_set_suspend_voltage, + .set_suspend_enable = wm8350_dcdc_set_suspend_enable, + .set_suspend_disable = wm8350_dcdc_set_suspend_disable, + .set_suspend_mode = wm8350_dcdc_set_suspend_mode, +}; + +static const struct regulator_ops wm8350_dcdc2_5_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_suspend_enable = wm8350_dcdc25_set_suspend_enable, + .set_suspend_disable = wm8350_dcdc25_set_suspend_disable, +}; + +static const struct regulator_ops wm8350_ldo_ops = { + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_mode = wm8350_ldo_get_mode, + .set_suspend_voltage = wm8350_ldo_set_suspend_voltage, + .set_suspend_enable = wm8350_ldo_set_suspend_enable, + .set_suspend_disable = wm8350_ldo_set_suspend_disable, +}; + +static const struct regulator_ops wm8350_isink_ops = { + .set_current_limit = wm8350_isink_set_current, + .get_current_limit = wm8350_isink_get_current, + .enable = wm8350_isink_enable, + .disable = wm8350_isink_disable, + .is_enabled = wm8350_isink_is_enabled, + .enable_time = wm8350_isink_enable_time, +}; + +static const struct regulator_desc wm8350_reg[NUM_WM8350_REGULATORS] = { + { + .name = "DCDC1", + .id = WM8350_DCDC_1, + .ops = &wm8350_dcdc_ops, + .irq = WM8350_IRQ_UV_DC1, + .type = REGULATOR_VOLTAGE, + .n_voltages = WM8350_DCDC_MAX_VSEL + 1, + .min_uV = 850000, + .uV_step = 25000, + .vsel_reg = WM8350_DCDC1_CONTROL, + .vsel_mask = WM8350_DC1_VSEL_MASK, + .enable_reg = WM8350_DCDC_LDO_REQUESTED, + .enable_mask = WM8350_DC1_ENA, + .owner = THIS_MODULE, + }, + { + .name = "DCDC2", + .id = WM8350_DCDC_2, + .ops = &wm8350_dcdc2_5_ops, + .irq = WM8350_IRQ_UV_DC2, + .type = REGULATOR_VOLTAGE, + .enable_reg = WM8350_DCDC_LDO_REQUESTED, + .enable_mask = WM8350_DC2_ENA, + .owner = THIS_MODULE, + }, + { + .name = "DCDC3", + .id = WM8350_DCDC_3, + .ops = &wm8350_dcdc_ops, + .irq = WM8350_IRQ_UV_DC3, + .type = REGULATOR_VOLTAGE, + .n_voltages = WM8350_DCDC_MAX_VSEL + 1, + .min_uV = 850000, + .uV_step = 25000, + .vsel_reg = WM8350_DCDC3_CONTROL, + .vsel_mask = WM8350_DC3_VSEL_MASK, + .enable_reg = WM8350_DCDC_LDO_REQUESTED, + .enable_mask = WM8350_DC3_ENA, + .owner = THIS_MODULE, + }, + { + .name = "DCDC4", + .id = WM8350_DCDC_4, + .ops = &wm8350_dcdc_ops, + .irq = WM8350_IRQ_UV_DC4, + .type = REGULATOR_VOLTAGE, + .n_voltages = WM8350_DCDC_MAX_VSEL + 1, + .min_uV = 850000, + .uV_step = 25000, + .vsel_reg = WM8350_DCDC4_CONTROL, + .vsel_mask = WM8350_DC4_VSEL_MASK, + .enable_reg = WM8350_DCDC_LDO_REQUESTED, + .enable_mask = WM8350_DC4_ENA, + .owner = THIS_MODULE, + }, + { + .name = "DCDC5", + .id = WM8350_DCDC_5, + .ops = &wm8350_dcdc2_5_ops, + .irq = WM8350_IRQ_UV_DC5, + .type = REGULATOR_VOLTAGE, + .enable_reg = WM8350_DCDC_LDO_REQUESTED, + .enable_mask = WM8350_DC5_ENA, + .owner = THIS_MODULE, + }, + { + .name = "DCDC6", + .id = WM8350_DCDC_6, + .ops = &wm8350_dcdc_ops, + .irq = WM8350_IRQ_UV_DC6, + .type = REGULATOR_VOLTAGE, + .n_voltages = WM8350_DCDC_MAX_VSEL + 1, + .min_uV = 850000, + .uV_step = 25000, + .vsel_reg = WM8350_DCDC6_CONTROL, + .vsel_mask = WM8350_DC6_VSEL_MASK, + .enable_reg = WM8350_DCDC_LDO_REQUESTED, + .enable_mask = WM8350_DC6_ENA, + .owner = THIS_MODULE, + }, + { + .name = "LDO1", + .id = WM8350_LDO_1, + .ops = &wm8350_ldo_ops, + .irq = WM8350_IRQ_UV_LDO1, + .type = REGULATOR_VOLTAGE, + .n_voltages = WM8350_LDO1_VSEL_MASK + 1, + .linear_ranges = wm8350_ldo_ranges, + .n_linear_ranges = ARRAY_SIZE(wm8350_ldo_ranges), + .vsel_reg = WM8350_LDO1_CONTROL, + .vsel_mask = WM8350_LDO1_VSEL_MASK, + .enable_reg = WM8350_DCDC_LDO_REQUESTED, + .enable_mask = WM8350_LDO1_ENA, + .owner = THIS_MODULE, + }, + { + .name = "LDO2", + .id = WM8350_LDO_2, + .ops = &wm8350_ldo_ops, + .irq = WM8350_IRQ_UV_LDO2, + .type = REGULATOR_VOLTAGE, + .n_voltages = WM8350_LDO2_VSEL_MASK + 1, + .linear_ranges = wm8350_ldo_ranges, + .n_linear_ranges = ARRAY_SIZE(wm8350_ldo_ranges), + .vsel_reg = WM8350_LDO2_CONTROL, + .vsel_mask = WM8350_LDO2_VSEL_MASK, + .enable_reg = WM8350_DCDC_LDO_REQUESTED, + .enable_mask = WM8350_LDO2_ENA, + .owner = THIS_MODULE, + }, + { + .name = "LDO3", + .id = WM8350_LDO_3, + .ops = &wm8350_ldo_ops, + .irq = WM8350_IRQ_UV_LDO3, + .type = REGULATOR_VOLTAGE, + .n_voltages = WM8350_LDO3_VSEL_MASK + 1, + .linear_ranges = wm8350_ldo_ranges, + .n_linear_ranges = ARRAY_SIZE(wm8350_ldo_ranges), + .vsel_reg = WM8350_LDO3_CONTROL, + .vsel_mask = WM8350_LDO3_VSEL_MASK, + .enable_reg = WM8350_DCDC_LDO_REQUESTED, + .enable_mask = WM8350_LDO3_ENA, + .owner = THIS_MODULE, + }, + { + .name = "LDO4", + .id = WM8350_LDO_4, + .ops = &wm8350_ldo_ops, + .irq = WM8350_IRQ_UV_LDO4, + .type = REGULATOR_VOLTAGE, + .n_voltages = WM8350_LDO4_VSEL_MASK + 1, + .linear_ranges = wm8350_ldo_ranges, + .n_linear_ranges = ARRAY_SIZE(wm8350_ldo_ranges), + .vsel_reg = WM8350_LDO4_CONTROL, + .vsel_mask = WM8350_LDO4_VSEL_MASK, + .enable_reg = WM8350_DCDC_LDO_REQUESTED, + .enable_mask = WM8350_LDO4_ENA, + .owner = THIS_MODULE, + }, + { + .name = "ISINKA", + .id = WM8350_ISINK_A, + .ops = &wm8350_isink_ops, + .irq = WM8350_IRQ_CS1, + .type = REGULATOR_CURRENT, + .owner = THIS_MODULE, + }, + { + .name = "ISINKB", + .id = WM8350_ISINK_B, + .ops = &wm8350_isink_ops, + .irq = WM8350_IRQ_CS2, + .type = REGULATOR_CURRENT, + .owner = THIS_MODULE, + }, +}; + +static irqreturn_t pmic_uv_handler(int irq, void *data) +{ + struct regulator_dev *rdev = (struct regulator_dev *)data; + + mutex_lock(&rdev->mutex); + if (irq == WM8350_IRQ_CS1 || irq == WM8350_IRQ_CS2) + regulator_notifier_call_chain(rdev, + REGULATOR_EVENT_REGULATION_OUT, + NULL); + else + regulator_notifier_call_chain(rdev, + REGULATOR_EVENT_UNDER_VOLTAGE, + NULL); + mutex_unlock(&rdev->mutex); + + return IRQ_HANDLED; +} + +static int wm8350_regulator_probe(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = dev_get_drvdata(&pdev->dev); + struct regulator_config config = { }; + struct regulator_dev *rdev; + int ret; + u16 val; + + if (pdev->id < WM8350_DCDC_1 || pdev->id > WM8350_ISINK_B) + return -ENODEV; + + /* do any regulatior specific init */ + switch (pdev->id) { + case WM8350_DCDC_1: + val = wm8350_reg_read(wm8350, WM8350_DCDC1_LOW_POWER); + wm8350->pmic.dcdc1_hib_mode = val & WM8350_DCDC_HIB_MODE_MASK; + break; + case WM8350_DCDC_3: + val = wm8350_reg_read(wm8350, WM8350_DCDC3_LOW_POWER); + wm8350->pmic.dcdc3_hib_mode = val & WM8350_DCDC_HIB_MODE_MASK; + break; + case WM8350_DCDC_4: + val = wm8350_reg_read(wm8350, WM8350_DCDC4_LOW_POWER); + wm8350->pmic.dcdc4_hib_mode = val & WM8350_DCDC_HIB_MODE_MASK; + break; + case WM8350_DCDC_6: + val = wm8350_reg_read(wm8350, WM8350_DCDC6_LOW_POWER); + wm8350->pmic.dcdc6_hib_mode = val & WM8350_DCDC_HIB_MODE_MASK; + break; + } + + config.dev = &pdev->dev; + config.init_data = dev_get_platdata(&pdev->dev); + config.driver_data = dev_get_drvdata(&pdev->dev); + config.regmap = wm8350->regmap; + + /* register regulator */ + rdev = devm_regulator_register(&pdev->dev, &wm8350_reg[pdev->id], + &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", + wm8350_reg[pdev->id].name); + return PTR_ERR(rdev); + } + + /* register regulator IRQ */ + ret = wm8350_register_irq(wm8350, wm8350_reg[pdev->id].irq, + pmic_uv_handler, 0, "UV", rdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register regulator %s IRQ\n", + wm8350_reg[pdev->id].name); + return ret; + } + + return 0; +} + +static int wm8350_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + struct wm8350 *wm8350 = rdev_get_drvdata(rdev); + + wm8350_free_irq(wm8350, wm8350_reg[pdev->id].irq, rdev); + + return 0; +} + +int wm8350_register_regulator(struct wm8350 *wm8350, int reg, + struct regulator_init_data *initdata) +{ + struct platform_device *pdev; + int ret; + if (reg < 0 || reg >= NUM_WM8350_REGULATORS) + return -EINVAL; + + if (wm8350->pmic.pdev[reg]) + return -EBUSY; + + if (reg >= WM8350_DCDC_1 && reg <= WM8350_DCDC_6 && + reg > wm8350->pmic.max_dcdc) + return -ENODEV; + if (reg >= WM8350_ISINK_A && reg <= WM8350_ISINK_B && + reg > wm8350->pmic.max_isink) + return -ENODEV; + + pdev = platform_device_alloc("wm8350-regulator", reg); + if (!pdev) + return -ENOMEM; + + wm8350->pmic.pdev[reg] = pdev; + + initdata->driver_data = wm8350; + + pdev->dev.platform_data = initdata; + pdev->dev.parent = wm8350->dev; + platform_set_drvdata(pdev, wm8350); + + ret = platform_device_add(pdev); + + if (ret != 0) { + dev_err(wm8350->dev, "Failed to register regulator %d: %d\n", + reg, ret); + platform_device_put(pdev); + wm8350->pmic.pdev[reg] = NULL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_register_regulator); + +/** + * wm8350_register_led - Register a WM8350 LED output + * + * @param wm8350 The WM8350 device to configure. + * @param lednum LED device index to create. + * @param dcdc The DCDC to use for the LED. + * @param isink The ISINK to use for the LED. + * @param pdata Configuration for the LED. + * + * The WM8350 supports the use of an ISINK together with a DCDC to + * provide a power-efficient LED driver. This function registers the + * regulators and instantiates the platform device for a LED. The + * operating modes for the LED regulators must be configured using + * wm8350_isink_set_flash(), wm8350_dcdc25_set_mode() and + * wm8350_dcdc_set_slot() prior to calling this function. + */ +int wm8350_register_led(struct wm8350 *wm8350, int lednum, int dcdc, int isink, + struct wm8350_led_platform_data *pdata) +{ + struct wm8350_led *led; + struct platform_device *pdev; + int ret; + + if (lednum >= ARRAY_SIZE(wm8350->pmic.led) || lednum < 0) { + dev_err(wm8350->dev, "Invalid LED index %d\n", lednum); + return -ENODEV; + } + + led = &wm8350->pmic.led[lednum]; + + if (led->pdev) { + dev_err(wm8350->dev, "LED %d already allocated\n", lednum); + return -EINVAL; + } + + pdev = platform_device_alloc("wm8350-led", lednum); + if (pdev == NULL) { + dev_err(wm8350->dev, "Failed to allocate LED %d\n", lednum); + return -ENOMEM; + } + + led->isink_consumer.dev_name = dev_name(&pdev->dev); + led->isink_consumer.supply = "led_isink"; + led->isink_init.num_consumer_supplies = 1; + led->isink_init.consumer_supplies = &led->isink_consumer; + led->isink_init.constraints.min_uA = 0; + led->isink_init.constraints.max_uA = pdata->max_uA; + led->isink_init.constraints.valid_ops_mask + = REGULATOR_CHANGE_CURRENT | REGULATOR_CHANGE_STATUS; + led->isink_init.constraints.valid_modes_mask = REGULATOR_MODE_NORMAL; + ret = wm8350_register_regulator(wm8350, isink, &led->isink_init); + if (ret != 0) { + platform_device_put(pdev); + return ret; + } + + led->dcdc_consumer.dev_name = dev_name(&pdev->dev); + led->dcdc_consumer.supply = "led_vcc"; + led->dcdc_init.num_consumer_supplies = 1; + led->dcdc_init.consumer_supplies = &led->dcdc_consumer; + led->dcdc_init.constraints.valid_modes_mask = REGULATOR_MODE_NORMAL; + led->dcdc_init.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; + ret = wm8350_register_regulator(wm8350, dcdc, &led->dcdc_init); + if (ret != 0) { + platform_device_put(pdev); + return ret; + } + + switch (isink) { + case WM8350_ISINK_A: + wm8350->pmic.isink_A_dcdc = dcdc; + break; + case WM8350_ISINK_B: + wm8350->pmic.isink_B_dcdc = dcdc; + break; + } + + pdev->dev.platform_data = pdata; + pdev->dev.parent = wm8350->dev; + ret = platform_device_add(pdev); + if (ret != 0) { + dev_err(wm8350->dev, "Failed to register LED %d: %d\n", + lednum, ret); + platform_device_put(pdev); + return ret; + } + + led->pdev = pdev; + + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_register_led); + +static struct platform_driver wm8350_regulator_driver = { + .probe = wm8350_regulator_probe, + .remove = wm8350_regulator_remove, + .driver = { + .name = "wm8350-regulator", + }, +}; + +static int __init wm8350_regulator_init(void) +{ + return platform_driver_register(&wm8350_regulator_driver); +} +subsys_initcall(wm8350_regulator_init); + +static void __exit wm8350_regulator_exit(void) +{ + platform_driver_unregister(&wm8350_regulator_driver); +} +module_exit(wm8350_regulator_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("WM8350 voltage and current regulator driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8350-regulator"); diff --git a/drivers/regulator/wm8400-regulator.c b/drivers/regulator/wm8400-regulator.c new file mode 100644 index 000000000..fb1837657 --- /dev/null +++ b/drivers/regulator/wm8400-regulator.c @@ -0,0 +1,282 @@ +/* + * Regulator support for WM8400 + * + * Copyright 2008 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the + * License, or (at your option) any later version. + * + */ + +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/wm8400-private.h> + +static const struct regulator_linear_range wm8400_ldo_ranges[] = { + REGULATOR_LINEAR_RANGE(900000, 0, 14, 50000), + REGULATOR_LINEAR_RANGE(1700000, 15, 31, 100000), +}; + +static const struct regulator_ops wm8400_ldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .map_voltage = regulator_map_voltage_linear_range, +}; + +static unsigned int wm8400_dcdc_get_mode(struct regulator_dev *dev) +{ + struct wm8400 *wm8400 = rdev_get_drvdata(dev); + int offset = (rdev_get_id(dev) - WM8400_DCDC1) * 2; + u16 data[2]; + int ret; + + ret = wm8400_block_read(wm8400, WM8400_DCDC1_CONTROL_1 + offset, 2, + data); + if (ret != 0) + return 0; + + /* Datasheet: hibernate */ + if (data[0] & WM8400_DC1_SLEEP) + return REGULATOR_MODE_STANDBY; + + /* Datasheet: standby */ + if (!(data[0] & WM8400_DC1_ACTIVE)) + return REGULATOR_MODE_IDLE; + + /* Datasheet: active with or without force PWM */ + if (data[1] & WM8400_DC1_FRC_PWM) + return REGULATOR_MODE_FAST; + else + return REGULATOR_MODE_NORMAL; +} + +static int wm8400_dcdc_set_mode(struct regulator_dev *dev, unsigned int mode) +{ + struct wm8400 *wm8400 = rdev_get_drvdata(dev); + int offset = (rdev_get_id(dev) - WM8400_DCDC1) * 2; + int ret; + + switch (mode) { + case REGULATOR_MODE_FAST: + /* Datasheet: active with force PWM */ + ret = wm8400_set_bits(wm8400, WM8400_DCDC1_CONTROL_2 + offset, + WM8400_DC1_FRC_PWM, WM8400_DC1_FRC_PWM); + if (ret != 0) + return ret; + + return wm8400_set_bits(wm8400, WM8400_DCDC1_CONTROL_1 + offset, + WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, + WM8400_DC1_ACTIVE); + + case REGULATOR_MODE_NORMAL: + /* Datasheet: active */ + ret = wm8400_set_bits(wm8400, WM8400_DCDC1_CONTROL_2 + offset, + WM8400_DC1_FRC_PWM, 0); + if (ret != 0) + return ret; + + return wm8400_set_bits(wm8400, WM8400_DCDC1_CONTROL_1 + offset, + WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, + WM8400_DC1_ACTIVE); + + case REGULATOR_MODE_IDLE: + /* Datasheet: standby */ + return wm8400_set_bits(wm8400, WM8400_DCDC1_CONTROL_1 + offset, + WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, 0); + default: + return -EINVAL; + } +} + +static unsigned int wm8400_dcdc_get_optimum_mode(struct regulator_dev *dev, + int input_uV, int output_uV, + int load_uA) +{ + return REGULATOR_MODE_NORMAL; +} + +static const struct regulator_ops wm8400_dcdc_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_mode = wm8400_dcdc_get_mode, + .set_mode = wm8400_dcdc_set_mode, + .get_optimum_mode = wm8400_dcdc_get_optimum_mode, +}; + +static struct regulator_desc regulators[] = { + { + .name = "LDO1", + .id = WM8400_LDO1, + .ops = &wm8400_ldo_ops, + .enable_reg = WM8400_LDO1_CONTROL, + .enable_mask = WM8400_LDO1_ENA, + .n_voltages = WM8400_LDO1_VSEL_MASK + 1, + .linear_ranges = wm8400_ldo_ranges, + .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), + .vsel_reg = WM8400_LDO1_CONTROL, + .vsel_mask = WM8400_LDO1_VSEL_MASK, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "LDO2", + .id = WM8400_LDO2, + .ops = &wm8400_ldo_ops, + .enable_reg = WM8400_LDO2_CONTROL, + .enable_mask = WM8400_LDO2_ENA, + .n_voltages = WM8400_LDO2_VSEL_MASK + 1, + .linear_ranges = wm8400_ldo_ranges, + .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), + .type = REGULATOR_VOLTAGE, + .vsel_reg = WM8400_LDO2_CONTROL, + .vsel_mask = WM8400_LDO2_VSEL_MASK, + .owner = THIS_MODULE, + }, + { + .name = "LDO3", + .id = WM8400_LDO3, + .ops = &wm8400_ldo_ops, + .enable_reg = WM8400_LDO3_CONTROL, + .enable_mask = WM8400_LDO3_ENA, + .n_voltages = WM8400_LDO3_VSEL_MASK + 1, + .linear_ranges = wm8400_ldo_ranges, + .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), + .vsel_reg = WM8400_LDO3_CONTROL, + .vsel_mask = WM8400_LDO3_VSEL_MASK, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "LDO4", + .id = WM8400_LDO4, + .ops = &wm8400_ldo_ops, + .enable_reg = WM8400_LDO4_CONTROL, + .enable_mask = WM8400_LDO4_ENA, + .n_voltages = WM8400_LDO4_VSEL_MASK + 1, + .linear_ranges = wm8400_ldo_ranges, + .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), + .vsel_reg = WM8400_LDO4_CONTROL, + .vsel_mask = WM8400_LDO4_VSEL_MASK, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "DCDC1", + .id = WM8400_DCDC1, + .ops = &wm8400_dcdc_ops, + .enable_reg = WM8400_DCDC1_CONTROL_1, + .enable_mask = WM8400_DC1_ENA_MASK, + .n_voltages = WM8400_DC1_VSEL_MASK + 1, + .vsel_reg = WM8400_DCDC1_CONTROL_1, + .vsel_mask = WM8400_DC1_VSEL_MASK, + .min_uV = 850000, + .uV_step = 25000, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + { + .name = "DCDC2", + .id = WM8400_DCDC2, + .ops = &wm8400_dcdc_ops, + .enable_reg = WM8400_DCDC2_CONTROL_1, + .enable_mask = WM8400_DC1_ENA_MASK, + .n_voltages = WM8400_DC2_VSEL_MASK + 1, + .vsel_reg = WM8400_DCDC2_CONTROL_1, + .vsel_mask = WM8400_DC2_VSEL_MASK, + .min_uV = 850000, + .uV_step = 25000, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, +}; + +static int wm8400_regulator_probe(struct platform_device *pdev) +{ + struct wm8400 *wm8400 = container_of(pdev, struct wm8400, regulators[pdev->id]); + struct regulator_config config = { }; + struct regulator_dev *rdev; + + config.dev = &pdev->dev; + config.init_data = dev_get_platdata(&pdev->dev); + config.driver_data = wm8400; + config.regmap = wm8400->regmap; + + rdev = devm_regulator_register(&pdev->dev, ®ulators[pdev->id], + &config); + if (IS_ERR(rdev)) + return PTR_ERR(rdev); + + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static struct platform_driver wm8400_regulator_driver = { + .driver = { + .name = "wm8400-regulator", + }, + .probe = wm8400_regulator_probe, +}; + +/** + * wm8400_register_regulator - enable software control of a WM8400 regulator + * + * This function enables software control of a WM8400 regulator via + * the regulator API. It is intended to be called from the + * platform_init() callback of the WM8400 MFD driver. + * + * @param dev The WM8400 device to operate on. + * @param reg The regulator to control. + * @param initdata Regulator initdata for the regulator. + */ +int wm8400_register_regulator(struct device *dev, int reg, + struct regulator_init_data *initdata) +{ + struct wm8400 *wm8400 = dev_get_drvdata(dev); + + if (wm8400->regulators[reg].name) + return -EBUSY; + + initdata->driver_data = wm8400; + + wm8400->regulators[reg].name = "wm8400-regulator"; + wm8400->regulators[reg].id = reg; + wm8400->regulators[reg].dev.parent = dev; + wm8400->regulators[reg].dev.platform_data = initdata; + + return platform_device_register(&wm8400->regulators[reg]); +} +EXPORT_SYMBOL_GPL(wm8400_register_regulator); + +static int __init wm8400_regulator_init(void) +{ + return platform_driver_register(&wm8400_regulator_driver); +} +subsys_initcall(wm8400_regulator_init); + +static void __exit wm8400_regulator_exit(void) +{ + platform_driver_unregister(&wm8400_regulator_driver); +} +module_exit(wm8400_regulator_exit); + +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("WM8400 regulator driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8400-regulator"); diff --git a/drivers/regulator/wm8994-regulator.c b/drivers/regulator/wm8994-regulator.c new file mode 100644 index 000000000..7a4ce6df4 --- /dev/null +++ b/drivers/regulator/wm8994-regulator.c @@ -0,0 +1,199 @@ +/* + * wm8994-regulator.c -- Regulator driver for the WM8994 + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/gpio.h> +#include <linux/slab.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/wm8994/pdata.h> + +struct wm8994_ldo { + struct regulator_dev *regulator; + struct wm8994 *wm8994; + struct regulator_consumer_supply supply; + struct regulator_init_data init_data; +}; + +#define WM8994_LDO1_MAX_SELECTOR 0x7 +#define WM8994_LDO2_MAX_SELECTOR 0x3 + +static const struct regulator_ops wm8994_ldo1_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static int wm8994_ldo2_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + struct wm8994_ldo *ldo = rdev_get_drvdata(rdev); + + if (selector > WM8994_LDO2_MAX_SELECTOR) + return -EINVAL; + + switch (ldo->wm8994->type) { + case WM8994: + return (selector * 100000) + 900000; + case WM8958: + return (selector * 100000) + 1000000; + case WM1811: + switch (selector) { + case 0: + return -EINVAL; + default: + return (selector * 100000) + 950000; + } + break; + default: + return -EINVAL; + } +} + +static const struct regulator_ops wm8994_ldo2_ops = { + .list_voltage = wm8994_ldo2_list_voltage, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_desc wm8994_ldo_desc[] = { + { + .name = "LDO1", + .id = 1, + .type = REGULATOR_VOLTAGE, + .n_voltages = WM8994_LDO1_MAX_SELECTOR + 1, + .vsel_reg = WM8994_LDO_1, + .vsel_mask = WM8994_LDO1_VSEL_MASK, + .ops = &wm8994_ldo1_ops, + .min_uV = 2400000, + .uV_step = 100000, + .enable_time = 3000, + .owner = THIS_MODULE, + }, + { + .name = "LDO2", + .id = 2, + .type = REGULATOR_VOLTAGE, + .n_voltages = WM8994_LDO2_MAX_SELECTOR + 1, + .vsel_reg = WM8994_LDO_2, + .vsel_mask = WM8994_LDO2_VSEL_MASK, + .ops = &wm8994_ldo2_ops, + .enable_time = 3000, + .owner = THIS_MODULE, + }, +}; + +static const struct regulator_consumer_supply wm8994_ldo_consumer[] = { + { .supply = "AVDD1" }, + { .supply = "DCVDD" }, +}; + +static const struct regulator_init_data wm8994_ldo_default[] = { + { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + }, + { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + }, +}; + +static int wm8994_ldo_probe(struct platform_device *pdev) +{ + struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent); + struct wm8994_pdata *pdata = dev_get_platdata(wm8994->dev); + int id = pdev->id % ARRAY_SIZE(pdata->ldo); + struct regulator_config config = { }; + struct wm8994_ldo *ldo; + int ret; + + dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); + + ldo = devm_kzalloc(&pdev->dev, sizeof(struct wm8994_ldo), GFP_KERNEL); + if (!ldo) + return -ENOMEM; + + ldo->wm8994 = wm8994; + ldo->supply = wm8994_ldo_consumer[id]; + ldo->supply.dev_name = dev_name(wm8994->dev); + + config.dev = wm8994->dev; + config.driver_data = ldo; + config.regmap = wm8994->regmap; + config.init_data = &ldo->init_data; + if (pdata) { + config.ena_gpio = pdata->ldo[id].enable; + } else if (wm8994->dev->of_node) { + config.ena_gpio = wm8994->pdata.ldo[id].enable; + config.ena_gpio_initialized = true; + } + + /* Use default constraints if none set up */ + if (!pdata || !pdata->ldo[id].init_data || wm8994->dev->of_node) { + dev_dbg(wm8994->dev, "Using default init data, supply %s %s\n", + ldo->supply.dev_name, ldo->supply.supply); + + ldo->init_data = wm8994_ldo_default[id]; + ldo->init_data.consumer_supplies = &ldo->supply; + if (!config.ena_gpio) + ldo->init_data.constraints.valid_ops_mask = 0; + } else { + ldo->init_data = *pdata->ldo[id].init_data; + } + + ldo->regulator = devm_regulator_register(&pdev->dev, + &wm8994_ldo_desc[id], + &config); + if (IS_ERR(ldo->regulator)) { + ret = PTR_ERR(ldo->regulator); + dev_err(wm8994->dev, "Failed to register LDO%d: %d\n", + id + 1, ret); + goto err; + } + + platform_set_drvdata(pdev, ldo); + + return 0; + +err: + return ret; +} + +static struct platform_driver wm8994_ldo_driver = { + .probe = wm8994_ldo_probe, + .driver = { + .name = "wm8994-ldo", + }, +}; + +module_platform_driver(wm8994_ldo_driver); + +/* Module information */ +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("WM8994 LDO driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8994-ldo"); |