hwmon fixes for v7.0-rc6

- PMBus driver fixes
 
   * Add mutex protection for regulator operations
 
   * Fix reading from "write-only" attributes
 
   * Mark lowest/average/highest/rated attributes as read-only
 
   * isl68137: Add mutex protection for AVS enable sysfs attributes
 
   * ina233:  Fix error handling and sign extension when reading shunt voltage
 
 - adm1177: Fix sysfs ABI violation and current unit conversion
 
 - peci: Fix off-by-one in cputemp_is_visible(), and crit_hyst returning
   delta instead of absolute temperature
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmnHFFEACgkQyx8mb86f
 mYEUJQ//anhuaDrLyAQUU5mtgoQu53gazUEW8mg2jIYKrQJx6nXpWuKWcgoASDRX
 YJbvIydm9VFOrXLhfefncSI3xTgyB/PQV0gnNMZBwSxXX4Fw+zAXiH7aS6haUp82
 BuM09yreaNTqa1v5b+5Av13BPnL+ZbiEfyoIhCRKtaI0UlBgoDD+DRT3EkXwubnE
 CVFKptgD5BvSTWgO/wmOdOiBCJNfxeH/xwIY4INsInDwFwQP8F1tF+m9EB1mkbII
 LG/kjwiNv1Xqf/CCsJnlMN7rYLlb3KvhdMcMtB380ImX3tJ+SP35c1+ZJ9DP+es9
 2dScRBThI33NaqAz2l0wpqhDjuY/KjIAjpxIcRJpR4hlWr/vVQwoGyRBofNkasIO
 +aV3mHP6qbVhwFtcGZ5MqU7AOnBOJTim9KqaBoZAR8p6VnViTUXN2wgwZyU3NnXO
 I9f0zIpagw7I0gP3WWbMv2++PVnEP/+Huc0aZtIeFVqcR17TFp9Zj+nILa0guuvq
 ZxgdR/2BXfEgMNNIKyleIXuXLvuPFQ6MH+cf/roVeC7XKtdYeVh3LUJdtlVRT8xO
 2xrCbKScuAnzrmKPpqkSESU7VUJ2F6hFFl8DVUY8UZgv/DQWFvEQjf9r0mzHs0eb
 lpmh1BPqp4V8KhChef80tGBqMuNezXklY4IQcUvKyLuuRY0WOpM=
 =dKNH
 -----END PGP SIGNATURE-----

Merge tag 'hwmon-for-v7.0-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging

Pull hwmon fixes from Guenter Roeck:

 - PMBus driver fixes:
     - Add mutex protection for regulator operations
     - Fix reading from "write-only" attributes
     - Mark lowest/average/highest/rated attributes as read-only
     - isl68137: Add mutex protection for AVS enable sysfs attributes
     - ina233:  Fix error handling and sign extension when reading shunt voltage

 - adm1177: Fix sysfs ABI violation and current unit conversion

 - peci: Fix off-by-one in cputemp_is_visible(), and crit_hyst returning
   delta instead of absolute temperature

* tag 'hwmon-for-v7.0-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging:
  hwmon: (pmbus/core) Protect regulator operations with mutex
  hwmon: (pmbus) Introduce the concept of "write-only" attributes
  hwmon: (pmbus) Mark lowest/average/highest/rated attributes as read-only
  hwmon: (adm1177) fix sysfs ABI violation and current unit conversion
  hwmon: (peci/cputemp) Fix off-by-one in cputemp_is_visible()
  hwmon: (peci/cputemp) Fix crit_hyst returning delta instead of absolute temperature
  hwmon: (pmbus/isl68137) Add mutex protection for AVS enable sysfs attributes
  hwmon: (pmbus/ina233) Fix error handling and sign extension in shunt voltage read
master
Linus Torvalds 2026-03-27 20:02:34 -07:00
commit be762d8b6d
7 changed files with 217 additions and 75 deletions

View File

