hwmon: Add notification support

For hwmon drivers using the hwmon_device_register_with_info() API, it
is desirable to have a generic notification mechanism available. This
mechanism can be used to notify userspace as well as the thermal
subsystem if the driver experiences any events, such as warning or
critical alarms.

Implement hwmon_notify_event() to provide this mechanism. The function
generates a sysfs event and a udev event. If the device is registered
with the thermal subsystem and the event is associated with a temperature
sensor, also notify the thermal subsystem that a thermal event occurred.

Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Cc: Maxim Kaurkin <Maxim.Kaurkin@baikalelectronics.ru>
Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: linux-mips@vger.kernel.org
Cc: devicetree@vger.kernel.org
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
This commit is contained in:
Guenter Roeck 2020-05-28 17:28:04 +03:00
parent ddc65caa56
commit 1597b374af
2 changed files with 68 additions and 3 deletions

View File

@ -15,6 +15,7 @@
#include <linux/gfp.h> #include <linux/gfp.h>
#include <linux/hwmon.h> #include <linux/hwmon.h>
#include <linux/idr.h> #include <linux/idr.h>
#include <linux/list.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/pci.h> #include <linux/pci.h>
#include <linux/slab.h> #include <linux/slab.h>
@ -31,7 +32,7 @@ struct hwmon_device {
const char *name; const char *name;
struct device dev; struct device dev;
const struct hwmon_chip_info *chip; const struct hwmon_chip_info *chip;
struct list_head tzdata;
struct attribute_group group; struct attribute_group group;
const struct attribute_group **groups; const struct attribute_group **groups;
}; };
@ -55,12 +56,12 @@ struct hwmon_device_attribute {
/* /*
* Thermal zone information * Thermal zone information
* In addition to the reference to the hwmon device,
* also provides the sensor index.
*/ */
struct hwmon_thermal_data { struct hwmon_thermal_data {
struct list_head node; /* hwmon tzdata list entry */
struct device *dev; /* Reference to hwmon device */ struct device *dev; /* Reference to hwmon device */
int index; /* sensor index */ int index; /* sensor index */
struct thermal_zone_device *tzd;/* thermal zone device */
}; };
static ssize_t static ssize_t
@ -156,10 +157,17 @@ static const struct thermal_zone_of_device_ops hwmon_thermal_ops = {
.get_temp = hwmon_thermal_get_temp, .get_temp = hwmon_thermal_get_temp,
}; };
static void hwmon_thermal_remove_sensor(void *data)
{
list_del(data);
}
static int hwmon_thermal_add_sensor(struct device *dev, int index) static int hwmon_thermal_add_sensor(struct device *dev, int index)
{ {
struct hwmon_device *hwdev = to_hwmon_device(dev);
struct hwmon_thermal_data *tdata; struct hwmon_thermal_data *tdata;
struct thermal_zone_device *tzd; struct thermal_zone_device *tzd;
int err;
tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL); tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL);
if (!tdata) if (!tdata)
@ -177,6 +185,13 @@ static int hwmon_thermal_add_sensor(struct device *dev, int index)
if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV))
return PTR_ERR(tzd); return PTR_ERR(tzd);
err = devm_add_action(dev, hwmon_thermal_remove_sensor, &tdata->node);
if (err)
return err;
tdata->tzd = tzd;
list_add(&tdata->node, &hwdev->tzdata);
return 0; return 0;
} }
@ -211,11 +226,27 @@ static int hwmon_thermal_register_sensors(struct device *dev)
return 0; return 0;
} }
static void hwmon_thermal_notify(struct device *dev, int index)
{
struct hwmon_device *hwdev = to_hwmon_device(dev);
struct hwmon_thermal_data *tzdata;
list_for_each_entry(tzdata, &hwdev->tzdata, node) {
if (tzdata->index == index) {
thermal_zone_device_update(tzdata->tzd,
THERMAL_EVENT_UNSPECIFIED);
}
}
}
#else #else
static int hwmon_thermal_register_sensors(struct device *dev) static int hwmon_thermal_register_sensors(struct device *dev)
{ {
return 0; return 0;
} }
static void hwmon_thermal_notify(struct device *dev, int index) { }
#endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */ #endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */
static int hwmon_attr_base(enum hwmon_sensor_types type) static int hwmon_attr_base(enum hwmon_sensor_types type)
@ -543,6 +574,35 @@ static const int __templates_size[] = {
[hwmon_intrusion] = ARRAY_SIZE(hwmon_intrusion_attr_templates), [hwmon_intrusion] = ARRAY_SIZE(hwmon_intrusion_attr_templates),
}; };
int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel)
{
char sattr[MAX_SYSFS_ATTR_NAME_LENGTH];
const char * const *templates;
const char *template;
int base;
if (type >= ARRAY_SIZE(__templates))
return -EINVAL;
if (attr >= __templates_size[type])
return -EINVAL;
templates = __templates[type];
template = templates[attr];
base = hwmon_attr_base(type);
scnprintf(sattr, MAX_SYSFS_ATTR_NAME_LENGTH, template, base + channel);
sysfs_notify(&dev->kobj, NULL, sattr);
kobject_uevent(&dev->kobj, KOBJ_CHANGE);
if (type == hwmon_temp)
hwmon_thermal_notify(dev, channel);
return 0;
}
EXPORT_SYMBOL_GPL(hwmon_notify_event);
static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
{ {
int i, n; int i, n;
@ -693,6 +753,8 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
if (err) if (err)
goto free_hwmon; goto free_hwmon;
INIT_LIST_HEAD(&hwdev->tzdata);
if (dev && dev->of_node && chip && chip->ops->read && if (dev && dev->of_node && chip && chip->ops->read &&
chip->info[0]->type == hwmon_chip && chip->info[0]->type == hwmon_chip &&
(chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) { (chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) {

View File

@ -436,6 +436,9 @@ devm_hwmon_device_register_with_info(struct device *dev,
void hwmon_device_unregister(struct device *dev); void hwmon_device_unregister(struct device *dev);
void devm_hwmon_device_unregister(struct device *dev); void devm_hwmon_device_unregister(struct device *dev);
int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel);
/** /**
* hwmon_is_bad_char - Is the char invalid in a hwmon name * hwmon_is_bad_char - Is the char invalid in a hwmon name
* @ch: the char to be considered * @ch: the char to be considered