hwmon fixes for v7.3-rc3

* Core
 
   - Fix potential UAF in pec_store
 
   - Ensure that 'dev' passed to hwmon_notify_event() is a hwmon device
 
   - Document hwmon_notify_event()
 
 * applesmc: Fix key backlight workqueue leak on register failure
 
 * aspeed-pwm-tacho: Propagate reset deassert errors
 
 * asus_rog_ryujin: HID report fixes
 
 * chipcap2: Fix channels in humidity alarm notifications
 
 * corsair-cpro: debugfs fixes
 
 * gpd-fan: Documentation: replace full-width colon by a standard ASCII colon
 
 * gpio-fan: Take fan_data->lock in gpio_fan_shutdown(), and fix
   use-after-free in alarm work
 
 * ina2xx: Fix in0 and curr1 alarm handling, and acquire hwmon_lock in
   shunt_resistor_show()
 
 * ltc4282: Fully initializeclk_init_data
 
 * mcp9982: Propagate one-shot polling errors
 
 * nct6694: Do not expose enable on DTIN temperature channels
 
 * PMBus core: Clear generic status alarms with CLEAR_FAULTS
 
 * sht4x: Fix return value from heater_enable_store(), and add missing locks
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmqi9qsACgkQyx8mb86f
 mYGWTQ/9Gb8TEBQIK5thUZqmJ/MQ1o23brRrsQmRn1zuCxlgo5CnLXD3ym/icadR
 c98J2MTJNCf+03lahMDMEwuAOmymuiu0uGHOhiXKIRK/ermIercPZKOhzu3EsVMS
 f+sDZbjyjEir/5SxqD6QGuuBaTLOSy1HYCfp/YKWyW5TOj8Gf+8VDmgdClBM9Y2m
 YlJmlhZi54XZWYSeIrozq4Sw8a2utmM7PYvlQiiV6a/b/FvYd1vsWxe5l8XoQT8n
 b0WVtFS/qnICFpt7bon6eq6QGRZDgn8rFphv3zBc9Wtoxqnx5rKtlrXQclfjhmtI
 /ekSOdgxr2wMzMBL+mB6l3k4oD4J5yt43/KCR/Qap4JGPNG3NOlxEbUJ+jGN2C96
 kTuDuxq/HuoJWmWgiotB3bAoTm+izvJkO4hpj57X5HCaQYC8pmlQL1RXWy6d/tAS
 jPiC/Xd2Eo01EWgIT8JT1RSHUPV9dI/x4uU+My6w8++B2X4y5eqzFQYBO5EVu00W
 1HDThX9Nqp0mI/09IliHp+MexXJO2NUj8SbRn2TmPM6cy4o4RxwBfdx+ZMJazert
 0adW4rG3IF2xiRuc8yxaQA8toUK5hSLaknZ4SoxTjwy9kXjRCrFMU4TCJnJUUYY6
 CWQWaHCSs2IPfsDdy7blnL7uT810/9GwzLT8nRAKAe3s86QCE2k=
 =omwO
 -----END PGP SIGNATURE-----

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

Pull hwmon fixes from Guenter Roeck:

 - Core
     - Fix potential UAF in pec_store
     - Ensure that 'dev' passed to hwmon_notify_event() is a hwmon device
     - Document hwmon_notify_event()

 - applesmc: Fix key backlight workqueue leak on register failure

 - aspeed-pwm-tacho: Propagate reset deassert errors

 - asus_rog_ryujin: HID report fixes

 - chipcap2: Fix channels in humidity alarm notifications

 - corsair-cpro: debugfs fixes

 - gpd-fan: Documentation: replace full-width colon by a standard ASCII
   colon

 - gpio-fan: Take fan_data->lock in gpio_fan_shutdown(), and fix
   use-after-free in alarm work

 - ina2xx: Fix in0 and curr1 alarm handling, and acquire hwmon_lock in
   shunt_resistor_show()

 - ltc4282: Fully initializeclk_init_data

 - mcp9982: Propagate one-shot polling errors

 - nct6694: Do not expose enable on DTIN temperature channels

 - PMBus core: Clear generic status alarms with CLEAR_FAULTS

 - sht4x: Fix return value from heater_enable_store(), and add missing
   locks

* tag 'hwmon-for-v7.3-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (24 commits)
  hwmon: (nct6694) do not expose enable on DTIN temperature channels
  hwmon: (asus_rog_ryujin) Synchronize HID command and report handling
  hwmon: (asus_rog_ryujin) Validate HID report lengths
  hwmon: (corsair-cpro) Remove debugfs entries when probe fails
  hwmon: (aspeed-pwm-tacho) Propagate reset deassert errors
  hwmon: (gpio-fan) take fan_data->lock in gpio_fan_shutdown()
  hwmon: (corsair-cpro) Create debugfs entries after hwmon registration
  hwmon: (pmbus) Clear generic status alarms with CLEAR_FAULTS
  hwmon: (chipcap2) fix channels in humidity alarm notifications
  hwmon: (applesmc) fix key backlight workqueue leak on register failure
  hwmon: (sht4x) Fix return value from heater_enable_store()
  hwmon: (sht4x) Add missing locks
  hwmon: (yogafan) fix non-kernel-doc comment
  Documentation: hwmon: replace full-width colon by a standard ASCII colon
  hwmon: (ina2xx) Decouple in0 and curr1 alarms
  hwmon: (ina2xx) Replace masks with enum in alert functions
  hwmon: (ina2xx) Parameterize ina2xx_data in ina226_alert_read()
  hwmon: Ensure that 'dev' passed to hwmon_notify_event() is a hwmon device
  hwmon: (ina2xx) Acquire hwmon_lock in shunt_resistor_show()
  hwmon: Fix potential UAF in pec_store
  ...
This commit is contained in:
Linus Torvalds 2026-09-10 14:37:53 -07:00
commit aa416593f3
16 changed files with 267 additions and 84 deletions

View File

@ -67,7 +67,7 @@ pwm1_enable
at full speed. Write "1" to set to manual, write "2" to let the EC control
decide fan speed. Read this attribute to see current status.
NBIn consideration of the safety of the device, when setting to manual mode,
NB: In consideration of the safety of the device, when setting to manual mode,
the pwm speed will be set to the maximum value (255) by default. You can set
a different value by writing pwm1 later.

View File

@ -42,6 +42,9 @@ register/unregister functions::
char *devm_hwmon_sanitize_name(struct device *dev, const char *name);
int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel);
void hwmon_lock(struct device *dev);
void hwmon_unlock(struct device *dev);
@ -90,6 +93,18 @@ implemented in the driver, or debugfs functions, hwmon_lock() and hwmon_unlock()
can be used to ensure that calls to those functions are serialized. Those
functions also support guard() and scoped_guard() variants.
Drivers can call hwmon_notify_event() to notify userspace and the thermal
subsystem when a hardware monitoring event (such as an alarm or a fault
condition) occurs or clears. The parameters are the hwmon device, the sensor
type, the attribute identifier associated with the event (such as
hwmon_temp_max_alarm or hwmon_fan_fault), and the sensor channel number.
hwmon_notify_event() generates a sysfs event (calling sysfs_notify()) and a
udev event with the attribute name passed in the NAME environment property
(e.g., "NAME=temp1_max_alarm"). If the event is for a temperature sensor and
the sensor is attached to a thermal zone, it also notifies the thermal
subsystem to update the thermal zone. hwmon_notify_event() returns 0 on
success or a negative error code on failure.
Using devm_hwmon_device_register_with_info()
--------------------------------------------

View File

@ -1128,12 +1128,17 @@ static void applesmc_release_light_sensor(void)
static int applesmc_create_key_backlight(void)
{
int ret;
if (!smcreg.has_key_backlight)
return 0;
applesmc_led_wq = create_singlethread_workqueue("applesmc-led");
if (!applesmc_led_wq)
return -ENOMEM;
return led_classdev_register(&pdev->dev, &applesmc_backlight);
ret = led_classdev_register(&pdev->dev, &applesmc_backlight);
if (ret)
destroy_workqueue(applesmc_led_wq);
return ret;
}
static void applesmc_release_key_backlight(void)

View File

@ -934,7 +934,9 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev)
"missing or invalid reset controller device tree entry");
return PTR_ERR(priv->rst);
}
reset_control_deassert(priv->rst);
ret = reset_control_deassert(priv->rst);
if (ret)
return ret;
ret = devm_add_action_or_reset(dev, aspeed_pwm_tacho_remove, priv);
if (ret)

View File

@ -184,6 +184,7 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd
static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length,
struct completion *status_completion)
{
unsigned long flags;
int ret;
/*
@ -191,9 +192,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i
* completion. Reinit is done because hidraw could have triggered
* the raw event parsing and marked the passed in completion as done.
*/
spin_lock_bh(&priv->status_report_request_lock);
spin_lock_irqsave(&priv->status_report_request_lock, flags);
reinit_completion(status_completion);
spin_unlock_bh(&priv->status_report_request_lock);
spin_unlock_irqrestore(&priv->status_report_request_lock, flags);
/* Send command for getting data */
ret = rog_ryujin_write_expanded(priv, cmd, cmd_length);
@ -421,11 +422,19 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo
int size)
{
struct rog_ryujin_data *priv = hid_get_drvdata(hdev);
unsigned long flags;
if (data[0] != RYUJIN_CMD_PREFIX)
if (size < 2 || data[0] != RYUJIN_CMD_PREFIX)
return 0;
spin_lock_irqsave(&priv->status_report_request_lock, flags);
if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) {
if (size <= priv->info->temp_offset + 1 ||
size <= priv->info->pump_speed_offset + 1 ||
size <= priv->info->fan_speed_offset + 1)
goto unlock;
/* Received coolant temp and speeds of pump and internal fan */
priv->temp_input[0] = data[priv->info->temp_offset] * 1000 +
data[priv->info->temp_offset + 1] * 100;
@ -437,6 +446,9 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo
if (!completion_done(&priv->cooler_status_received))
complete_all(&priv->cooler_status_received);
} else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) {
if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1)
goto unlock;
/* Received speeds of four fans attached to the controller */
priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1);
priv->speed_input[3] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_2);
@ -446,6 +458,9 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo
if (!completion_done(&priv->controller_status_received))
complete_all(&priv->controller_status_received);
} else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) {
if (size <= RYUJIN_INTERNAL_FAN_DUTY)
goto unlock;
/* Received report for pump and internal fan duties (in %) */
if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) {
/*
@ -463,7 +478,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo
* We're expecting a report, so parse it.
*/
goto read_cooler_duty;
return 0;
goto unlock;
}
read_cooler_duty:
priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]);
@ -472,6 +487,9 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo
if (!completion_done(&priv->cooler_duty_received))
complete_all(&priv->cooler_duty_received);
} else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) {
if (size <= RYUJIN_CONTROLLER_DUTY)
goto unlock;
/* Received report for controller duty for fans (in PWM) */
if (data[RYUJIN_CONTROLLER_DUTY] == 0) {
/*
@ -489,7 +507,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo
* We're expecting a report, so parse it.
*/
goto read_controller_duty;
return 0;
goto unlock;
}
read_controller_duty:
priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY];
@ -498,6 +516,8 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo
complete_all(&priv->controller_duty_received);
}
unlock:
spin_unlock_irqrestore(&priv->status_report_request_lock, flags);
return 0;
}

View File

@ -92,11 +92,6 @@ struct cc2_data {
bool process_irqs;
};
enum cc2_chan_addr {
CC2_CHAN_TEMP = 0,
CC2_CHAN_HUMIDITY,
};
/* %RH as a per cent mille from a register value */
static long cc2_rh_convert(u16 data)
{
@ -499,7 +494,7 @@ static irqreturn_t cc2_low_interrupt(int irq, void *data)
if (cc2->process_irqs) {
hwmon_notify_event(cc2->hwmon, hwmon_humidity,
hwmon_humidity_min_alarm, CC2_CHAN_HUMIDITY);
hwmon_humidity_min_alarm, 0);
cc2->rh_alarm.low_alarm = true;
}
@ -512,7 +507,7 @@ static irqreturn_t cc2_high_interrupt(int irq, void *data)
if (cc2->process_irqs) {
hwmon_notify_event(cc2->hwmon, hwmon_humidity,
hwmon_humidity_max_alarm, CC2_CHAN_HUMIDITY);
hwmon_humidity_max_alarm, 0);
cc2->rh_alarm.high_alarm = true;
}

View File

@ -566,21 +566,18 @@ static int bootloader_show(struct seq_file *seqf, void *unused)
}
DEFINE_SHOW_ATTRIBUTE(bootloader);
static void ccp_debugfs_init(struct ccp_device *ccp)
static void ccp_debugfs_init(struct ccp_device *ccp, bool fw_valid, bool bl_valid)
{
char name[32];
int ret;
scnprintf(name, sizeof(name), "corsaircpro-%s", dev_name(&ccp->hdev->dev));
ccp->debugfs = debugfs_create_dir(name, NULL);
ret = get_fw_version(ccp);
if (!ret)
if (fw_valid)
debugfs_create_file("firmware_version", 0444,
ccp->debugfs, ccp, &firmware_fops);
ret = get_bl_version(ccp);
if (!ret)
if (bl_valid)
debugfs_create_file("bootloader_version", 0444,
ccp->debugfs, ccp, &bootloader_fops);
}
@ -588,6 +585,7 @@ static void ccp_debugfs_init(struct ccp_device *ccp)
static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct ccp_device *ccp;
bool fw_valid, bl_valid;
int ret;
ccp = devm_kzalloc(&hdev->dev, sizeof(*ccp), GFP_KERNEL);
@ -632,17 +630,27 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (ret)
goto out_hw_close;
ccp_debugfs_init(ccp);
/*
* Query the versions before registering the hwmon device: they send
* USB commands without holding ccp->mutex, which is only safe while
* nothing else can call send_usb_cmd().
*/
fw_valid = !get_fw_version(ccp);
bl_valid = !get_bl_version(ccp);
ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro",
ccp, &ccp_chip_info, NULL);
if (IS_ERR(ccp->hwmon_dev)) {
ret = PTR_ERR(ccp->hwmon_dev);
goto out_hw_close;
goto out_debugfs_remove;
}
ccp_debugfs_init(ccp, fw_valid, bl_valid);
return 0;
out_debugfs_remove:
debugfs_remove_recursive(ccp->debugfs);
out_hw_close:
hid_hw_close(hdev);
hid_device_io_stop(hdev);

View File

@ -12,6 +12,7 @@
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/devm-helpers.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/kstrtox.h>
@ -84,6 +85,7 @@ static DEVICE_ATTR_RO(fan1_alarm);
static int fan_alarm_init(struct gpio_fan_data *fan_data)
{
int alarm_irq;
int err;
struct device *dev = fan_data->dev;
/*
@ -94,7 +96,11 @@ static int fan_alarm_init(struct gpio_fan_data *fan_data)
if (alarm_irq <= 0)
return 0;
INIT_WORK(&fan_data->alarm_work, fan_alarm_notify);
err = devm_work_autocancel(dev, &fan_data->alarm_work,
fan_alarm_notify);
if (err)
return err;
irq_set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH);
return devm_request_irq(dev, alarm_irq, fan_alarm_irq_handler,
IRQF_SHARED, "GPIO fan alarm", fan_data);
@ -606,8 +612,11 @@ static void gpio_fan_shutdown(struct platform_device *pdev)
{
struct gpio_fan_data *fan_data = platform_get_drvdata(pdev);
if (fan_data->gpios)
if (fan_data->gpios) {
mutex_lock(&fan_data->lock);
set_fan_speed(fan_data, 0);
mutex_unlock(&fan_data->lock);
}
}
static int gpio_fan_runtime_suspend(struct device *dev)

View File

@ -318,6 +318,11 @@ static int hwmon_attr_base(enum hwmon_sensor_types type)
return 1;
}
static bool is_hwmon_device(struct device *dev)
{
return dev->class == &hwmon_class;
}
#if IS_REACHABLE(CONFIG_I2C)
/*
@ -338,7 +343,7 @@ static int hwmon_attr_base(enum hwmon_sensor_types type)
static int hwmon_match_device(struct device *dev, const void *data)
{
return dev->class == &hwmon_class;
return is_hwmon_device(dev);
}
static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy,
@ -371,18 +376,17 @@ static ssize_t pec_store(struct device *dev, const struct device_attribute *deva
* handling is not required.
*/
hwdev = to_hwmon_device(hdev);
guard(mutex)(&hwdev->lock);
if (hwdev->chip->ops->write) {
err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val);
if (err && err != -EOPNOTSUPP)
goto put;
scoped_guard(mutex, &hwdev->lock) {
if (hwdev->chip->ops->write) {
err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val);
if (err && err != -EOPNOTSUPP)
goto put;
}
if (!val)
client->flags &= ~I2C_CLIENT_PEC;
else
client->flags |= I2C_CLIENT_PEC;
}
if (!val)
client->flags &= ~I2C_CLIENT_PEC;
else
client->flags |= I2C_CLIENT_PEC;
err = count;
put:
put_device(hdev);
@ -782,6 +786,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
const char *template;
int base;
if (WARN(!is_hwmon_device(dev), "%s is not a hardware monitoring device\n",
dev_name(dev)))
return -EINVAL;
if (type >= ARRAY_SIZE(__templates))
return -EINVAL;
if (attr >= __templates_size[type])