@ -27,10 +27,10 @@ for details.
Sysfs entries
-------------
The following attributes are supported. Current maxim attribute
The following attributes are supported. Current maximum attribute
is read-write, all other attributes are read-only.
in0_input Measured voltage in microvolts.
in0_input Measured voltage in millivolts.
curr1_input Measured current in microamperes.
curr1_max_alarm Overcurrent alarm in microamperes.
curr1_input Measured current in milliamperes.
curr1_max Overcurrent shutdown threshold in milliamperes.

View File

@ -51,8 +51,9 @@ temp1_max Provides thermal control temperature of the CPU package
temp1_crit Provides shutdown temperature of the CPU package which
is also known as the maximum processor junction
temperature, Tjmax or Tprochot.
temp1_crit_hyst Provides the hysteresis value from Tcontrol to Tjmax of
the CPU package.
temp1_crit_hyst Provides the hysteresis temperature of the CPU
package. Returns Tcontrol, the temperature at which
the critical condition clears.
temp2_label "DTS"
temp2_input Provides current temperature of the CPU package scaled
@ -62,8 +63,9 @@ temp2_max Provides thermal control temperature of the CPU package
temp2_crit Provides shutdown temperature of the CPU package which
is also known as the maximum processor junction
temperature, Tjmax or Tprochot.
temp2_crit_hyst Provides the hysteresis value from Tcontrol to Tjmax of
the CPU package.
temp2_crit_hyst Provides the hysteresis temperature of the CPU
package. Returns Tcontrol, the temperature at which
the critical condition clears.
temp3_label "Tcontrol"
temp3_input Provides current Tcontrol temperature of the CPU

View File

