* [PATCH v3 0/3] Add TI TPS1689 pmbus eFuse
@ 2026-02-17 8:12 Stoyan Bogdanov
2026-02-17 8:12 ` [PATCH v3 1/3] hwmon: (pmbus/tps25990): Rework TPS25990 non standatd direct conversion Stoyan Bogdanov
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Stoyan Bogdanov @ 2026-02-17 8:12 UTC (permalink / raw)
To: jbrunet, linux, robh, krzk+dt, conor+dt, corbet, skhan
Cc: linux-hwmon, devicetree, linux-doc, linux-kernel, Stoyan Bogdanov
Rework TPS25990 eFuse pmbus driver to provide more unified way for
non standard direct conversions.Remove existing defines for TPS25990
conversions and replace them with enum listing all supported parameters
and structure using m, b, R. Add functions to covert raw to real value
and real value back to raw. Add data structure to hold pmbus_driver_info
and local_direct_values for direct conversion.
Add support TPS1689 support to TPS25990 driver, since both
chips are sharing a lot of similar internal functions with exception of
work range like Voltage and Current.
Change log v2 -> v3:
- Fix error detected from kernel test bot regarding division
Tests:
- Test builds for x86_64, arm64, i386
- Retest driver on arm64
- Validate driver direct conversion functions manualy
Stoyan Bogdanov (3):
hwmon: (pmbus/tps25990): Rework TPS25990 non standatd direct
conversion
dt-bindings: hwmon: pmbus/tps1689: Add TPS1689
hwmon: (pmbus/tps1689): Add TPS1689 support
.../bindings/hwmon/pmbus/ti,tps25990.yaml | 4 +-
Documentation/hwmon/tps25990.rst | 15 +-
drivers/hwmon/pmbus/tps25990.c | 190 +++++++++++++++---
3 files changed, 175 insertions(+), 34 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v3 1/3] hwmon: (pmbus/tps25990): Rework TPS25990 non standatd direct conversion 2026-02-17 8:12 [PATCH v3 0/3] Add TI TPS1689 pmbus eFuse Stoyan Bogdanov @ 2026-02-17 8:12 ` Stoyan Bogdanov 2026-03-08 17:27 ` Guenter Roeck 2026-02-17 8:12 ` [PATCH v3 2/3] dt-bindings: hwmon: pmbus/tps1689: Add TPS1689 Stoyan Bogdanov 2026-02-17 8:12 ` [PATCH v3 3/3] hwmon: (pmbus/tps1689): Add TPS1689 support Stoyan Bogdanov 2 siblings, 1 reply; 10+ messages in thread From: Stoyan Bogdanov @ 2026-02-17 8:12 UTC (permalink / raw) To: jbrunet, linux, robh, krzk+dt, conor+dt, corbet, skhan Cc: linux-hwmon, devicetree, linux-doc, linux-kernel, Stoyan Bogdanov Rework existing implementation for calculation of direct format conversion for TPS25990. With this implamentation is leveraged code reusability for non standard parameters. - Add enum for parameter - Add m, b, R structure to hold value per device - Add data structure to hold for pmbus_driver_info and local_direct_values - Conversion functions are implemented according to formula from TPS25990 datasheet - Remove previously used defines replace with structure Signed-off-by: Stoyan Bogdanov <sbogdanov@baylibre.com> --- drivers/hwmon/pmbus/tps25990.c | 115 +++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 27 deletions(-) diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c index c13edd7e1abf..33f6367f797c 100644 --- a/drivers/hwmon/pmbus/tps25990.c +++ b/drivers/hwmon/pmbus/tps25990.c @@ -36,17 +36,58 @@ #define TPS25990_UNLOCKED BIT(7) #define TPS25990_8B_SHIFT 2 -#define TPS25990_VIN_OVF_NUM 525100 -#define TPS25990_VIN_OVF_DIV 10163 -#define TPS25990_VIN_OVF_OFF 155 -#define TPS25990_IIN_OCF_NUM 953800 -#define TPS25990_IIN_OCF_DIV 129278 -#define TPS25990_IIN_OCF_OFF 157 #define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \ PK_MIN_AVG_RST_AVG | \ PK_MIN_AVG_RST_MIN) +enum tps25990_parameters { + TPS25990_VIN_OVF = 0, /* VIN over volatage fault */ + TPS25990_IIN_OCF, /* IIN Over currect fault */ + TPS25590_DIRECT_VALUES_MAX, /* Max value ensure there enough space */ +}; + +struct local_direct_value { + int m[TPS25590_DIRECT_VALUES_MAX]; /* mantissa for direct data format */ + int b[TPS25590_DIRECT_VALUES_MAX]; /* offset */ + int R[TPS25590_DIRECT_VALUES_MAX]; /* exponent */ +}; + +struct tps25990_data { + struct pmbus_driver_info *info; + struct local_direct_value *info_local; +}; + +static int tps25990_raw_to_value(struct i2c_client *client, int param, int raw) +{ + struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); + struct local_direct_value *info_local = data->info_local; + + /* Formula : X = (Y / 10^R - b) / m */ + if (info_local->R[param] >= 0) + raw /= int_pow(10, info_local->R[param]); + else + raw *= int_pow(10, -info_local->R[param]); + + return DIV_ROUND_CLOSEST(raw - info_local->b[param], info_local->m[param]); +} + +static unsigned int tps25990_value_to_raw(struct i2c_client *client, int param, int val) +{ + struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); + struct local_direct_value *info_local = data->info_local; + + /* Formula : Y = ( m * X + b) * 10^R */ + val = (long)val * info_local->m[param] + info_local->b[param]; + + if (info_local->R[param] >= 0) + val *= int_pow(10, info_local->R[param]); + else + val = DIV_ROUND_CLOSEST(val, int_pow(10, -info_local->R[param])); + + return val; +} + /* * Arbitrary default Rimon value: 1kOhm * This correspond to an overcurrent limit of 55A, close to the specified limit @@ -184,9 +225,7 @@ static int tps25990_read_word_data(struct i2c_client *client, ret = pmbus_read_word_data(client, page, phase, reg); if (ret < 0) break; - ret = DIV_ROUND_CLOSEST(ret * TPS25990_VIN_OVF_NUM, - TPS25990_VIN_OVF_DIV); - ret += TPS25990_VIN_OVF_OFF; + ret = tps25990_raw_to_value(client, TPS25990_VIN_OVF, ret); break; case PMBUS_IIN_OC_FAULT_LIMIT: @@ -198,9 +237,7 @@ static int tps25990_read_word_data(struct i2c_client *client, ret = pmbus_read_byte_data(client, page, TPS25990_VIREF); if (ret < 0) break; - ret = DIV_ROUND_CLOSEST(ret * TPS25990_IIN_OCF_NUM, - TPS25990_IIN_OCF_DIV); - ret += TPS25990_IIN_OCF_OFF; + ret = tps25990_raw_to_value(client, TPS25990_IIN_OCF, ret); break; case PMBUS_VIRT_SAMPLES: @@ -246,17 +283,13 @@ static int tps25990_write_word_data(struct i2c_client *client, break; case PMBUS_VIN_OV_FAULT_LIMIT: - value -= TPS25990_VIN_OVF_OFF; - value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_VIN_OVF_DIV, - TPS25990_VIN_OVF_NUM); + value = tps25990_value_to_raw(client, TPS25990_VIN_OVF, value); value = clamp_val(value, 0, 0xf); ret = pmbus_write_word_data(client, page, reg, value); break; case PMBUS_IIN_OC_FAULT_LIMIT: - value -= TPS25990_IIN_OCF_OFF; - value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_IIN_OCF_DIV, - TPS25990_IIN_OCF_NUM); + value = tps25990_value_to_raw(client, TPS25990_IIN_OCF, value); value = clamp_val(value, 0, 0x3f); ret = pmbus_write_byte_data(client, page, TPS25990_VIREF, value); break; @@ -337,7 +370,16 @@ static const struct regulator_desc tps25990_reg_desc[] = { }; #endif -static const struct pmbus_driver_info tps25990_base_info = { +struct local_direct_value tps25590_local_info = { + .m[TPS25990_VIN_OVF] = 10163, + .b[TPS25990_VIN_OVF] = -30081, + .R[TPS25990_VIN_OVF] = -4, + .m[TPS25990_IIN_OCF] = 9538, + .b[TPS25990_IIN_OCF] = 0, + .R[TPS25990_IIN_OCF] = -6, +}; + +static struct pmbus_driver_info tps25990_base_info = { .pages = 1, .format[PSC_VOLTAGE_IN] = direct, .m[PSC_VOLTAGE_IN] = 5251, @@ -386,14 +428,19 @@ static const struct pmbus_driver_info tps25990_base_info = { #endif }; +struct tps25990_data data_tps25990 = { + .info = &tps25990_base_info, + .info_local = &tps25590_local_info, +}; + static const struct i2c_device_id tps25990_i2c_id[] = { - { "tps25990" }, + { .name = "tps25990", .driver_data = (kernel_ulong_t)&data_tps25990 }, {} }; MODULE_DEVICE_TABLE(i2c, tps25990_i2c_id); static const struct of_device_id tps25990_of_match[] = { - { .compatible = "ti,tps25990" }, + { .compatible = "ti,tps25990", .data = &data_tps25990 }, {} }; MODULE_DEVICE_TABLE(of, tps25990_of_match); @@ -401,23 +448,37 @@ MODULE_DEVICE_TABLE(of, tps25990_of_match); static int tps25990_probe(struct i2c_client *client) { struct device *dev = &client->dev; - struct pmbus_driver_info *info; + struct tps25990_data *data; u32 rimon = TPS25990_DEFAULT_RIMON; + struct pmbus_driver_info *info_get; + struct local_direct_value *info_local_get; int ret; ret = device_property_read_u32(dev, "ti,rimon-micro-ohms", &rimon); if (ret < 0 && ret != -EINVAL) return dev_err_probe(dev, ret, "failed to get rimon\n"); - info = devm_kmemdup(dev, &tps25990_base_info, sizeof(*info), GFP_KERNEL); - if (!info) + data = (struct tps25990_data *)of_device_get_match_data(dev); + if (!data) + return -EOPNOTSUPP; + + info_get = data->info; + /* Make copy of pmbus_info and replace it to preserve original values */ + data->info = devm_kmemdup(dev, info_get, sizeof(*info_get), GFP_KERNEL); + if (!data->info) + return -ENOMEM; + + info_local_get = data->info_local; + /* Make copy of pmbus_info and replace it to preserve original values */ + data->info_local = devm_kmemdup(dev, info_local_get, sizeof(*info_local_get), GFP_KERNEL); + if (!data->info_local) return -ENOMEM; /* Adapt the current and power scale for each instance */ - tps25990_set_m(&info->m[PSC_CURRENT_IN], rimon); - tps25990_set_m(&info->m[PSC_POWER], rimon); + tps25990_set_m(&data->info->m[PSC_CURRENT_IN], rimon); + tps25990_set_m(&data->info->m[PSC_POWER], rimon); - return pmbus_do_probe(client, info); + return pmbus_do_probe(client, data->info); } static struct i2c_driver tps25990_driver = { -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3 1/3] hwmon: (pmbus/tps25990): Rework TPS25990 non standatd direct conversion 2026-02-17 8:12 ` [PATCH v3 1/3] hwmon: (pmbus/tps25990): Rework TPS25990 non standatd direct conversion Stoyan Bogdanov @ 2026-03-08 17:27 ` Guenter Roeck 2026-03-24 1:17 ` Stoyan Bogdanov 0 siblings, 1 reply; 10+ messages in thread From: Guenter Roeck @ 2026-03-08 17:27 UTC (permalink / raw) To: Stoyan Bogdanov Cc: jbrunet, robh, krzk+dt, conor+dt, corbet, skhan, linux-hwmon, devicetree, linux-doc, linux-kernel On Tue, Feb 17, 2026 at 10:12:01AM +0200, Stoyan Bogdanov wrote: > Rework existing implementation for calculation of direct > format conversion for TPS25990. With this implamentation > is leveraged code reusability for non standard parameters. > - Add enum for parameter > - Add m, b, R structure to hold value per device > - Add data structure to hold for pmbus_driver_info and > local_direct_values > - Conversion functions are implemented according to formula from > TPS25990 datasheet > - Remove previously used defines replace with structure > > Signed-off-by: Stoyan Bogdanov <sbogdanov@baylibre.com> > --- > drivers/hwmon/pmbus/tps25990.c | 115 +++++++++++++++++++++++++-------- > 1 file changed, 88 insertions(+), 27 deletions(-) > > diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c > index c13edd7e1abf..33f6367f797c 100644 > --- a/drivers/hwmon/pmbus/tps25990.c > +++ b/drivers/hwmon/pmbus/tps25990.c > @@ -36,17 +36,58 @@ > #define TPS25990_UNLOCKED BIT(7) > > #define TPS25990_8B_SHIFT 2 > -#define TPS25990_VIN_OVF_NUM 525100 > -#define TPS25990_VIN_OVF_DIV 10163 > -#define TPS25990_VIN_OVF_OFF 155 > -#define TPS25990_IIN_OCF_NUM 953800 > -#define TPS25990_IIN_OCF_DIV 129278 > -#define TPS25990_IIN_OCF_OFF 157 > > #define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \ > PK_MIN_AVG_RST_AVG | \ > PK_MIN_AVG_RST_MIN) > > +enum tps25990_parameters { > + TPS25990_VIN_OVF = 0, /* VIN over volatage fault */ > + TPS25990_IIN_OCF, /* IIN Over currect fault */ > + TPS25590_DIRECT_VALUES_MAX, /* Max value ensure there enough space */ > +}; > + > +struct local_direct_value { > + int m[TPS25590_DIRECT_VALUES_MAX]; /* mantissa for direct data format */ > + int b[TPS25590_DIRECT_VALUES_MAX]; /* offset */ > + int R[TPS25590_DIRECT_VALUES_MAX]; /* exponent */ > +}; > + > +struct tps25990_data { > + struct pmbus_driver_info *info; > + struct local_direct_value *info_local; > +}; > + > +static int tps25990_raw_to_value(struct i2c_client *client, int param, int raw) > +{ > + struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); This returns the global data structure (which is corrupted on multi-instance systems) instead of per-instance data. > + struct local_direct_value *info_local = data->info_local; > + > + /* Formula : X = (Y / 10^R - b) / m */ > + if (info_local->R[param] >= 0) > + raw /= int_pow(10, info_local->R[param]); > + else > + raw *= int_pow(10, -info_local->R[param]); This will overflow if the exponent is -6 and raw is larger than ~2147. This may not happen with current parameters, but it is brittle. > + > + return DIV_ROUND_CLOSEST(raw - info_local->b[param], info_local->m[param]); > +} > + > +static unsigned int tps25990_value_to_raw(struct i2c_client *client, int param, int val) > +{ > + struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); > + struct local_direct_value *info_local = data->info_local; > + > + /* Formula : Y = ( m * X + b) * 10^R */ > + val = (long)val * info_local->m[param] + info_local->b[param]; This will overflow on 32-bit systems (where long is a 32-bit value). > + > + if (info_local->R[param] >= 0) > + val *= int_pow(10, info_local->R[param]); > + else > + val = DIV_ROUND_CLOSEST(val, int_pow(10, -info_local->R[param])); > + > + return val; > +} > + > /* > * Arbitrary default Rimon value: 1kOhm > * This correspond to an overcurrent limit of 55A, close to the specified limit > @@ -184,9 +225,7 @@ static int tps25990_read_word_data(struct i2c_client *client, > ret = pmbus_read_word_data(client, page, phase, reg); > if (ret < 0) > break; > - ret = DIV_ROUND_CLOSEST(ret * TPS25990_VIN_OVF_NUM, > - TPS25990_VIN_OVF_DIV); > - ret += TPS25990_VIN_OVF_OFF; > + ret = tps25990_raw_to_value(client, TPS25990_VIN_OVF, ret); > break; > > case PMBUS_IIN_OC_FAULT_LIMIT: > @@ -198,9 +237,7 @@ static int tps25990_read_word_data(struct i2c_client *client, > ret = pmbus_read_byte_data(client, page, TPS25990_VIREF); > if (ret < 0) > break; > - ret = DIV_ROUND_CLOSEST(ret * TPS25990_IIN_OCF_NUM, > - TPS25990_IIN_OCF_DIV); > - ret += TPS25990_IIN_OCF_OFF; > + ret = tps25990_raw_to_value(client, TPS25990_IIN_OCF, ret); > break; > > case PMBUS_VIRT_SAMPLES: > @@ -246,17 +283,13 @@ static int tps25990_write_word_data(struct i2c_client *client, > break; > > case PMBUS_VIN_OV_FAULT_LIMIT: > - value -= TPS25990_VIN_OVF_OFF; > - value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_VIN_OVF_DIV, > - TPS25990_VIN_OVF_NUM); > + value = tps25990_value_to_raw(client, TPS25990_VIN_OVF, value); > value = clamp_val(value, 0, 0xf); > ret = pmbus_write_word_data(client, page, reg, value); > break; > > case PMBUS_IIN_OC_FAULT_LIMIT: > - value -= TPS25990_IIN_OCF_OFF; > - value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_IIN_OCF_DIV, > - TPS25990_IIN_OCF_NUM); > + value = tps25990_value_to_raw(client, TPS25990_IIN_OCF, value); > value = clamp_val(value, 0, 0x3f); > ret = pmbus_write_byte_data(client, page, TPS25990_VIREF, value); > break; > @@ -337,7 +370,16 @@ static const struct regulator_desc tps25990_reg_desc[] = { > }; > #endif > > -static const struct pmbus_driver_info tps25990_base_info = { > +struct local_direct_value tps25590_local_info = { > + .m[TPS25990_VIN_OVF] = 10163, > + .b[TPS25990_VIN_OVF] = -30081, > + .R[TPS25990_VIN_OVF] = -4, > + .m[TPS25990_IIN_OCF] = 9538, > + .b[TPS25990_IIN_OCF] = 0, > + .R[TPS25990_IIN_OCF] = -6, > +}; > + > +static struct pmbus_driver_info tps25990_base_info = { > .pages = 1, > .format[PSC_VOLTAGE_IN] = direct, > .m[PSC_VOLTAGE_IN] = 5251, > @@ -386,14 +428,19 @@ static const struct pmbus_driver_info tps25990_base_info = { > #endif > }; > > +struct tps25990_data data_tps25990 = { > + .info = &tps25990_base_info, > + .info_local = &tps25590_local_info, > +}; > + > static const struct i2c_device_id tps25990_i2c_id[] = { > - { "tps25990" }, > + { .name = "tps25990", .driver_data = (kernel_ulong_t)&data_tps25990 }, > {} > }; > MODULE_DEVICE_TABLE(i2c, tps25990_i2c_id); > > static const struct of_device_id tps25990_of_match[] = { > - { .compatible = "ti,tps25990" }, > + { .compatible = "ti,tps25990", .data = &data_tps25990 }, > {} > }; > MODULE_DEVICE_TABLE(of, tps25990_of_match); > @@ -401,23 +448,37 @@ MODULE_DEVICE_TABLE(of, tps25990_of_match); > static int tps25990_probe(struct i2c_client *client) > { > struct device *dev = &client->dev; > - struct pmbus_driver_info *info; > + struct tps25990_data *data; > u32 rimon = TPS25990_DEFAULT_RIMON; > + struct pmbus_driver_info *info_get; > + struct local_direct_value *info_local_get; > int ret; > > ret = device_property_read_u32(dev, "ti,rimon-micro-ohms", &rimon); > if (ret < 0 && ret != -EINVAL) > return dev_err_probe(dev, ret, "failed to get rimon\n"); > > - info = devm_kmemdup(dev, &tps25990_base_info, sizeof(*info), GFP_KERNEL); > - if (!info) > + data = (struct tps25990_data *)of_device_get_match_data(dev); > + if (!data) > + return -EOPNOTSUPP; By calling of_device_get_match_data() which returns NULL on non-devicetree systems, this patch breaks support for such systems. > + > + info_get = data->info; > + /* Make copy of pmbus_info and replace it to preserve original values */ > + data->info = devm_kmemdup(dev, info_get, sizeof(*info_get), GFP_KERNEL); This overwrites information in the global data structure returned by of_device_get_match_data(). When multiple instances of the device exist, each subsequent probe overwrites the global pointer. If any device is removed, the global pointer now points to freed memory, leading to a Use-After-Free when other instances access it via of_device_get_match_data or during their own removal/re-probe. This also completely breaks multi-instance support. > + if (!data->info) > + return -ENOMEM; > + > + info_local_get = data->info_local; > + /* Make copy of pmbus_info and replace it to preserve original values */ > + data->info_local = devm_kmemdup(dev, info_local_get, sizeof(*info_local_get), GFP_KERNEL); > + if (!data->info_local) > return -ENOMEM; > > /* Adapt the current and power scale for each instance */ > - tps25990_set_m(&info->m[PSC_CURRENT_IN], rimon); > - tps25990_set_m(&info->m[PSC_POWER], rimon); > + tps25990_set_m(&data->info->m[PSC_CURRENT_IN], rimon); > + tps25990_set_m(&data->info->m[PSC_POWER], rimon); > > - return pmbus_do_probe(client, info); > + return pmbus_do_probe(client, data->info); > } > > static struct i2c_driver tps25990_driver = { ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 1/3] hwmon: (pmbus/tps25990): Rework TPS25990 non standatd direct conversion 2026-03-08 17:27 ` Guenter Roeck @ 2026-03-24 1:17 ` Stoyan Bogdanov 0 siblings, 0 replies; 10+ messages in thread From: Stoyan Bogdanov @ 2026-03-24 1:17 UTC (permalink / raw) To: Guenter Roeck Cc: jbrunet, robh, krzk+dt, conor+dt, corbet, skhan, linux-hwmon, devicetree, linux-doc, linux-kernel On Sun, Mar 8, 2026 at 7:27 PM Guenter Roeck <linux@roeck-us.net> wrote: > > On Tue, Feb 17, 2026 at 10:12:01AM +0200, Stoyan Bogdanov wrote: > > Rework existing implementation for calculation of direct > > format conversion for TPS25990. With this implamentation > > is leveraged code reusability for non standard parameters. > > - Add enum for parameter > > - Add m, b, R structure to hold value per device > > - Add data structure to hold for pmbus_driver_info and > > local_direct_values > > - Conversion functions are implemented according to formula from > > TPS25990 datasheet > > - Remove previously used defines replace with structure > > > > Signed-off-by: Stoyan Bogdanov <sbogdanov@baylibre.com> > > --- > > drivers/hwmon/pmbus/tps25990.c | 115 +++++++++++++++++++++++++-------- > > 1 file changed, 88 insertions(+), 27 deletions(-) > > > > diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c > > index c13edd7e1abf..33f6367f797c 100644 > > --- a/drivers/hwmon/pmbus/tps25990.c > > +++ b/drivers/hwmon/pmbus/tps25990.c > > @@ -36,17 +36,58 @@ > > #define TPS25990_UNLOCKED BIT(7) > > > > #define TPS25990_8B_SHIFT 2 > > -#define TPS25990_VIN_OVF_NUM 525100 > > -#define TPS25990_VIN_OVF_DIV 10163 > > -#define TPS25990_VIN_OVF_OFF 155 > > -#define TPS25990_IIN_OCF_NUM 953800 > > -#define TPS25990_IIN_OCF_DIV 129278 > > -#define TPS25990_IIN_OCF_OFF 157 > > > > #define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \ > > PK_MIN_AVG_RST_AVG | \ > > PK_MIN_AVG_RST_MIN) > > > > +enum tps25990_parameters { > > + TPS25990_VIN_OVF = 0, /* VIN over volatage fault */ > > + TPS25990_IIN_OCF, /* IIN Over currect fault */ > > + TPS25590_DIRECT_VALUES_MAX, /* Max value ensure there enough space */ > > +}; > > + > > +struct local_direct_value { > > + int m[TPS25590_DIRECT_VALUES_MAX]; /* mantissa for direct data format */ > > + int b[TPS25590_DIRECT_VALUES_MAX]; /* offset */ > > + int R[TPS25590_DIRECT_VALUES_MAX]; /* exponent */ > > +}; > > + > > +struct tps25990_data { > > + struct pmbus_driver_info *info; > > + struct local_direct_value *info_local; > > +}; > > + > > +static int tps25990_raw_to_value(struct i2c_client *client, int param, int raw) > > +{ > > + struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); > > This returns the global data structure (which is corrupted on > multi-instance systems) instead of per-instance data. Noted, will fix that in next revision. > > + struct local_direct_value *info_local = data->info_local; > > + > > + /* Formula : X = (Y / 10^R - b) / m */ > > + if (info_local->R[param] >= 0) > > + raw /= int_pow(10, info_local->R[param]); > > + else > > + raw *= int_pow(10, -info_local->R[param]); > > This will overflow if the exponent is -6 and raw is larger than ~2147. > This may not happen with current parameters, but it is brittle. > I understand that the whole calculation is fragile Another approach will be to break it in to phases, but it will lost precision // make sure R is positive u32 r_pos = info_local->R[param] >= 0 ? info_local->R[param] : -info_local->R[param]; // if (r_pos < 9) will overflow 32bit u32 r_pow = int_pow(10, r_pos); if (info_local->R[param] >= 0) { val = DIV_ROUND_CLOSEST_ULL(raw , r_pow); // result should fit in 32bit val -= info_local->b[param]; // fit 32bit val = DIV_ROUND_CLOSEST(val , info_local->m[param]); // fit 32bit } else { // info_local->R[param] < 0 // val = raw * r_pos; // most likely will overflow 32 bit with big numbers // raw * r_pow / m // assume r_pow is bigger number val = DIV_ROUND_CLOSEST(r_pow , info_local->m[param]); // fit 32bit val = raw * val; b_div = DIV_ROUND_CLOSEST(info_local->b[param], info_local->m[param]); // fit 32bit val -= b_div; } > > + > > + return DIV_ROUND_CLOSEST(raw - info_local->b[param], info_local->m[param]); > > +} > > + > > +static unsigned int tps25990_value_to_raw(struct i2c_client *client, int param, int val) > > +{ > > + struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); > > + struct local_direct_value *info_local = data->info_local; > > + > > + /* Formula : Y = ( m * X + b) * 10^R */ > > + val = (long)val * info_local->m[param] + info_local->b[param]; > > This will overflow on 32-bit systems (where long is a 32-bit value). > Similar approach could be used as in tps25990_raw_to_value > > + > > + if (info_local->R[param] >= 0) > > + val *= int_pow(10, info_local->R[param]); > > + else > > + val = DIV_ROUND_CLOSEST(val, int_pow(10, -info_local->R[param])); > > + > > + return val; > > +} > > + > > /* > > * Arbitrary default Rimon value: 1kOhm > > * This correspond to an overcurrent limit of 55A, close to the specified limit > > @@ -184,9 +225,7 @@ static int tps25990_read_word_data(struct i2c_client *client, > > ret = pmbus_read_word_data(client, page, phase, reg); > > if (ret < 0) > > break; > > - ret = DIV_ROUND_CLOSEST(ret * TPS25990_VIN_OVF_NUM, > > - TPS25990_VIN_OVF_DIV); > > - ret += TPS25990_VIN_OVF_OFF; > > + ret = tps25990_raw_to_value(client, TPS25990_VIN_OVF, ret); > > break; > > > > case PMBUS_IIN_OC_FAULT_LIMIT: > > @@ -198,9 +237,7 @@ static int tps25990_read_word_data(struct i2c_client *client, > > ret = pmbus_read_byte_data(client, page, TPS25990_VIREF); > > if (ret < 0) > > break; > > - ret = DIV_ROUND_CLOSEST(ret * TPS25990_IIN_OCF_NUM, > > - TPS25990_IIN_OCF_DIV); > > - ret += TPS25990_IIN_OCF_OFF; > > + ret = tps25990_raw_to_value(client, TPS25990_IIN_OCF, ret); > > break; > > > > case PMBUS_VIRT_SAMPLES: > > @@ -246,17 +283,13 @@ static int tps25990_write_word_data(struct i2c_client *client, > > break; > > > > case PMBUS_VIN_OV_FAULT_LIMIT: > > - value -= TPS25990_VIN_OVF_OFF; > > - value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_VIN_OVF_DIV, > > - TPS25990_VIN_OVF_NUM); > > + value = tps25990_value_to_raw(client, TPS25990_VIN_OVF, value); > > value = clamp_val(value, 0, 0xf); > > ret = pmbus_write_word_data(client, page, reg, value); > > break; > > > > case PMBUS_IIN_OC_FAULT_LIMIT: > > - value -= TPS25990_IIN_OCF_OFF; > > - value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_IIN_OCF_DIV, > > - TPS25990_IIN_OCF_NUM); > > + value = tps25990_value_to_raw(client, TPS25990_IIN_OCF, value); > > value = clamp_val(value, 0, 0x3f); > > ret = pmbus_write_byte_data(client, page, TPS25990_VIREF, value); > > break; > > @@ -337,7 +370,16 @@ static const struct regulator_desc tps25990_reg_desc[] = { > > }; > > #endif > > > > -static const struct pmbus_driver_info tps25990_base_info = { > > +struct local_direct_value tps25590_local_info = { > > + .m[TPS25990_VIN_OVF] = 10163, > > + .b[TPS25990_VIN_OVF] = -30081, > > + .R[TPS25990_VIN_OVF] = -4, > > + .m[TPS25990_IIN_OCF] = 9538, > > + .b[TPS25990_IIN_OCF] = 0, > > + .R[TPS25990_IIN_OCF] = -6, > > +}; > > + > > +static struct pmbus_driver_info tps25990_base_info = { > > .pages = 1, > > .format[PSC_VOLTAGE_IN] = direct, > > .m[PSC_VOLTAGE_IN] = 5251, > > @@ -386,14 +428,19 @@ static const struct pmbus_driver_info tps25990_base_info = { > > #endif > > }; > > > > +struct tps25990_data data_tps25990 = { > > + .info = &tps25990_base_info, > > + .info_local = &tps25590_local_info, > > +}; > > + > > static const struct i2c_device_id tps25990_i2c_id[] = { > > - { "tps25990" }, > > + { .name = "tps25990", .driver_data = (kernel_ulong_t)&data_tps25990 }, > > {} > > }; > > MODULE_DEVICE_TABLE(i2c, tps25990_i2c_id); > > > > static const struct of_device_id tps25990_of_match[] = { > > - { .compatible = "ti,tps25990" }, > > + { .compatible = "ti,tps25990", .data = &data_tps25990 }, > > {} > > }; > > MODULE_DEVICE_TABLE(of, tps25990_of_match); > > @@ -401,23 +448,37 @@ MODULE_DEVICE_TABLE(of, tps25990_of_match); > > static int tps25990_probe(struct i2c_client *client) > > { > > struct device *dev = &client->dev; > > - struct pmbus_driver_info *info; > > + struct tps25990_data *data; > > u32 rimon = TPS25990_DEFAULT_RIMON; > > + struct pmbus_driver_info *info_get; > > + struct local_direct_value *info_local_get; > > int ret; > > > > ret = device_property_read_u32(dev, "ti,rimon-micro-ohms", &rimon); > > if (ret < 0 && ret != -EINVAL) > > return dev_err_probe(dev, ret, "failed to get rimon\n"); > > > > - info = devm_kmemdup(dev, &tps25990_base_info, sizeof(*info), GFP_KERNEL); > > - if (!info) > > + data = (struct tps25990_data *)of_device_get_match_data(dev); > > + if (!data) > > + return -EOPNOTSUPP; > > By calling of_device_get_match_data() which returns NULL on non-devicetree > systems, this patch breaks support for such systems. > I understand , will fix that in next revision > > + > > + info_get = data->info; > > + /* Make copy of pmbus_info and replace it to preserve original values */ > > + data->info = devm_kmemdup(dev, info_get, sizeof(*info_get), GFP_KERNEL); > > This overwrites information in the global data structure returned by > of_device_get_match_data(). When multiple instances of the device exist, > each subsequent probe overwrites the global pointer. If any device is > removed, the global pointer now points to freed memory, leading to a > Use-After-Free when other instances access it via of_device_get_match_data > or during their own removal/re-probe. > > This also completely breaks multi-instance support. > I understand , will fix that in next revision > > + if (!data->info) > > + return -ENOMEM; > > + > > + info_local_get = data->info_local; > > + /* Make copy of pmbus_info and replace it to preserve original values */ > > + data->info_local = devm_kmemdup(dev, info_local_get, sizeof(*info_local_get), GFP_KERNEL); > > + if (!data->info_local) > > return -ENOMEM; > > > > /* Adapt the current and power scale for each instance */ > > - tps25990_set_m(&info->m[PSC_CURRENT_IN], rimon); > > - tps25990_set_m(&info->m[PSC_POWER], rimon); > > + tps25990_set_m(&data->info->m[PSC_CURRENT_IN], rimon); > > + tps25990_set_m(&data->info->m[PSC_POWER], rimon); > > > > - return pmbus_do_probe(client, info); > > + return pmbus_do_probe(client, data->info); > > } > > > > static struct i2c_driver tps25990_driver = { ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 2/3] dt-bindings: hwmon: pmbus/tps1689: Add TPS1689 2026-02-17 8:12 [PATCH v3 0/3] Add TI TPS1689 pmbus eFuse Stoyan Bogdanov 2026-02-17 8:12 ` [PATCH v3 1/3] hwmon: (pmbus/tps25990): Rework TPS25990 non standatd direct conversion Stoyan Bogdanov @ 2026-02-17 8:12 ` Stoyan Bogdanov 2026-03-08 17:31 ` Guenter Roeck 2026-02-17 8:12 ` [PATCH v3 3/3] hwmon: (pmbus/tps1689): Add TPS1689 support Stoyan Bogdanov 2 siblings, 1 reply; 10+ messages in thread From: Stoyan Bogdanov @ 2026-02-17 8:12 UTC (permalink / raw) To: jbrunet, linux, robh, krzk+dt, conor+dt, corbet, skhan Cc: linux-hwmon, devicetree, linux-doc, linux-kernel, Stoyan Bogdanov, Krzysztof Kozlowski Add device compatible support for TPS1689 Signed-off-by: Stoyan Bogdanov <sbogdanov@baylibre.com> Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> --- .../devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml index f4115870e450..973ee00c2c49 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml @@ -16,7 +16,9 @@ description: properties: compatible: - const: ti,tps25990 + enum: + - ti,tps1689 + - ti,tps25990 reg: maxItems: 1 -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/3] dt-bindings: hwmon: pmbus/tps1689: Add TPS1689 2026-02-17 8:12 ` [PATCH v3 2/3] dt-bindings: hwmon: pmbus/tps1689: Add TPS1689 Stoyan Bogdanov @ 2026-03-08 17:31 ` Guenter Roeck 2026-03-24 1:17 ` Stoyan Bogdanov 0 siblings, 1 reply; 10+ messages in thread From: Guenter Roeck @ 2026-03-08 17:31 UTC (permalink / raw) To: Stoyan Bogdanov Cc: jbrunet, robh, krzk+dt, conor+dt, corbet, skhan, linux-hwmon, devicetree, linux-doc, linux-kernel, Krzysztof Kozlowski On Tue, Feb 17, 2026 at 10:12:02AM +0200, Stoyan Bogdanov wrote: > Add device compatible support for TPS1689 > The title and description of this patch are really misleading since they don't mention that support is added to ti,tps25990.yaml. Also, the "title" field in the .yaml file still refers to "Texas Instruments TPS25990 Stackable eFuse" which isn't really accurate anymore. Guenter > Signed-off-by: Stoyan Bogdanov <sbogdanov@baylibre.com> > Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> > --- > .../devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml | 4 +++- > 1 file changed, 3 insertions(+), 1 deletion(-) > > diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml > index f4115870e450..973ee00c2c49 100644 > --- a/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml > +++ b/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml > @@ -16,7 +16,9 @@ description: > > properties: > compatible: > - const: ti,tps25990 > + enum: > + - ti,tps1689 > + - ti,tps25990 > > reg: > maxItems: 1 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/3] dt-bindings: hwmon: pmbus/tps1689: Add TPS1689 2026-03-08 17:31 ` Guenter Roeck @ 2026-03-24 1:17 ` Stoyan Bogdanov 0 siblings, 0 replies; 10+ messages in thread From: Stoyan Bogdanov @ 2026-03-24 1:17 UTC (permalink / raw) To: Guenter Roeck Cc: jbrunet, robh, krzk+dt, conor+dt, corbet, skhan, linux-hwmon, devicetree, linux-doc, linux-kernel, Krzysztof Kozlowski On Sun, Mar 8, 2026 at 7:31 PM Guenter Roeck <linux@roeck-us.net> wrote: > > On Tue, Feb 17, 2026 at 10:12:02AM +0200, Stoyan Bogdanov wrote: > > Add device compatible support for TPS1689 > > > > The title and description of this patch are really misleading > since they don't mention that support is added to ti,tps25990.yaml. > I will add in next revision > Also, the "title" field in the .yaml file still refers to "Texas > Instruments TPS25990 Stackable eFuse" which isn't really accurate > anymore. I thought it should stay as is since driver is still tps25590, but yes it make sens to be changed. I believe this should be acceptable title: Texas Instruments Stackable eFuses > Guenter > > > Signed-off-by: Stoyan Bogdanov <sbogdanov@baylibre.com> > > Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> > > --- > > .../devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml | 4 +++- > > 1 file changed, 3 insertions(+), 1 deletion(-) > > > > diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml > > index f4115870e450..973ee00c2c49 100644 > > --- a/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml > > +++ b/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml > > @@ -16,7 +16,9 @@ description: > > > > properties: > > compatible: > > - const: ti,tps25990 > > + enum: > > + - ti,tps1689 > > + - ti,tps25990 > > > > reg: > > maxItems: 1 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 3/3] hwmon: (pmbus/tps1689): Add TPS1689 support 2026-02-17 8:12 [PATCH v3 0/3] Add TI TPS1689 pmbus eFuse Stoyan Bogdanov 2026-02-17 8:12 ` [PATCH v3 1/3] hwmon: (pmbus/tps25990): Rework TPS25990 non standatd direct conversion Stoyan Bogdanov 2026-02-17 8:12 ` [PATCH v3 2/3] dt-bindings: hwmon: pmbus/tps1689: Add TPS1689 Stoyan Bogdanov @ 2026-02-17 8:12 ` Stoyan Bogdanov 2026-03-08 17:34 ` Guenter Roeck 2 siblings, 1 reply; 10+ messages in thread From: Stoyan Bogdanov @ 2026-02-17 8:12 UTC (permalink / raw) To: jbrunet, linux, robh, krzk+dt, conor+dt, corbet, skhan Cc: linux-hwmon, devicetree, linux-doc, linux-kernel, Stoyan Bogdanov Extend tps25990 existing driver to support tps1689 eFuse, since they are sharing command interface and functionality Update documentation for tps1689 Signed-off-by: Stoyan Bogdanov <sbogdanov@baylibre.com> --- Documentation/hwmon/tps25990.rst | 15 ++++-- drivers/hwmon/pmbus/tps25990.c | 91 ++++++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 14 deletions(-) diff --git a/Documentation/hwmon/tps25990.rst b/Documentation/hwmon/tps25990.rst index 04faec780d26..e8bc9a550bda 100644 --- a/Documentation/hwmon/tps25990.rst +++ b/Documentation/hwmon/tps25990.rst @@ -9,26 +9,31 @@ Supported chips: Prefix: 'tps25990' - * Datasheet + Datasheet: Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps25990 - Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps25990 + * TI TPS1689 + + Prefix: 'tps1689' + + Datasheet: Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps1689 Author: Jerome Brunet <jbrunet@baylibre.com> + Stoyan Bogdanov <sbogdanov@baylibre.com> Description ----------- -This driver implements support for TI TPS25990 eFuse. +This driver implements support for TI TPS25990 and TI TPS1689 eFuse chips. This is an integrated, high-current circuit protection and power management device with PMBUS interface -Device compliant with: +Devices are compliant with: - PMBus rev 1.3 interface. -Device supports direct format for reading input voltages, +Devices supports direct format for reading input voltages, output voltage, input current, input power and temperature. Due to the specificities of the chip, all history reset attributes diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c index 33f6367f797c..f9ff4edadf53 100644 --- a/drivers/hwmon/pmbus/tps25990.c +++ b/drivers/hwmon/pmbus/tps25990.c @@ -58,34 +58,38 @@ struct tps25990_data { struct local_direct_value *info_local; }; -static int tps25990_raw_to_value(struct i2c_client *client, int param, int raw) +static int tps25990_raw_to_value(struct i2c_client *client, int param, u32 raw) { struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); struct local_direct_value *info_local = data->info_local; + int val; /* Formula : X = (Y / 10^R - b) / m */ if (info_local->R[param] >= 0) - raw /= int_pow(10, info_local->R[param]); + val = DIV_ROUND_CLOSEST_ULL(raw, int_pow(10, info_local->R[param])); else - raw *= int_pow(10, -info_local->R[param]); + val = raw * int_pow(10, -info_local->R[param]); - return DIV_ROUND_CLOSEST(raw - info_local->b[param], info_local->m[param]); + val = DIV_ROUND_CLOSEST(val - info_local->b[param], info_local->m[param]); + + return val; } static unsigned int tps25990_value_to_raw(struct i2c_client *client, int param, int val) { struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); struct local_direct_value *info_local = data->info_local; + u32 raw; // return raw up to u16 -> u32 /* Formula : Y = ( m * X + b) * 10^R */ - val = (long)val * info_local->m[param] + info_local->b[param]; + raw = ((long)val * info_local->m[param]) + info_local->b[param]; if (info_local->R[param] >= 0) - val *= int_pow(10, info_local->R[param]); + raw *= int_pow(10, info_local->R[param]); else - val = DIV_ROUND_CLOSEST(val, int_pow(10, -info_local->R[param])); + raw = DIV_ROUND_CLOSEST_ULL(raw, int_pow(10, -info_local->R[param])); - return val; + return raw; } /* @@ -281,7 +285,6 @@ static int tps25990_write_word_data(struct i2c_client *client, value = clamp_val(value, 0, 0xff); ret = pmbus_write_word_data(client, page, reg, value); break; - case PMBUS_VIN_OV_FAULT_LIMIT: value = tps25990_value_to_raw(client, TPS25990_VIN_OVF, value); value = clamp_val(value, 0, 0xf); @@ -370,6 +373,15 @@ static const struct regulator_desc tps25990_reg_desc[] = { }; #endif +struct local_direct_value tps1689_local_info = { + .m[TPS25990_VIN_OVF] = 3984, + .b[TPS25990_VIN_OVF] = -63750, + .R[TPS25990_VIN_OVF] = -3, + .m[TPS25990_IIN_OCF] = 7111, + .b[TPS25990_IIN_OCF] = -2133, + .R[TPS25990_IIN_OCF] = -2, +}; + struct local_direct_value tps25590_local_info = { .m[TPS25990_VIN_OVF] = 10163, .b[TPS25990_VIN_OVF] = -30081, @@ -379,6 +391,60 @@ struct local_direct_value tps25590_local_info = { .R[TPS25990_IIN_OCF] = -6, }; +static struct pmbus_driver_info tps1689_base_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_VOLTAGE_IN] = 1166, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -2, + .format[PSC_VOLTAGE_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 1166, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -2, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 140, + .b[PSC_TEMPERATURE] = 32103, + .R[PSC_TEMPERATURE] = -2, + /* + * Current and Power measurement depends on the ohm value + * of Rimon. m is multiplied by 1000 below to have an integer + * and -3 is added to R to compensate. + */ + .format[PSC_CURRENT_IN] = direct, + .m[PSC_CURRENT_IN] = 9548, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = -6, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_CURRENT_OUT] = 24347, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = -3, + .format[PSC_POWER] = direct, + .m[PSC_POWER] = 2775, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = -4, + .func[0] = (PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | + PMBUS_HAVE_VMON | + PMBUS_HAVE_IIN | + PMBUS_HAVE_PIN | + PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_SAMPLES), + + .read_word_data = tps25990_read_word_data, + .write_word_data = tps25990_write_word_data, + .read_byte_data = tps25990_read_byte_data, + .write_byte_data = tps25990_write_byte_data, + +#if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR) + .reg_desc = tps25990_reg_desc, + .num_regulators = ARRAY_SIZE(tps25990_reg_desc), +#endif +}; + static struct pmbus_driver_info tps25990_base_info = { .pages = 1, .format[PSC_VOLTAGE_IN] = direct, @@ -428,18 +494,25 @@ static struct pmbus_driver_info tps25990_base_info = { #endif }; +struct tps25990_data data_tps1689 = { + .info = &tps1689_base_info, + .info_local = &tps1689_local_info, +}; + struct tps25990_data data_tps25990 = { .info = &tps25990_base_info, .info_local = &tps25590_local_info, }; static const struct i2c_device_id tps25990_i2c_id[] = { + { .name = "tps1689", .driver_data = (kernel_ulong_t)&data_tps1689 }, { .name = "tps25990", .driver_data = (kernel_ulong_t)&data_tps25990 }, {} }; MODULE_DEVICE_TABLE(i2c, tps25990_i2c_id); static const struct of_device_id tps25990_of_match[] = { + { .compatible = "ti,tps1689", .data = &data_tps1689 }, { .compatible = "ti,tps25990", .data = &data_tps25990 }, {} }; -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3 3/3] hwmon: (pmbus/tps1689): Add TPS1689 support 2026-02-17 8:12 ` [PATCH v3 3/3] hwmon: (pmbus/tps1689): Add TPS1689 support Stoyan Bogdanov @ 2026-03-08 17:34 ` Guenter Roeck 2026-03-24 1:17 ` Stoyan Bogdanov 0 siblings, 1 reply; 10+ messages in thread From: Guenter Roeck @ 2026-03-08 17:34 UTC (permalink / raw) To: Stoyan Bogdanov Cc: jbrunet, robh, krzk+dt, conor+dt, corbet, skhan, linux-hwmon, devicetree, linux-doc, linux-kernel On Tue, Feb 17, 2026 at 10:12:03AM +0200, Stoyan Bogdanov wrote: > Extend tps25990 existing driver to support tps1689 eFuse, > since they are sharing command interface and functionality > Update documentation for tps1689 > > Signed-off-by: Stoyan Bogdanov <sbogdanov@baylibre.com> > --- > Documentation/hwmon/tps25990.rst | 15 ++++-- > drivers/hwmon/pmbus/tps25990.c | 91 ++++++++++++++++++++++++++++---- > 2 files changed, 92 insertions(+), 14 deletions(-) > > diff --git a/Documentation/hwmon/tps25990.rst b/Documentation/hwmon/tps25990.rst > index 04faec780d26..e8bc9a550bda 100644 > --- a/Documentation/hwmon/tps25990.rst > +++ b/Documentation/hwmon/tps25990.rst > @@ -9,26 +9,31 @@ Supported chips: > > Prefix: 'tps25990' > > - * Datasheet > + Datasheet: Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps25990 > > - Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps25990 > + * TI TPS1689 > + > + Prefix: 'tps1689' > + > + Datasheet: Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps1689 > > Author: > > Jerome Brunet <jbrunet@baylibre.com> > + Stoyan Bogdanov <sbogdanov@baylibre.com> > > Description > ----------- > > -This driver implements support for TI TPS25990 eFuse. > +This driver implements support for TI TPS25990 and TI TPS1689 eFuse chips. > This is an integrated, high-current circuit protection and power > management device with PMBUS interface > > -Device compliant with: > +Devices are compliant with: > > - PMBus rev 1.3 interface. > > -Device supports direct format for reading input voltages, > +Devices supports direct format for reading input voltages, > output voltage, input current, input power and temperature. > > Due to the specificities of the chip, all history reset attributes > diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c > index 33f6367f797c..f9ff4edadf53 100644 > --- a/drivers/hwmon/pmbus/tps25990.c > +++ b/drivers/hwmon/pmbus/tps25990.c > @@ -58,34 +58,38 @@ struct tps25990_data { > struct local_direct_value *info_local; > }; > > -static int tps25990_raw_to_value(struct i2c_client *client, int param, int raw) > +static int tps25990_raw_to_value(struct i2c_client *client, int param, u32 raw) > { > struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); > struct local_direct_value *info_local = data->info_local; > + int val; > > /* Formula : X = (Y / 10^R - b) / m */ > if (info_local->R[param] >= 0) > - raw /= int_pow(10, info_local->R[param]); > + val = DIV_ROUND_CLOSEST_ULL(raw, int_pow(10, info_local->R[param])); > else > - raw *= int_pow(10, -info_local->R[param]); > + val = raw * int_pow(10, -info_local->R[param]); Can the calculation in the 'else' branch overflow? If info_local->R[param] is -6 (as it is for TPS25990_IIN_OCF), then int_pow(10, 6) is 1,000,000. If raw is a 16-bit word like 65535, the product is 65,535,000,000. Since val is an int, it will overflow on 32-bit systems (and even on 64-bit systems if int is 32-bit, which is the case in the kernel). Should val be a 64-bit type for the intermediate calculation? > > - return DIV_ROUND_CLOSEST(raw - info_local->b[param], info_local->m[param]); > + val = DIV_ROUND_CLOSEST(val - info_local->b[param], info_local->m[param]); > + > + return val; > } > > static unsigned int tps25990_value_to_raw(struct i2c_client *client, int param, int val) > { > struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); > struct local_direct_value *info_local = data->info_local; > + u32 raw; // return raw up to u16 -> u32 > > /* Formula : Y = ( m * X + b) * 10^R */ > - val = (long)val * info_local->m[param] + info_local->b[param]; > + raw = ((long)val * info_local->m[param]) + info_local->b[param]; > > if (info_local->R[param] >= 0) > - val *= int_pow(10, info_local->R[param]); > + raw *= int_pow(10, info_local->R[param]); > else > - val = DIV_ROUND_CLOSEST(val, int_pow(10, -info_local->R[param])); > + raw = DIV_ROUND_CLOSEST_ULL(raw, int_pow(10, -info_local->R[param])); > > - return val; > + return raw; Does this change handle negative results correctly? If (val * m + b) is negative, assigning it to the u32 raw variable will result in a very large positive number due to underflow. Subsequent scaling and clamping in the caller will then produce an incorrect result (e.g., 0xf instead of 0). The previous implementation used a signed type for the intermediate result. Is there a reason to switch to u32 before the final result is determined? > } > > /* > @@ -281,7 +285,6 @@ static int tps25990_write_word_data(struct i2c_client *client, > value = clamp_val(value, 0, 0xff); > ret = pmbus_write_word_data(client, page, reg, value); > break; > - > case PMBUS_VIN_OV_FAULT_LIMIT: > value = tps25990_value_to_raw(client, TPS25990_VIN_OVF, value); > value = clamp_val(value, 0, 0xf); > @@ -370,6 +373,15 @@ static const struct regulator_desc tps25990_reg_desc[] = { > }; > #endif > > +struct local_direct_value tps1689_local_info = { > + .m[TPS25990_VIN_OVF] = 3984, > + .b[TPS25990_VIN_OVF] = -63750, > + .R[TPS25990_VIN_OVF] = -3, > + .m[TPS25990_IIN_OCF] = 7111, > + .b[TPS25990_IIN_OCF] = -2133, > + .R[TPS25990_IIN_OCF] = -2, > +}; > + > struct local_direct_value tps25590_local_info = { > .m[TPS25990_VIN_OVF] = 10163, > .b[TPS25990_VIN_OVF] = -30081, > @@ -379,6 +391,60 @@ struct local_direct_value tps25590_local_info = { > .R[TPS25990_IIN_OCF] = -6, > }; > > +static struct pmbus_driver_info tps1689_base_info = { > + .pages = 1, > + .format[PSC_VOLTAGE_IN] = direct, > + .m[PSC_VOLTAGE_IN] = 1166, > + .b[PSC_VOLTAGE_IN] = 0, > + .R[PSC_VOLTAGE_IN] = -2, > + .format[PSC_VOLTAGE_OUT] = direct, > + .m[PSC_VOLTAGE_OUT] = 1166, > + .b[PSC_VOLTAGE_OUT] = 0, > + .R[PSC_VOLTAGE_OUT] = -2, > + .format[PSC_TEMPERATURE] = direct, > + .m[PSC_TEMPERATURE] = 140, > + .b[PSC_TEMPERATURE] = 32103, > + .R[PSC_TEMPERATURE] = -2, > + /* > + * Current and Power measurement depends on the ohm value > + * of Rimon. m is multiplied by 1000 below to have an integer > + * and -3 is added to R to compensate. > + */ > + .format[PSC_CURRENT_IN] = direct, > + .m[PSC_CURRENT_IN] = 9548, > + .b[PSC_CURRENT_IN] = 0, > + .R[PSC_CURRENT_IN] = -6, > + .format[PSC_CURRENT_OUT] = direct, > + .m[PSC_CURRENT_OUT] = 24347, > + .b[PSC_CURRENT_OUT] = 0, > + .R[PSC_CURRENT_OUT] = -3, > + .format[PSC_POWER] = direct, > + .m[PSC_POWER] = 2775, > + .b[PSC_POWER] = 0, > + .R[PSC_POWER] = -4, > + .func[0] = (PMBUS_HAVE_VIN | > + PMBUS_HAVE_VOUT | > + PMBUS_HAVE_VMON | > + PMBUS_HAVE_IIN | > + PMBUS_HAVE_PIN | > + PMBUS_HAVE_TEMP | > + PMBUS_HAVE_STATUS_VOUT | > + PMBUS_HAVE_STATUS_IOUT | > + PMBUS_HAVE_STATUS_INPUT | > + PMBUS_HAVE_STATUS_TEMP | > + PMBUS_HAVE_SAMPLES), > + > + .read_word_data = tps25990_read_word_data, > + .write_word_data = tps25990_write_word_data, > + .read_byte_data = tps25990_read_byte_data, > + .write_byte_data = tps25990_write_byte_data, > + > +#if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR) > + .reg_desc = tps25990_reg_desc, > + .num_regulators = ARRAY_SIZE(tps25990_reg_desc), > +#endif > +}; > + > static struct pmbus_driver_info tps25990_base_info = { > .pages = 1, > .format[PSC_VOLTAGE_IN] = direct, > @@ -428,18 +494,25 @@ static struct pmbus_driver_info tps25990_base_info = { > #endif > }; > > +struct tps25990_data data_tps1689 = { > + .info = &tps1689_base_info, > + .info_local = &tps1689_local_info, > +}; > + > struct tps25990_data data_tps25990 = { > .info = &tps25990_base_info, > .info_local = &tps25590_local_info, > }; > > static const struct i2c_device_id tps25990_i2c_id[] = { > + { .name = "tps1689", .driver_data = (kernel_ulong_t)&data_tps1689 }, > { .name = "tps25990", .driver_data = (kernel_ulong_t)&data_tps25990 }, > {} > }; > MODULE_DEVICE_TABLE(i2c, tps25990_i2c_id); > > static const struct of_device_id tps25990_of_match[] = { > + { .compatible = "ti,tps1689", .data = &data_tps1689 }, > { .compatible = "ti,tps25990", .data = &data_tps25990 }, > {} > }; ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 3/3] hwmon: (pmbus/tps1689): Add TPS1689 support 2026-03-08 17:34 ` Guenter Roeck @ 2026-03-24 1:17 ` Stoyan Bogdanov 0 siblings, 0 replies; 10+ messages in thread From: Stoyan Bogdanov @ 2026-03-24 1:17 UTC (permalink / raw) To: Guenter Roeck Cc: jbrunet, robh, krzk+dt, conor+dt, corbet, skhan, linux-hwmon, devicetree, linux-doc, linux-kernel On Sun, Mar 8, 2026 at 7:34 PM Guenter Roeck <linux@roeck-us.net> wrote: > > On Tue, Feb 17, 2026 at 10:12:03AM +0200, Stoyan Bogdanov wrote: > > Extend tps25990 existing driver to support tps1689 eFuse, > > since they are sharing command interface and functionality > > Update documentation for tps1689 > > > > Signed-off-by: Stoyan Bogdanov <sbogdanov@baylibre.com> > > --- > > Documentation/hwmon/tps25990.rst | 15 ++++-- > > drivers/hwmon/pmbus/tps25990.c | 91 ++++++++++++++++++++++++++++---- > > 2 files changed, 92 insertions(+), 14 deletions(-) > > > > diff --git a/Documentation/hwmon/tps25990.rst b/Documentation/hwmon/tps25990.rst > > index 04faec780d26..e8bc9a550bda 100644 > > --- a/Documentation/hwmon/tps25990.rst > > +++ b/Documentation/hwmon/tps25990.rst > > @@ -9,26 +9,31 @@ Supported chips: > > > > Prefix: 'tps25990' > > > > - * Datasheet > > + Datasheet: Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps25990 > > > > - Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps25990 > > + * TI TPS1689 > > + > > + Prefix: 'tps1689' > > + > > + Datasheet: Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps1689 > > > > Author: > > > > Jerome Brunet <jbrunet@baylibre.com> > > + Stoyan Bogdanov <sbogdanov@baylibre.com> > > > > Description > > ----------- > > > > -This driver implements support for TI TPS25990 eFuse. > > +This driver implements support for TI TPS25990 and TI TPS1689 eFuse chips. > > This is an integrated, high-current circuit protection and power > > management device with PMBUS interface > > > > -Device compliant with: > > +Devices are compliant with: > > > > - PMBus rev 1.3 interface. > > > > -Device supports direct format for reading input voltages, > > +Devices supports direct format for reading input voltages, > > output voltage, input current, input power and temperature. > > > > Due to the specificities of the chip, all history reset attributes > > diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c > > index 33f6367f797c..f9ff4edadf53 100644 > > --- a/drivers/hwmon/pmbus/tps25990.c > > +++ b/drivers/hwmon/pmbus/tps25990.c > > @@ -58,34 +58,38 @@ struct tps25990_data { > > struct local_direct_value *info_local; > > }; > > > > -static int tps25990_raw_to_value(struct i2c_client *client, int param, int raw) > > +static int tps25990_raw_to_value(struct i2c_client *client, int param, u32 raw) > > { > > struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); > > struct local_direct_value *info_local = data->info_local; > > + int val; > > > > /* Formula : X = (Y / 10^R - b) / m */ > > if (info_local->R[param] >= 0) > > - raw /= int_pow(10, info_local->R[param]); > > + val = DIV_ROUND_CLOSEST_ULL(raw, int_pow(10, info_local->R[param])); > > else > > - raw *= int_pow(10, -info_local->R[param]); > > + val = raw * int_pow(10, -info_local->R[param]); > > Can the calculation in the 'else' branch overflow? > > If info_local->R[param] is -6 (as it is for TPS25990_IIN_OCF), then > int_pow(10, 6) is 1,000,000. If raw is a 16-bit word like 65535, the product > is 65,535,000,000. > > Since val is an int, it will overflow on 32-bit systems (and even on 64-bit > systems if int is 32-bit, which is the case in the kernel). > > Should val be a 64-bit type for the intermediate calculation? > > > > > - return DIV_ROUND_CLOSEST(raw - info_local->b[param], info_local->m[param]); > > + val = DIV_ROUND_CLOSEST(val - info_local->b[param], info_local->m[param]); > > + > > + return val; > > } > > > > static unsigned int tps25990_value_to_raw(struct i2c_client *client, int param, int val) > > { > > struct tps25990_data *data = (struct tps25990_data *)of_device_get_match_data(&client->dev); > > struct local_direct_value *info_local = data->info_local; > > + u32 raw; // return raw up to u16 -> u32 > > > > /* Formula : Y = ( m * X + b) * 10^R */ > > - val = (long)val * info_local->m[param] + info_local->b[param]; > > + raw = ((long)val * info_local->m[param]) + info_local->b[param]; > > > > if (info_local->R[param] >= 0) > > - val *= int_pow(10, info_local->R[param]); > > + raw *= int_pow(10, info_local->R[param]); > > else > > - val = DIV_ROUND_CLOSEST(val, int_pow(10, -info_local->R[param])); > > + raw = DIV_ROUND_CLOSEST_ULL(raw, int_pow(10, -info_local->R[param])); > > > > - return val; > > + return raw; > > Does this change handle negative results correctly? > > If (val * m + b) is negative, assigning it to the u32 raw variable will > result in a very large positive number due to underflow. Subsequent > scaling and clamping in the caller will then produce an incorrect result > (e.g., 0xf instead of 0). > > The previous implementation used a signed type for the intermediate result. > Is there a reason to switch to u32 before the final result is determined? This was supposed to be in to previous implementation because, CI catch errors and this was theoretically the fix. This patch should have only tps1689 related changes and those are not. I will move them on next revision to if still needed after rework or conversion functions to hwmon: (pmbus/tps25990): Rework TPS25990 non standatd direct conversion > > } > > > > /* > > @@ -281,7 +285,6 @@ static int tps25990_write_word_data(struct i2c_client *client, > > value = clamp_val(value, 0, 0xff); > > ret = pmbus_write_word_data(client, page, reg, value); > > break; > > - > > case PMBUS_VIN_OV_FAULT_LIMIT: > > value = tps25990_value_to_raw(client, TPS25990_VIN_OVF, value); > > value = clamp_val(value, 0, 0xf); > > @@ -370,6 +373,15 @@ static const struct regulator_desc tps25990_reg_desc[] = { > > }; > > #endif > > > > +struct local_direct_value tps1689_local_info = { > > + .m[TPS25990_VIN_OVF] = 3984, > > + .b[TPS25990_VIN_OVF] = -63750, > > + .R[TPS25990_VIN_OVF] = -3, > > + .m[TPS25990_IIN_OCF] = 7111, > > + .b[TPS25990_IIN_OCF] = -2133, > > + .R[TPS25990_IIN_OCF] = -2, > > +}; > > + > > struct local_direct_value tps25590_local_info = { > > .m[TPS25990_VIN_OVF] = 10163, > > .b[TPS25990_VIN_OVF] = -30081, > > @@ -379,6 +391,60 @@ struct local_direct_value tps25590_local_info = { > > .R[TPS25990_IIN_OCF] = -6, > > }; > > > > +static struct pmbus_driver_info tps1689_base_info = { > > + .pages = 1, > > + .format[PSC_VOLTAGE_IN] = direct, > > + .m[PSC_VOLTAGE_IN] = 1166, > > + .b[PSC_VOLTAGE_IN] = 0, > > + .R[PSC_VOLTAGE_IN] = -2, > > + .format[PSC_VOLTAGE_OUT] = direct, > > + .m[PSC_VOLTAGE_OUT] = 1166, > > + .b[PSC_VOLTAGE_OUT] = 0, > > + .R[PSC_VOLTAGE_OUT] = -2, > > + .format[PSC_TEMPERATURE] = direct, > > + .m[PSC_TEMPERATURE] = 140, > > + .b[PSC_TEMPERATURE] = 32103, > > + .R[PSC_TEMPERATURE] = -2, > > + /* > > + * Current and Power measurement depends on the ohm value > > + * of Rimon. m is multiplied by 1000 below to have an integer > > + * and -3 is added to R to compensate. > > + */ > > + .format[PSC_CURRENT_IN] = direct, > > + .m[PSC_CURRENT_IN] = 9548, > > + .b[PSC_CURRENT_IN] = 0, > > + .R[PSC_CURRENT_IN] = -6, > > + .format[PSC_CURRENT_OUT] = direct, > > + .m[PSC_CURRENT_OUT] = 24347, > > + .b[PSC_CURRENT_OUT] = 0, > > + .R[PSC_CURRENT_OUT] = -3, > > + .format[PSC_POWER] = direct, > > + .m[PSC_POWER] = 2775, > > + .b[PSC_POWER] = 0, > > + .R[PSC_POWER] = -4, > > + .func[0] = (PMBUS_HAVE_VIN | > > + PMBUS_HAVE_VOUT | > > + PMBUS_HAVE_VMON | > > + PMBUS_HAVE_IIN | > > + PMBUS_HAVE_PIN | > > + PMBUS_HAVE_TEMP | > > + PMBUS_HAVE_STATUS_VOUT | > > + PMBUS_HAVE_STATUS_IOUT | > > + PMBUS_HAVE_STATUS_INPUT | > > + PMBUS_HAVE_STATUS_TEMP | > > + PMBUS_HAVE_SAMPLES), > > + > > + .read_word_data = tps25990_read_word_data, > > + .write_word_data = tps25990_write_word_data, > > + .read_byte_data = tps25990_read_byte_data, > > + .write_byte_data = tps25990_write_byte_data, > > + > > +#if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR) > > + .reg_desc = tps25990_reg_desc, > > + .num_regulators = ARRAY_SIZE(tps25990_reg_desc), > > +#endif > > +}; > > + > > static struct pmbus_driver_info tps25990_base_info = { > > .pages = 1, > > .format[PSC_VOLTAGE_IN] = direct, > > @@ -428,18 +494,25 @@ static struct pmbus_driver_info tps25990_base_info = { > > #endif > > }; > > > > +struct tps25990_data data_tps1689 = { > > + .info = &tps1689_base_info, > > + .info_local = &tps1689_local_info, > > +}; > > + > > struct tps25990_data data_tps25990 = { > > .info = &tps25990_base_info, > > .info_local = &tps25590_local_info, > > }; > > > > static const struct i2c_device_id tps25990_i2c_id[] = { > > + { .name = "tps1689", .driver_data = (kernel_ulong_t)&data_tps1689 }, > > { .name = "tps25990", .driver_data = (kernel_ulong_t)&data_tps25990 }, > > {} > > }; > > MODULE_DEVICE_TABLE(i2c, tps25990_i2c_id); > > > > static const struct of_device_id tps25990_of_match[] = { > > + { .compatible = "ti,tps1689", .data = &data_tps1689 }, > > { .compatible = "ti,tps25990", .data = &data_tps25990 }, > > {} > > }; ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-03-24 1:17 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-02-17 8:12 [PATCH v3 0/3] Add TI TPS1689 pmbus eFuse Stoyan Bogdanov 2026-02-17 8:12 ` [PATCH v3 1/3] hwmon: (pmbus/tps25990): Rework TPS25990 non standatd direct conversion Stoyan Bogdanov 2026-03-08 17:27 ` Guenter Roeck 2026-03-24 1:17 ` Stoyan Bogdanov 2026-02-17 8:12 ` [PATCH v3 2/3] dt-bindings: hwmon: pmbus/tps1689: Add TPS1689 Stoyan Bogdanov 2026-03-08 17:31 ` Guenter Roeck 2026-03-24 1:17 ` Stoyan Bogdanov 2026-02-17 8:12 ` [PATCH v3 3/3] hwmon: (pmbus/tps1689): Add TPS1689 support Stoyan Bogdanov 2026-03-08 17:34 ` Guenter Roeck 2026-03-24 1:17 ` Stoyan Bogdanov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox