mirror of
https://github.com/torvalds/linux.git
synced 2026-09-12 20:53:03 +02:00
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:
commit
aa416593f3
|
|
@ -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.
|
||||
|
||||
NB:In 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
--------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mask = ina2xx_alert_type_to_mask(alert);
|
||||
if (regval & mask) {
|
||||
ret = regmap_read(regmap, INA226_ALERT_LIMIT, ®val);
|
||||
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, ®val);
|
||||
/*
|
||||
* 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, ®val);
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user