diff options
Diffstat (limited to 'drivers/regulator/helpers.c')
| -rw-r--r-- | drivers/regulator/helpers.c | 186 |
1 files changed, 154 insertions, 32 deletions
diff --git a/drivers/regulator/helpers.c b/drivers/regulator/helpers.c index e970e9d2f8be..0def82eb8b46 100644 --- a/drivers/regulator/helpers.c +++ b/drivers/regulator/helpers.c @@ -5,13 +5,14 @@ // Copyright 2007, 2008 Wolfson Microelectronics PLC. // Copyright 2008 SlimLogic Ltd. -#include <linux/kernel.h> -#include <linux/err.h> +#include <linux/bitops.h> #include <linux/delay.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/kernel.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/regulator/driver.h> -#include <linux/module.h> #include "internal.h" @@ -104,13 +105,14 @@ static int regulator_range_selector_to_index(struct regulator_dev *rdev, { int i; - if (!rdev->desc->linear_range_selectors) + if (!rdev->desc->linear_range_selectors_bitfield) return -EINVAL; rval &= rdev->desc->vsel_range_mask; + rval >>= ffs(rdev->desc->vsel_range_mask) - 1; for (i = 0; i < rdev->desc->n_linear_ranges; i++) { - if (rdev->desc->linear_range_selectors[i] == rval) + if (rdev->desc->linear_range_selectors_bitfield[i] == rval) return i; } return -EINVAL; @@ -123,7 +125,7 @@ static int regulator_range_selector_to_index(struct regulator_dev *rdev, * * Regulators that use regmap for their register I/O and use pickable * ranges can set the vsel_reg, vsel_mask, vsel_range_reg and vsel_range_mask - * fields in their descriptor and then use this as their get_voltage_vsel + * fields in their descriptor and then use this as their get_voltage_sel * operation, saving some code. */ int regulator_get_voltage_sel_pickable_regmap(struct regulator_dev *rdev) @@ -159,6 +161,32 @@ int regulator_get_voltage_sel_pickable_regmap(struct regulator_dev *rdev) } EXPORT_SYMBOL_GPL(regulator_get_voltage_sel_pickable_regmap); +static int write_separate_vsel_and_range(struct regulator_dev *rdev, + unsigned int sel, unsigned int range) +{ + bool range_updated; + int ret; + + ret = regmap_update_bits_base(rdev->regmap, rdev->desc->vsel_range_reg, + rdev->desc->vsel_range_mask, + range, &range_updated, false, false); + if (ret) + return ret; + + /* + * Some PMICs treat the vsel_reg same as apply-bit. Force it to be + * written if the range changed, even if the old selector was same as + * the new one + */ + if (rdev->desc->range_applied_by_vsel && range_updated) + return regmap_write_bits(rdev->regmap, + rdev->desc->vsel_reg, + rdev->desc->vsel_mask, sel); + + return regmap_update_bits(rdev->regmap, rdev->desc->vsel_reg, + rdev->desc->vsel_mask, sel); +} + /** * regulator_set_voltage_sel_pickable_regmap - pickable range set_voltage_sel * @@ -167,7 +195,7 @@ EXPORT_SYMBOL_GPL(regulator_get_voltage_sel_pickable_regmap); * * Regulators that use regmap for their register I/O and use pickable * ranges can set the vsel_reg, vsel_mask, vsel_range_reg and vsel_range_mask - * fields in their descriptor and then use this as their set_voltage_vsel + * fields in their descriptor and then use this as their set_voltage_sel * operation, saving some code. */ int regulator_set_voltage_sel_pickable_regmap(struct regulator_dev *rdev, @@ -194,23 +222,15 @@ int regulator_set_voltage_sel_pickable_regmap(struct regulator_dev *rdev, sel <<= ffs(rdev->desc->vsel_mask) - 1; sel += rdev->desc->linear_ranges[i].min_sel; - range = rdev->desc->linear_range_selectors[i]; + range = rdev->desc->linear_range_selectors_bitfield[i]; + range <<= ffs(rdev->desc->vsel_range_mask) - 1; - if (rdev->desc->vsel_reg == rdev->desc->vsel_range_reg) { - ret = regmap_update_bits(rdev->regmap, - rdev->desc->vsel_reg, + if (rdev->desc->vsel_reg == rdev->desc->vsel_range_reg) + ret = regmap_update_bits(rdev->regmap, rdev->desc->vsel_reg, rdev->desc->vsel_range_mask | rdev->desc->vsel_mask, sel | range); - } else { - ret = regmap_update_bits(rdev->regmap, - rdev->desc->vsel_range_reg, - rdev->desc->vsel_range_mask, range); - if (ret) - return ret; - - ret = regmap_update_bits(rdev->regmap, rdev->desc->vsel_reg, - rdev->desc->vsel_mask, sel); - } + else + ret = write_separate_vsel_and_range(rdev, sel, range); if (ret) return ret; @@ -230,7 +250,7 @@ EXPORT_SYMBOL_GPL(regulator_set_voltage_sel_pickable_regmap); * * 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. + * as their get_voltage_sel operation, saving some code. */ int regulator_get_voltage_sel_regmap(struct regulator_dev *rdev) { @@ -256,7 +276,7 @@ EXPORT_SYMBOL_GPL(regulator_get_voltage_sel_regmap); * * 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. + * as their set_voltage_sel operation, saving some code. */ int regulator_set_voltage_sel_regmap(struct regulator_dev *rdev, unsigned sel) { @@ -486,7 +506,7 @@ int regulator_map_voltage_pickable_linear_range(struct regulator_dev *rdev, continue; } - ret = selector + sel; + ret = selector + sel - range->min_sel; voltage = rdev->desc->ops->list_voltage(rdev, ret); @@ -509,6 +529,33 @@ int regulator_map_voltage_pickable_linear_range(struct regulator_dev *rdev, EXPORT_SYMBOL_GPL(regulator_map_voltage_pickable_linear_range); /** + * regulator_desc_list_voltage_linear - List voltages with simple calculation + * + * @desc: Regulator desc for regulator which volatges are to be listed + * @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 prior regulator registration to list + * the voltages. This is useful when voltages need to be listed during + * device-tree parsing. + */ +int regulator_desc_list_voltage_linear(const struct regulator_desc *desc, + unsigned int selector) +{ + if (selector >= desc->n_voltages) + return -EINVAL; + + if (selector < desc->linear_min_sel) + return 0; + + selector -= desc->linear_min_sel; + + return desc->min_uV + (desc->uV_step * selector); +} +EXPORT_SYMBOL_GPL(regulator_desc_list_voltage_linear); + +/** * regulator_list_voltage_linear - List voltages with simple calculation * * @rdev: Regulator device @@ -521,14 +568,7 @@ EXPORT_SYMBOL_GPL(regulator_map_voltage_pickable_linear_range); 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); + return regulator_desc_list_voltage_linear(rdev->desc, selector); } EXPORT_SYMBOL_GPL(regulator_list_voltage_linear); @@ -649,6 +689,8 @@ int regulator_list_voltage_table(struct regulator_dev *rdev, if (selector >= rdev->desc->n_voltages) return -EINVAL; + if (selector < rdev->desc->linear_min_sel) + return 0; return rdev->desc->volt_table[selector]; } @@ -879,3 +921,83 @@ bool regulator_is_equal(struct regulator *reg1, struct regulator *reg2) return reg1->rdev == reg2->rdev; } EXPORT_SYMBOL_GPL(regulator_is_equal); + +/** + * regulator_find_closest_bigger - helper to find offset in ramp delay table + * + * @target: targeted ramp_delay + * @table: table with supported ramp delays + * @num_sel: number of entries in the table + * @sel: Pointer to store table offset + * + * This is the internal helper used by regulator_set_ramp_delay_regmap to + * map ramp delay to register value. It should only be used directly if + * regulator_set_ramp_delay_regmap cannot handle a specific device setup + * (e.g. because the value is split over multiple registers). + */ +int regulator_find_closest_bigger(unsigned int target, const unsigned int *table, + unsigned int num_sel, unsigned int *sel) +{ + unsigned int s, tmp, max, maxsel = 0; + bool found = false; + + max = table[0]; + + for (s = 0; s < num_sel; s++) { + if (table[s] > max) { + max = table[s]; + maxsel = s; + } + if (table[s] >= target) { + if (!found || table[s] - target < tmp - target) { + tmp = table[s]; + *sel = s; + found = true; + if (tmp == target) + break; + } + } + } + + if (!found) { + *sel = maxsel; + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(regulator_find_closest_bigger); + +/** + * regulator_set_ramp_delay_regmap - set_ramp_delay() helper + * + * @rdev: regulator to operate on + * @ramp_delay: ramp-rate value given in units V/S (uV/uS) + * + * Regulators that use regmap for their register I/O can set the ramp_reg + * and ramp_mask fields in their descriptor and then use this as their + * set_ramp_delay operation, saving some code. + */ +int regulator_set_ramp_delay_regmap(struct regulator_dev *rdev, int ramp_delay) +{ + int ret; + unsigned int sel; + + if (WARN_ON(!rdev->desc->n_ramp_values || !rdev->desc->ramp_delay_table)) + return -EINVAL; + + ret = regulator_find_closest_bigger(ramp_delay, rdev->desc->ramp_delay_table, + rdev->desc->n_ramp_values, &sel); + + if (ret) { + dev_warn(rdev_get_dev(rdev), + "Can't set ramp-delay %u, setting %u\n", ramp_delay, + rdev->desc->ramp_delay_table[sel]); + } + + sel <<= ffs(rdev->desc->ramp_mask) - 1; + + return regmap_update_bits(rdev->regmap, rdev->desc->ramp_reg, + rdev->desc->ramp_mask, sel); +} +EXPORT_SYMBOL_GPL(regulator_set_ramp_delay_regmap); |