View File

@ -8,6 +8,7 @@
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/device.h>
@ -129,6 +130,17 @@ enum ina2xx_ids {
sy24655
};
enum ina2xx_alert_type {
INA2XX_ALERT_NONE,
INA2XX_ALERT_CURRENT_LOW,
INA2XX_ALERT_CURRENT_HIGH,
INA2XX_ALERT_POWER_HIGH,
INA2XX_ALERT_BUS_VOLTAGE_LOW,
INA2XX_ALERT_BUS_VOLTAGE_HIGH,
INA2XX_ALERT_SHUNT_VOLTAGE_LOW,
INA2XX_ALERT_SHUNT_VOLTAGE_HIGH,
};
struct ina2xx_config {
u16 config_default;
bool has_alerts; /* chip supports alerts and limits */
@ -148,6 +160,7 @@ struct ina2xx_data {
const struct ina2xx_config *config;
enum ina2xx_ids chip;
enum ina2xx_alert_type active_alert;
long rshunt;
long current_lsb_uA;
long power_lsb_uW;
@ -428,16 +441,78 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val)
}
}
static int ina226_alert_limit_read(struct ina2xx_data *data, u32 mask, int reg, long *val)
static u32 ina2xx_alert_type_to_mask(enum ina2xx_alert_type alert)
{
switch (alert) {
case INA2XX_ALERT_CURRENT_LOW:
case INA2XX_ALERT_SHUNT_VOLTAGE_LOW:
return INA226_SHUNT_UNDER_VOLTAGE_MASK;
case INA2XX_ALERT_CURRENT_HIGH:
case INA2XX_ALERT_SHUNT_VOLTAGE_HIGH:
return INA226_SHUNT_OVER_VOLTAGE_MASK;
case INA2XX_ALERT_BUS_VOLTAGE_LOW:
return INA226_BUS_UNDER_VOLTAGE_MASK;
case INA2XX_ALERT_BUS_VOLTAGE_HIGH:
return INA226_BUS_OVER_VOLTAGE_MASK;
case INA2XX_ALERT_POWER_HIGH:
return INA226_POWER_OVER_LIMIT_MASK;
case INA2XX_ALERT_NONE:
return 0;
default:
/* programmer error */
WARN_ON_ONCE(1);
return 0;
}
}
static enum ina2xx_alert_type ina2xx_mask_to_alert_type(u32 mask)
{
int top_bit = fls(mask & INA226_ALERT_CONFIG_MASK);
if (!top_bit)
return INA2XX_ALERT_NONE;
/*
* Multiple bits may be set, with the highest-set function taking
* precedence according to the datasheet. Shunt voltage masks are
* assumed to map to voltage monitoring rather than current monitoring,
* since the latter isn't directly implemented in the hardware.
*/
switch (BIT(top_bit - 1)) {
case INA226_SHUNT_OVER_VOLTAGE_MASK:
return INA2XX_ALERT_SHUNT_VOLTAGE_HIGH;
case INA226_SHUNT_UNDER_VOLTAGE_MASK:
return INA2XX_ALERT_SHUNT_VOLTAGE_LOW;
case INA226_BUS_OVER_VOLTAGE_MASK:
return INA2XX_ALERT_BUS_VOLTAGE_HIGH;
case INA226_BUS_UNDER_VOLTAGE_MASK:
return INA2XX_ALERT_BUS_VOLTAGE_LOW;
case INA226_POWER_OVER_LIMIT_MASK:
return INA2XX_ALERT_POWER_HIGH;
default:
return INA2XX_ALERT_NONE;
}
}
static int ina226_alert_limit_read(struct ina2xx_data *data, enum ina2xx_alert_type alert,
int reg, long *val)
{
struct regmap *regmap = data->regmap;
int regval;
u32 mask;
int ret;
/* Avoid nonzero reads from inactive alerts caused by shared limit register */
if (data->active_alert != alert) {
*val = 0;
return 0;
}
ret = regmap_read(regmap, INA226_MASK_ENABLE, &regval);
if (ret)
return ret;
mask = ina2xx_alert_type_to_mask(alert);
if (regval & mask) {
ret = regmap_read(regmap, INA226_ALERT_LIMIT, &regval);
if (ret)
@ -449,9 +524,11 @@ static int ina226_alert_limit_read(struct ina2xx_data *data, u32 mask, int reg,
return 0;
}
static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, int reg, long val)
static int ina226_alert_limit_write(struct ina2xx_data *data, enum ina2xx_alert_type alert,
int reg, long val)
{
struct regmap *regmap = data->regmap;
u32 mask;
int ret;
if (val < 0)
@ -466,15 +543,22 @@ static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, int reg,
INA226_ALERT_CONFIG_MASK, 0);
if (ret < 0)
return ret;
data->active_alert = INA2XX_ALERT_NONE;
ret = regmap_write(regmap, INA226_ALERT_LIMIT,
ina226_alert_to_reg(data, reg, val));
if (ret < 0)
return ret;
if (val)
return regmap_update_bits(regmap, INA226_MASK_ENABLE,
INA226_ALERT_CONFIG_MASK, mask);
if (val) {
mask = ina2xx_alert_type_to_mask(alert);
ret = regmap_update_bits(regmap, INA226_MASK_ENABLE,
INA226_ALERT_CONFIG_MASK, mask);
if (ret < 0)
return ret;
data->active_alert = alert;
}
return 0;
}
@ -498,15 +582,26 @@ static int ina2xx_chip_read(struct device *dev, u32 attr, long *val)
return 0;
}
static int ina226_alert_read(struct regmap *regmap, u32 mask, long *val)
static int ina226_alert_read(struct ina2xx_data *data, enum ina2xx_alert_type alert, long *val)
{
unsigned int regval;
u32 mask;
int ret;
ret = regmap_read_bypassed(regmap, INA226_MASK_ENABLE, &regval);
/*
* With alert latching, reading alerts from hardware also clears the
* alert, so return early if the alert is inactive.
*/
if (data->active_alert != alert) {
*val = 0;
return 0;
}
ret = regmap_read_bypassed(data->regmap, INA226_MASK_ENABLE, &regval);
if (ret)
return ret;
mask = ina2xx_alert_type_to_mask(alert);
*val = (regval & mask) && (regval & INA226_ALERT_FUNCTION_FLAG);
return 0;
@ -515,10 +610,10 @@ static int ina226_alert_read(struct regmap *regmap, u32 mask, long *val)
static int ina2xx_in_read(struct device *dev, u32 attr, int channel, long *val)
{
int voltage_reg = channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE;
u32 under_voltage_mask = channel ? INA226_BUS_UNDER_VOLTAGE_MASK
: INA226_SHUNT_UNDER_VOLTAGE_MASK;
u32 over_voltage_mask = channel ? INA226_BUS_OVER_VOLTAGE_MASK
: INA226_SHUNT_OVER_VOLTAGE_MASK;
enum ina2xx_alert_type under_voltage_alert = channel ? INA2XX_ALERT_BUS_VOLTAGE_LOW
: INA2XX_ALERT_SHUNT_VOLTAGE_LOW;
enum ina2xx_alert_type over_voltage_alert = channel ? INA2XX_ALERT_BUS_VOLTAGE_HIGH
: INA2XX_ALERT_SHUNT_VOLTAGE_HIGH;
struct ina2xx_data *data = dev_get_drvdata(dev);
struct regmap *regmap = data->regmap;
unsigned int regval;
@ -532,15 +627,15 @@ static int ina2xx_in_read(struct device *dev, u32 attr, int channel, long *val)
*val = ina2xx_get_value(data, voltage_reg, regval);
break;
case hwmon_in_lcrit:
return ina226_alert_limit_read(data, under_voltage_mask,
return ina226_alert_limit_read(data, under_voltage_alert,
voltage_reg, val);
case hwmon_in_crit:
return ina226_alert_limit_read(data, over_voltage_mask,
return ina226_alert_limit_read(data, over_voltage_alert,
voltage_reg, val);
case hwmon_in_lcrit_alarm:
return ina226_alert_read(regmap, under_voltage_mask, val);
return ina226_alert_read(data, under_voltage_alert, val);
case hwmon_in_crit_alarm:
return ina226_alert_read(regmap, over_voltage_mask, val);
return ina226_alert_read(data, over_voltage_alert, val);
default:
return -EOPNOTSUPP;
}
@ -594,10 +689,10 @@ static int ina2xx_power_read(struct device *dev, u32 attr, long *val)
case hwmon_power_average:
return sy24655_average_power_read(data, SY24655_EIN, val);
case hwmon_power_crit:
return ina226_alert_limit_read(data, INA226_POWER_OVER_LIMIT_MASK,
return ina226_alert_limit_read(data, INA2XX_ALERT_POWER_HIGH,
INA2XX_POWER, val);
case hwmon_power_crit_alarm:
return ina226_alert_read(data->regmap, INA226_POWER_OVER_LIMIT_MASK, val);
return ina226_alert_read(data, INA2XX_ALERT_POWER_HIGH, val);
default:
return -EOPNOTSUPP;
}
@ -633,15 +728,15 @@ static int ina2xx_curr_read(struct device *dev, u32 attr, long *val)
*val = ina2xx_get_value(data, INA2XX_CURRENT, regval);
return 0;
case hwmon_curr_lcrit:
return ina226_alert_limit_read(data, INA226_SHUNT_UNDER_VOLTAGE_MASK,
return ina226_alert_limit_read(data, INA2XX_ALERT_CURRENT_LOW,
INA2XX_CURRENT, val);
case hwmon_curr_crit:
return ina226_alert_limit_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK,
return ina226_alert_limit_read(data, INA2XX_ALERT_CURRENT_HIGH,
INA2XX_CURRENT, val);
case hwmon_curr_lcrit_alarm:
return ina226_alert_read(regmap, INA226_SHUNT_UNDER_VOLTAGE_MASK, val);
return ina226_alert_read(data, INA2XX_ALERT_CURRENT_LOW, val);
case hwmon_curr_crit_alarm:
return ina226_alert_read(regmap, INA226_SHUNT_OVER_VOLTAGE_MASK, val);
return ina226_alert_read(data, INA2XX_ALERT_CURRENT_HIGH, val);
default:
return -EOPNOTSUPP;
}
@ -685,12 +780,12 @@ static int ina2xx_in_write(struct device *dev, u32 attr, int channel, long val)
switch (attr) {
case hwmon_in_lcrit:
return ina226_alert_limit_write(data,
channel ? INA226_BUS_UNDER_VOLTAGE_MASK : INA226_SHUNT_UNDER_VOLTAGE_MASK,
channel ? INA2XX_ALERT_BUS_VOLTAGE_LOW : INA2XX_ALERT_SHUNT_VOLTAGE_LOW,
channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE,
val);
case hwmon_in_crit:
return ina226_alert_limit_write(data,
channel ? INA226_BUS_OVER_VOLTAGE_MASK : INA226_SHUNT_OVER_VOLTAGE_MASK,
channel ? INA2XX_ALERT_BUS_VOLTAGE_HIGH : INA2XX_ALERT_SHUNT_VOLTAGE_HIGH,
channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE,
val);
default:
@ -705,7 +800,7 @@ static int ina2xx_power_write(struct device *dev, u32 attr, long val)
switch (attr) {
case hwmon_power_crit:
return ina226_alert_limit_write(data, INA226_POWER_OVER_LIMIT_MASK,
return ina226_alert_limit_write(data, INA2XX_ALERT_POWER_HIGH,
INA2XX_POWER, val);
default:
return -EOPNOTSUPP;
@ -719,10 +814,10 @@ static int ina2xx_curr_write(struct device *dev, u32 attr, long val)
switch (attr) {
case hwmon_curr_lcrit:
return ina226_alert_limit_write(data, INA226_SHUNT_UNDER_VOLTAGE_MASK,
return ina226_alert_limit_write(data, INA2XX_ALERT_CURRENT_LOW,
INA2XX_CURRENT, val);
case hwmon_curr_crit:
return ina226_alert_limit_write(data, INA226_SHUNT_OVER_VOLTAGE_MASK,
return ina226_alert_limit_write(data, INA2XX_ALERT_CURRENT_HIGH,
INA2XX_CURRENT, val);
default:
return -EOPNOTSUPP;
@ -883,8 +978,12 @@ static ssize_t shunt_resistor_show(struct device *dev,
struct device_attribute *da, char *buf)
{
struct ina2xx_data *data = dev_get_drvdata(dev);
long rshunt;
return sysfs_emit(buf, "%li\n", data->rshunt);
scoped_guard(hwmon_lock, dev) {
rshunt = data->rshunt;
}
return sysfs_emit(buf, "%li\n", rshunt);
}
static ssize_t shunt_resistor_store(struct device *dev,
@ -940,6 +1039,16 @@ static int ina2xx_init(struct device *dev, struct ina2xx_data *data)
if (data->config->has_alerts) {
bool active_high = device_property_read_bool(dev, "ti,alert-polarity-active-high");
unsigned int mask_enable;
/*
* Infer active alert from MASK_ENABLE in case it's already
* configured (e.g., by a past probe or firmware)
*/
ret = regmap_read(regmap, INA226_MASK_ENABLE, &mask_enable);
if (ret < 0)
return ret;
data->active_alert = ina2xx_mask_to_alert_type(mask_enable);
regmap_update_bits(regmap, INA226_MASK_ENABLE,
INA226_ALERT_LATCH_ENABLE | INA226_ALERT_POLARITY,

View File

@ -1106,7 +1106,7 @@ static const struct clk_ops ltc4282_ops = {
static int ltc428_clk_provider_setup(struct ltc4282_state *st,
struct device *dev)
{
struct clk_init_data init;
struct clk_init_data init = {};
int ret;
if (!IS_ENABLED(CONFIG_COMMON_CLK))

View File

@ -395,6 +395,8 @@ static int mcp9982_read(struct device *dev, enum hwmon_sensor_types type, u32 at
reg_status, !(reg_status & MCP9982_STATUS_BUSY),
MCP9982_WAKE_UP_TIME_US,
MCP9982_WAKE_UP_TIME_US * 10);
if (ret)
return ret;
break;
}
break;

View File

@ -159,6 +159,9 @@ static inline s8 temp_to_reg(long val)
#define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE | \
HWMON_T_MAX | HWMON_T_MAX_HYST | \
HWMON_T_MAX_ALARM)
#define NCT6694_HWMON_DTIN_CONFIG (HWMON_T_INPUT | \
HWMON_T_MAX | HWMON_T_MAX_HYST | \
HWMON_T_MAX_ALARM)
#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE | \
HWMON_F_MIN | HWMON_F_MIN_ALARM)
#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE | \
@ -193,22 +196,22 @@ static const struct hwmon_channel_info *nct6694_info[] = {
NCT6694_HWMON_TEMP_CONFIG, /* TDP2 */
NCT6694_HWMON_TEMP_CONFIG, /* TDP3 */
NCT6694_HWMON_TEMP_CONFIG, /* TDP4 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN0 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN1 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN2 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN3 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN4 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN5 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN6 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN7 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN8 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN9 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN10 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN11 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN12 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN13 */
NCT6694_HWMON_TEMP_CONFIG, /* DTIN14 */
NCT6694_HWMON_TEMP_CONFIG), /* DTIN15 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN0 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN1 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN2 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN3 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN4 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN5 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN6 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN7 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN8 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN9 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN10 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN11 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN12 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN13 */
NCT6694_HWMON_DTIN_CONFIG, /* DTIN14 */
NCT6694_HWMON_DTIN_CONFIG), /* DTIN15 */
HWMON_CHANNEL_INFO(fan,
NCT6694_HWMON_FAN_CONFIG, /* FIN0 */

View File

@ -1275,7 +1275,9 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b,
regval = status & mask;
if (regval) {
if (data->revision >= PMBUS_REV_12) {
/* Generic STATUS_WORD alarms are not individually clearable. */
if (data->revision >= PMBUS_REV_12 &&
reg != PMBUS_STATUS_WORD) {
ret = _pmbus_write_byte_data(client, page, reg, regval);
if (ret)
return ret;

View File

@ -277,6 +277,8 @@ static ssize_t heater_enable_store(struct device *dev,
heating_time_bound = 1100;
}
guard(hwmon_lock)(dev);
if (time_before(jiffies, data->heating_complete))
return -EBUSY;
@ -286,7 +288,7 @@ static ssize_t heater_enable_store(struct device *dev,
data->heating_complete = jiffies + msecs_to_jiffies(heating_time_bound);
data->data_pending = true;
return 0;
return count;
}
static ssize_t heater_power_show(struct device *dev,
@ -314,6 +316,8 @@ static ssize_t heater_power_store(struct device *dev,
if (power != 20 && power != 110 && power != 200)
return -EINVAL;
guard(hwmon_lock)(dev);
data->heater_power = power;
return count;
@ -344,6 +348,8 @@ static ssize_t heater_time_store(struct device *dev,
if (time != 100 && time != 1000)
return -EINVAL;
guard(hwmon_lock)(dev);
data->heater_time = time;
return count;

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
/*
* yoga_fan.c - Lenovo Yoga/Legion Fan Hardware Monitoring Driver
*
* Provides fan speed monitoring for Lenovo Yoga, Legion, and IdeaPad