@ -10,6 +10,8 @@
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/math64.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
@ -33,7 +35,7 @@
struct adm1177_state {
struct i2c_client *client;
u32 r_sense_uohm;
u32 alert_threshold_ua;
u64 alert_threshold_ua;
bool vrange_high;
};
@ -48,7 +50,7 @@ static int adm1177_write_cmd(struct adm1177_state *st, u8 cmd)
}
static int adm1177_write_alert_thr(struct adm1177_state *st,
u32 alert_threshold_ua)
u64 alert_threshold_ua)
{
u64 val;
int ret;
@ -91,8 +93,8 @@ static int adm1177_read(struct device *dev, enum hwmon_sensor_types type,
*val = div_u64((105840000ull * dummy),
4096 * st->r_sense_uohm);
return 0;
case hwmon_curr_max_alarm:
*val = st->alert_threshold_ua;
case hwmon_curr_max:
*val = div_u64(st->alert_threshold_ua, 1000);
return 0;
default:
return -EOPNOTSUPP;
@ -126,9 +128,10 @@ static int adm1177_write(struct device *dev, enum hwmon_sensor_types type,
switch (type) {
case hwmon_curr:
switch (attr) {
case hwmon_curr_max_alarm:
adm1177_write_alert_thr(st, val);
return 0;
case hwmon_curr_max:
val = clamp_val(val, 0,
div_u64(105840000ULL, st->r_sense_uohm));
return adm1177_write_alert_thr(st, (u64)val * 1000);
default:
return -EOPNOTSUPP;
}
@ -156,7 +159,7 @@ static umode_t adm1177_is_visible(const void *data,
if (st->r_sense_uohm)
return 0444;
return 0;
case hwmon_curr_max_alarm:
case hwmon_curr_max:
if (st->r_sense_uohm)
return 0644;
return 0;
@ -170,7 +173,7 @@ static umode_t adm1177_is_visible(const void *data,
static const struct hwmon_channel_info * const adm1177_info[] = {
HWMON_CHANNEL_INFO(curr,
HWMON_C_INPUT | HWMON_C_MAX_ALARM),
HWMON_C_INPUT | HWMON_C_MAX),
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT),
NULL
@ -192,7 +195,8 @@ static int adm1177_probe(struct i2c_client *client)
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct adm1177_state *st;
u32 alert_threshold_ua;
u64 alert_threshold_ua;
u32 prop;
int ret;
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
@ -208,22 +212,26 @@ static int adm1177_probe(struct i2c_client *client)
if (device_property_read_u32(dev, "shunt-resistor-micro-ohms",
&st->r_sense_uohm))
st->r_sense_uohm = 0;
if (device_property_read_u32(dev, "adi,shutdown-threshold-microamp",
&alert_threshold_ua)) {
if (st->r_sense_uohm)
/*
* set maximum default value from datasheet based on
* shunt-resistor
*/
alert_threshold_ua = div_u64(105840000000,
st->r_sense_uohm);
else
alert_threshold_ua = 0;
if (!device_property_read_u32(dev, "adi,shutdown-threshold-microamp",
&prop)) {
alert_threshold_ua = prop;
} else if (st->r_sense_uohm) {
/*
* set maximum default value from datasheet based on
* shunt-resistor
*/
alert_threshold_ua = div_u64(105840000000ULL,
st->r_sense_uohm);
} else {
alert_threshold_ua = 0;
}
st->vrange_high = device_property_read_bool(dev,
"adi,vrange-high-enable");
if (alert_threshold_ua && st->r_sense_uohm)
adm1177_write_alert_thr(st, alert_threshold_ua);
if (alert_threshold_ua && st->r_sense_uohm) {
ret = adm1177_write_alert_thr(st, alert_threshold_ua);
if (ret)
return ret;
}
ret = adm1177_write_cmd(st, ADM1177_CMD_V_CONT |
ADM1177_CMD_I_CONT |

View File

@ -131,7 +131,7 @@ static int get_temp_target(struct peci_cputemp *priv, enum peci_temp_target_type
*val = priv->temp.target.tjmax;
break;
case crit_hyst_type:
*val = priv->temp.target.tjmax - priv->temp.target.tcontrol;
*val = priv->temp.target.tcontrol;
break;
default:
ret = -EOPNOTSUPP;
@ -319,7 +319,7 @@ static umode_t cputemp_is_visible(const void *data, enum hwmon_sensor_types type
{
const struct peci_cputemp *priv = data;
if (channel > CPUTEMP_CHANNEL_NUMS)
if (channel >= CPUTEMP_CHANNEL_NUMS)
return 0;
if (channel < channel_core)

View File

@ -72,7 +72,8 @@ static int ina233_read_word_data(struct i2c_client *client, int page,
/* Adjust returned value to match VIN coefficients */
/* VIN: 1.25 mV VSHUNT: 2.5 uV LSB */
ret = DIV_ROUND_CLOSEST(ret * 25, 12500);
ret = clamp_val(DIV_ROUND_CLOSEST((s16)ret * 25, 12500),
S16_MIN, S16_MAX) & 0xffff;
break;
default:
ret = -ENODATA;

View File

@ -96,7 +96,15 @@ static ssize_t isl68137_avs_enable_show_page(struct i2c_client *client,
int page,
char *buf)
{
int val = pmbus_read_byte_data(client, page, PMBUS_OPERATION);
int val;
val = pmbus_lock_interruptible(client);
if (val)
return val;
val = pmbus_read_byte_data(client, page, PMBUS_OPERATION);
pmbus_unlock(client);
if (val < 0)
return val;
@ -118,6 +126,10 @@ static ssize_t isl68137_avs_enable_store_page(struct i2c_client *client,
op_val = result ? ISL68137_VOUT_AVS : 0;
rc = pmbus_lock_interruptible(client);
if (rc)
return rc;
/*
* Writes to VOUT setpoint over AVSBus will persist after the VRM is
* switched to PMBus control. Switching back to AVSBus control
@ -129,17 +141,20 @@ static ssize_t isl68137_avs_enable_store_page(struct i2c_client *client,
rc = pmbus_read_word_data(client, page, 0xff,
PMBUS_VOUT_COMMAND);
if (rc < 0)
return rc;
goto unlock;
rc = pmbus_write_word_data(client, page, PMBUS_VOUT_COMMAND,
rc);
if (rc < 0)
return rc;
goto unlock;
}
rc = pmbus_update_byte_data(client, page, PMBUS_OPERATION,
ISL68137_VOUT_AVS, op_val);
unlock:
pmbus_unlock(client);
return (rc < 0) ? rc : count;
}

View File

@ -6,6 +6,7 @@
* Copyright (c) 2012 Guenter Roeck
*/
#include <linux/atomic.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dcache.h>
@ -21,8 +22,8 @@
#include <linux/pmbus.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/of.h>
#include <linux/thermal.h>
#include <linux/workqueue.h>
#include "pmbus.h"
/*
@ -112,6 +113,11 @@ struct pmbus_data {
struct mutex update_lock;
#if IS_ENABLED(CONFIG_REGULATOR)
atomic_t regulator_events[PMBUS_PAGES];
struct work_struct regulator_notify_work;
#endif
bool has_status_word; /* device uses STATUS_WORD register */
int (*read_status)(struct i2c_client *client, int page);
@ -1209,6 +1215,12 @@ static ssize_t pmbus_show_boolean(struct device *dev,
return sysfs_emit(buf, "%d\n", val);
}
static ssize_t pmbus_show_zero(struct device *dev,
struct device_attribute *devattr, char *buf)
{
return sysfs_emit(buf, "0\n");
}
static ssize_t pmbus_show_sensor(struct device *dev,
struct device_attribute *devattr, char *buf)
{
@ -1407,7 +1419,7 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
int reg,
enum pmbus_sensor_classes class,
bool update, bool readonly,
bool convert)
bool writeonly, bool convert)
{
struct pmbus_sensor *sensor;
struct device_attribute *a;
@ -1436,7 +1448,8 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
sensor->data = -ENODATA;
pmbus_dev_attr_init(a, sensor->name,
readonly ? 0444 : 0644,
pmbus_show_sensor, pmbus_set_sensor);
writeonly ? pmbus_show_zero : pmbus_show_sensor,
pmbus_set_sensor);
if (pmbus_add_attribute(data, &a->attr))
return NULL;
@ -1495,8 +1508,10 @@ static int pmbus_add_label(struct pmbus_data *data,
struct pmbus_limit_attr {
u16 reg; /* Limit register */
u16 sbit; /* Alarm attribute status bit */
bool update; /* True if register needs updates */
bool low; /* True if low limit; for limits with compare functions only */
bool readonly:1; /* True if the attribute is read-only */
bool writeonly:1; /* True if the attribute is write-only */
bool update:1; /* True if register needs updates */
bool low:1; /* True if low limit; for limits with compare functions only */
const char *attr; /* Attribute name */
const char *alarm; /* Alarm attribute name */
};
@ -1511,9 +1526,9 @@ struct pmbus_sensor_attr {
u8 nlimit; /* # of limit registers */
enum pmbus_sensor_classes class;/* sensor class */
const char *label; /* sensor label */
bool paged; /* true if paged sensor */
bool update; /* true if update needed */
bool compare; /* true if compare function needed */
bool paged:1; /* true if paged sensor */
bool update:1; /* true if update needed */
bool compare:1; /* true if compare function needed */
u32 func; /* sensor mask */
u32 sfunc; /* sensor status mask */
int sreg; /* status register */
@ -1544,7 +1559,7 @@ static int pmbus_add_limit_attrs(struct i2c_client *client,
curr = pmbus_add_sensor(data, name, l->attr, index,
page, 0xff, l->reg, attr->class,
attr->update || l->update,
false, true);
l->readonly, l->writeonly, true);
if (!curr)
return -ENOMEM;
if (l->sbit && (info->func[page] & attr->sfunc)) {
@ -1584,7 +1599,7 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client,
return ret;
}
base = pmbus_add_sensor(data, name, "input", index, page, phase,
attr->reg, attr->class, true, true, true);
attr->reg, attr->class, true, true, false, true);
if (!base)
return -ENOMEM;
/* No limit and alarm attributes for phase specific sensors */
@ -1707,23 +1722,29 @@ static const struct pmbus_limit_attr vin_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_READ_VIN_AVG,
.update = true,
.readonly = true,
.attr = "average",
}, {
.reg = PMBUS_VIRT_READ_VIN_MIN,
.update = true,
.readonly = true,
.attr = "lowest",
}, {
.reg = PMBUS_VIRT_READ_VIN_MAX,
.update = true,
.readonly = true,
.attr = "highest",
}, {
.reg = PMBUS_VIRT_RESET_VIN_HISTORY,
.writeonly = true,
.attr = "reset_history",
}, {
.reg = PMBUS_MFR_VIN_MIN,
.readonly = true,
.attr = "rated_min",
}, {
.reg = PMBUS_MFR_VIN_MAX,
.readonly = true,
.attr = "rated_max",
},
};
@ -1776,23 +1797,29 @@ static const struct pmbus_limit_attr vout_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_READ_VOUT_AVG,
.update = true,
.readonly = true,
.attr = "average",
}, {
.reg = PMBUS_VIRT_READ_VOUT_MIN,
.update = true,
.readonly = true,
.attr = "lowest",
}, {
.reg = PMBUS_VIRT_READ_VOUT_MAX,
.update = true,
.readonly = true,
.attr = "highest",
}, {
.reg = PMBUS_VIRT_RESET_VOUT_HISTORY,
.writeonly = true,
.attr = "reset_history",
}, {
.reg = PMBUS_MFR_VOUT_MIN,
.readonly = true,
.attr = "rated_min",
}, {
.reg = PMBUS_MFR_VOUT_MAX,
.readonly = true,
.attr = "rated_max",
},
};
@ -1852,20 +1879,25 @@ static const struct pmbus_limit_attr iin_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_READ_IIN_AVG,
.update = true,
.readonly = true,
.attr = "average",
}, {
.reg = PMBUS_VIRT_READ_IIN_MIN,
.update = true,
.readonly = true,
.attr = "lowest",
}, {
.reg = PMBUS_VIRT_READ_IIN_MAX,
.update = true,
.readonly = true,
.attr = "highest",
}, {
.reg = PMBUS_VIRT_RESET_IIN_HISTORY,
.writeonly = true,
.attr = "reset_history",
}, {
.reg = PMBUS_MFR_IIN_MAX,
.readonly = true,
.attr = "rated_max",
},
};
@ -1889,20 +1921,25 @@ static const struct pmbus_limit_attr iout_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_READ_IOUT_AVG,
.update = true,
.readonly = true,
.attr = "average",
}, {
.reg = PMBUS_VIRT_READ_IOUT_MIN,
.update = true,
.readonly = true,
.attr = "lowest",
}, {
.reg = PMBUS_VIRT_READ_IOUT_MAX,
.update = true,
.readonly = true,
.attr = "highest",
}, {
.reg = PMBUS_VIRT_RESET_IOUT_HISTORY,
.writeonly = true,
.attr = "reset_history",
}, {
.reg = PMBUS_MFR_IOUT_MAX,
.readonly = true,
.attr = "rated_max",
},
};
@ -1943,20 +1980,25 @@ static const struct pmbus_limit_attr pin_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_READ_PIN_AVG,
.update = true,
.readonly = true,
.attr = "average",
}, {
.reg = PMBUS_VIRT_READ_PIN_MIN,
.update = true,
.readonly = true,
.attr = "input_lowest",
}, {
.reg = PMBUS_VIRT_READ_PIN_MAX,
.update = true,
.readonly = true,
.attr = "input_highest",
}, {
.reg = PMBUS_VIRT_RESET_PIN_HISTORY,
.writeonly = true,
.attr = "reset_history",
}, {
.reg = PMBUS_MFR_PIN_MAX,
.readonly = true,
.attr = "rated_max",
},
};
@ -1980,20 +2022,25 @@ static const struct pmbus_limit_attr pout_limit_attrs[] = {
}, {
.reg = PMBUS_VIRT_READ_POUT_AVG,
.update = true,
.readonly = true,
.attr = "average",
}, {
.reg = PMBUS_VIRT_READ_POUT_MIN,
.update = true,
.readonly = true,
.attr = "input_lowest",
}, {
.reg = PMBUS_VIRT_READ_POUT_MAX,
.update = true,
.readonly = true,
.attr = "input_highest",
}, {
.reg = PMBUS_VIRT_RESET_POUT_HISTORY,
.writeonly = true,
.attr = "reset_history",
}, {
.reg = PMBUS_MFR_POUT_MAX,
.readonly = true,
.attr = "rated_max",
},
};
@ -2049,18 +2096,23 @@ static const struct pmbus_limit_attr temp_limit_attrs[] = {
.sbit = PB_TEMP_OT_FAULT,
}, {
.reg = PMBUS_VIRT_READ_TEMP_MIN,
.readonly = true,
.attr = "lowest",
}, {
.reg = PMBUS_VIRT_READ_TEMP_AVG,
.readonly = true,
.attr = "average",
}, {
.reg = PMBUS_VIRT_READ_TEMP_MAX,
.readonly = true,
.attr = "highest",
}, {
.reg = PMBUS_VIRT_RESET_TEMP_HISTORY,
.writeonly = true,
.attr = "reset_history",
}, {
.reg = PMBUS_MFR_MAX_TEMP_1,
.readonly = true,
.attr = "rated_max",
},
};
@ -2090,18 +2142,23 @@ static const struct pmbus_limit_attr temp_limit_attrs2[] = {
.sbit = PB_TEMP_OT_FAULT,
}, {
.reg = PMBUS_VIRT_READ_TEMP2_MIN,
.readonly = true,
.attr = "lowest",
}, {
.reg = PMBUS_VIRT_READ_TEMP2_AVG,
.readonly = true,
.attr = "average",
}, {
.reg = PMBUS_VIRT_READ_TEMP2_MAX,
.readonly = true,
.attr = "highest",
}, {
.reg = PMBUS_VIRT_RESET_TEMP2_HISTORY,
.writeonly = true,
.attr = "reset_history",
}, {
.reg = PMBUS_MFR_MAX_TEMP_2,
.readonly = true,
.attr = "rated_max",
},
};
@ -2131,6 +2188,7 @@ static const struct pmbus_limit_attr temp_limit_attrs3[] = {
.sbit = PB_TEMP_OT_FAULT,
}, {
.reg = PMBUS_MFR_MAX_TEMP_3,
.readonly = true,
.attr = "rated_max",
},
};
@ -2214,7 +2272,7 @@ static int pmbus_add_fan_ctrl(struct i2c_client *client,
sensor = pmbus_add_sensor(data, "fan", "target", index, page,
0xff, PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN,
false, false, true);
false, false, false, true);
if (!sensor)
return -ENOMEM;
@ -2225,14 +2283,14 @@ static int pmbus_add_fan_ctrl(struct i2c_client *client,
sensor = pmbus_add_sensor(data, "pwm", NULL, index, page,
0xff, PMBUS_VIRT_PWM_1 + id, PSC_PWM,
false, false, true);
false, false, false, true);
if (!sensor)
return -ENOMEM;
sensor = pmbus_add_sensor(data, "pwm", "enable", index, page,
0xff, PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM,
true, false, false);
true, false, false, false);
if (!sensor)
return -ENOMEM;
@ -2274,7 +2332,7 @@ static int pmbus_add_fan_attributes(struct i2c_client *client,
if (pmbus_add_sensor(data, "fan", "input", index,
page, 0xff, pmbus_fan_registers[f],
PSC_FAN, true, true, true) == NULL)
PSC_FAN, true, true, false, true) == NULL)
return -ENOMEM;
/* Fan control */
@ -3176,12 +3234,19 @@ static int pmbus_regulator_get_voltage(struct regulator_dev *rdev)
.class = PSC_VOLTAGE_OUT,
.convert = true,
};
int ret;
mutex_lock(&data->update_lock);
s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT);
if (s.data < 0)
return s.data;
if (s.data < 0) {
ret = s.data;
goto unlock;
}
return (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */
ret = (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */
unlock:
mutex_unlock(&data->update_lock);
return ret;
}
static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
@ -3198,16 +3263,22 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
};
int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */
int low, high;
int ret;
*selector = 0;
mutex_lock(&data->update_lock);
low = pmbus_regulator_get_low_margin(client, s.page);
if (low < 0)
return low;
if (low < 0) {
ret = low;
goto unlock;
}
high = pmbus_regulator_get_high_margin(client, s.page);
if (high < 0)
return high;
if (high < 0) {
ret = high;
goto unlock;
}
/* Make sure we are within margins */
if (low > val)
@ -3217,7 +3288,10 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
val = pmbus_data2reg(data, &s, val);
return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
ret = _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
unlock:
mutex_unlock(&data->update_lock);
return ret;
}
static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
@ -3227,6 +3301,7 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_data *data = i2c_get_clientdata(client);
int val, low, high;
int ret;
if (data->flags & PMBUS_VOUT_PROTECTED)
return 0;
@ -3239,18 +3314,29 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
val = DIV_ROUND_CLOSEST(rdev->desc->min_uV +
(rdev->desc->uV_step * selector), 1000); /* convert to mV */
mutex_lock(&data->update_lock);
low = pmbus_regulator_get_low_margin(client, rdev_get_id(rdev));
if (low < 0)
return low;
if (low < 0) {
ret = low;
goto unlock;
}
high = pmbus_regulator_get_high_margin(client, rdev_get_id(rdev));
if (high < 0)
return high;
if (high < 0) {
ret = high;
goto unlock;
}
if (val >= low && val <= high)
return val * 1000; /* unit is uV */
if (val >= low && val <= high) {
ret = val * 1000; /* unit is uV */
goto unlock;
}
return 0;
ret = 0;
unlock:
mutex_unlock(&data->update_lock);
return ret;
}
const struct regulator_ops pmbus_regulator_ops = {
@ -3281,12 +3367,42 @@ int pmbus_regulator_init_cb(struct regulator_dev *rdev,
}
EXPORT_SYMBOL_NS_GPL(pmbus_regulator_init_cb, "PMBUS");
static void pmbus_regulator_notify_work_cancel(void *data)
{
struct pmbus_data *pdata = data;
cancel_work_sync(&pdata->regulator_notify_work);
}
static void pmbus_regulator_notify_worker(struct work_struct *work)
{
struct pmbus_data *data =
container_of(work, struct pmbus_data, regulator_notify_work);
int i, j;
for (i = 0; i < data->info->pages; i++) {
int event;
event = atomic_xchg(&data->regulator_events[i], 0);
if (!event)
continue;
for (j = 0; j < data->info->num_regulators; j++) {
if (i == rdev_get_id(data->rdevs[j])) {
regulator_notifier_call_chain(data->rdevs[j],
event, NULL);
break;
}
}
}
}
static int pmbus_regulator_register(struct pmbus_data *data)
{
struct device *dev = data->dev;
const struct pmbus_driver_info *info = data->info;
const struct pmbus_platform_data *pdata = dev_get_platdata(dev);
int i;
int i, ret;
data->rdevs = devm_kzalloc(dev, sizeof(struct regulator_dev *) * info->num_regulators,
GFP_KERNEL);
@ -3310,19 +3426,19 @@ static int pmbus_regulator_register(struct pmbus_data *data)
info->reg_desc[i].name);
}
INIT_WORK(&data->regulator_notify_work, pmbus_regulator_notify_worker);
ret = devm_add_action_or_reset(dev, pmbus_regulator_notify_work_cancel, data);
if (ret)
return ret;
return 0;
}
static void pmbus_regulator_notify(struct pmbus_data *data, int page, int event)
{
int j;
for (j = 0; j < data->info->num_regulators; j++) {
if (page == rdev_get_id(data->rdevs[j])) {
regulator_notifier_call_chain(data->rdevs[j], event, NULL);
break;
}
}
atomic_or(event, &data->regulator_events[page]);
schedule_work(&data->regulator_notify_work);
}
#else
static int pmbus_regulator_register(struct pmbus_data *data)