mirror of
https://github.com/torvalds/linux.git
synced 2024-12-04 01:51:34 +00:00
1ace99d7c7
The data-sheet for TPS6287x-Q1
https://www.ti.com/lit/ds/symlink/tps62873-q1.pdf
states at chapter 9.3.6.1 Output Voltage Range:
"Note that every change to the VRANGE[1:0] bits must be followed by a
write to the VSET register, even if the value of the VSET[7:0] bits does
not change."
The current implementation of the driver uses the
regulator_set_voltage_sel_pickable_regmap() helper which further uses
regmap_update_bits() to write the VSET-register. The
regmap_update_bits() will not access the hardware if the new register
value is same as old. It is worth noting that this is true also when the
register is marked volatile, which I can't say is wrong because
'read-mnodify-write'-cycle with a volatile register is in any case
something user should carefully consider.
The 'range_applied_by_vsel'-flag in regulator desc was added to force
the vsel register upodates by using regmap_write_bits(). This variant
will always unconditionally write the bits to the hardware.
It is worth noting that the vsel is now forced to be written to the
hardware, whether the range was changed or not. This may cause a
performance drop if users are wrtiting same voltage value repeteadly.
It would be possible to read the range register to determine if it was
changed, but this would be a performance issue for users who don't use
reg cache for vsel.
Always write the VSET register to the hardware regardless the cache.
Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
Fixes: 7b0518fbf2
("regulator: Add support for TI TPS6287x regulators")
Link: https://msgid.link/r/ZktD50C5twF1EuKu@fedora
Signed-off-by: Mark Brown <broonie@kernel.org>
191 lines
4.8 KiB
C
191 lines
4.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2023 Axis Communications AB
|
|
*
|
|
* Driver for Texas Instruments TPS6287x PMIC.
|
|
* Datasheet: https://www.ti.com/lit/ds/symlink/tps62873.pdf
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/module.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/regulator/machine.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/linear_range.h>
|
|
|
|
#define TPS6287X_VSET 0x00
|
|
#define TPS6287X_CTRL1 0x01
|
|
#define TPS6287X_CTRL1_VRAMP GENMASK(1, 0)
|
|
#define TPS6287X_CTRL1_FPWMEN BIT(4)
|
|
#define TPS6287X_CTRL1_SWEN BIT(5)
|
|
#define TPS6287X_CTRL2 0x02
|
|
#define TPS6287X_CTRL2_VRANGE GENMASK(3, 2)
|
|
#define TPS6287X_CTRL3 0x03
|
|
#define TPS6287X_STATUS 0x04
|
|
|
|
static const struct regmap_config tps6287x_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.max_register = TPS6287X_STATUS,
|
|
};
|
|
|
|
static const struct linear_range tps6287x_voltage_ranges[] = {
|
|
LINEAR_RANGE(400000, 0, 0xFF, 1250),
|
|
LINEAR_RANGE(400000, 0, 0xFF, 2500),
|
|
LINEAR_RANGE(400000, 0, 0xFF, 5000),
|
|
LINEAR_RANGE(800000, 0, 0xFF, 10000),
|
|
};
|
|
|
|
static const unsigned int tps6287x_voltage_range_sel[] = {
|
|
0x0, 0x1, 0x2, 0x3
|
|
};
|
|
|
|
static const unsigned int tps6287x_ramp_table[] = {
|
|
10000, 5000, 1250, 500
|
|
};
|
|
|
|
static int tps6287x_set_mode(struct regulator_dev *rdev, unsigned int mode)
|
|
{
|
|
unsigned int val;
|
|
|
|
switch (mode) {
|
|
case REGULATOR_MODE_NORMAL:
|
|
val = 0;
|
|
break;
|
|
case REGULATOR_MODE_FAST:
|
|
val = TPS6287X_CTRL1_FPWMEN;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return regmap_update_bits(rdev->regmap, TPS6287X_CTRL1,
|
|
TPS6287X_CTRL1_FPWMEN, val);
|
|
}
|
|
|
|
static unsigned int tps6287x_get_mode(struct regulator_dev *rdev)
|
|
{
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
ret = regmap_read(rdev->regmap, TPS6287X_CTRL1, &val);
|
|
if (ret < 0)
|
|
return 0;
|
|
|
|
return (val & TPS6287X_CTRL1_FPWMEN) ? REGULATOR_MODE_FAST :
|
|
REGULATOR_MODE_NORMAL;
|
|
}
|
|
|
|
static unsigned int tps6287x_of_map_mode(unsigned int mode)
|
|
{
|
|
switch (mode) {
|
|
case REGULATOR_MODE_NORMAL:
|
|
case REGULATOR_MODE_FAST:
|
|
return mode;
|
|
default:
|
|
return REGULATOR_MODE_INVALID;
|
|
}
|
|
}
|
|
|
|
static const struct regulator_ops tps6287x_regulator_ops = {
|
|
.enable = regulator_enable_regmap,
|
|
.disable = regulator_disable_regmap,
|
|
.set_mode = tps6287x_set_mode,
|
|
.get_mode = tps6287x_get_mode,
|
|
.is_enabled = regulator_is_enabled_regmap,
|
|
.get_voltage_sel = regulator_get_voltage_sel_pickable_regmap,
|
|
.set_voltage_sel = regulator_set_voltage_sel_pickable_regmap,
|
|
.list_voltage = regulator_list_voltage_pickable_linear_range,
|
|
.set_ramp_delay = regulator_set_ramp_delay_regmap,
|
|
};
|
|
|
|
static struct regulator_desc tps6287x_reg = {
|
|
.name = "tps6287x",
|
|
.owner = THIS_MODULE,
|
|
.ops = &tps6287x_regulator_ops,
|
|
.of_map_mode = tps6287x_of_map_mode,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.enable_reg = TPS6287X_CTRL1,
|
|
.enable_mask = TPS6287X_CTRL1_SWEN,
|
|
.vsel_reg = TPS6287X_VSET,
|
|
.vsel_mask = 0xFF,
|
|
.vsel_range_reg = TPS6287X_CTRL2,
|
|
.vsel_range_mask = TPS6287X_CTRL2_VRANGE,
|
|
.range_applied_by_vsel = true,
|
|
.ramp_reg = TPS6287X_CTRL1,
|
|
.ramp_mask = TPS6287X_CTRL1_VRAMP,
|
|
.ramp_delay_table = tps6287x_ramp_table,
|
|
.n_ramp_values = ARRAY_SIZE(tps6287x_ramp_table),
|
|
.n_voltages = 256 * ARRAY_SIZE(tps6287x_voltage_ranges),
|
|
.linear_ranges = tps6287x_voltage_ranges,
|
|
.n_linear_ranges = ARRAY_SIZE(tps6287x_voltage_ranges),
|
|
.linear_range_selectors_bitfield = tps6287x_voltage_range_sel,
|
|
};
|
|
|
|
static int tps6287x_i2c_probe(struct i2c_client *i2c)
|
|
{
|
|
struct device *dev = &i2c->dev;
|
|
struct regulator_config config = {};
|
|
struct regulator_dev *rdev;
|
|
|
|
config.regmap = devm_regmap_init_i2c(i2c, &tps6287x_regmap_config);
|
|
if (IS_ERR(config.regmap)) {
|
|
dev_err(dev, "Failed to init i2c\n");
|
|
return PTR_ERR(config.regmap);
|
|
}
|
|
|
|
config.dev = dev;
|
|
config.of_node = dev->of_node;
|
|
config.init_data = of_get_regulator_init_data(dev, dev->of_node,
|
|
&tps6287x_reg);
|
|
|
|
rdev = devm_regulator_register(dev, &tps6287x_reg, &config);
|
|
if (IS_ERR(rdev)) {
|
|
dev_err(dev, "Failed to register regulator\n");
|
|
return PTR_ERR(rdev);
|
|
}
|
|
|
|
dev_dbg(dev, "Probed regulator\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id tps6287x_dt_ids[] = {
|
|
{ .compatible = "ti,tps62870", },
|
|
{ .compatible = "ti,tps62871", },
|
|
{ .compatible = "ti,tps62872", },
|
|
{ .compatible = "ti,tps62873", },
|
|
{ }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, tps6287x_dt_ids);
|
|
|
|
static const struct i2c_device_id tps6287x_i2c_id[] = {
|
|
{ "tps62870", 0 },
|
|
{ "tps62871", 0 },
|
|
{ "tps62872", 0 },
|
|
{ "tps62873", 0 },
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, tps6287x_i2c_id);
|
|
|
|
static struct i2c_driver tps6287x_regulator_driver = {
|
|
.driver = {
|
|
.name = "tps6287x",
|
|
.of_match_table = tps6287x_dt_ids,
|
|
},
|
|
.probe = tps6287x_i2c_probe,
|
|
.id_table = tps6287x_i2c_id,
|
|
};
|
|
|
|
module_i2c_driver(tps6287x_regulator_driver);
|
|
|
|
MODULE_AUTHOR("Mårten Lindahl <marten.lindahl@axis.com>");
|
|
MODULE_DESCRIPTION("Regulator driver for TI TPS6287X PMIC");
|
|
MODULE_LICENSE("GPL");
|