diff options
-rw-r--r-- | drivers/hwmon/hwmon.c | 485 | ||||
-rw-r--r-- | include/linux/hwmon.h | 148 |
2 files changed, 606 insertions, 27 deletions
diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 649a68d119b4..3e4cc442a089 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -12,6 +12,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/bitops.h> #include <linux/device.h> #include <linux/err.h> #include <linux/gfp.h> @@ -21,6 +22,7 @@ #include <linux/pci.h> #include <linux/slab.h> #include <linux/string.h> +#include <linux/thermal.h> #define HWMON_ID_PREFIX "hwmon" #define HWMON_ID_FORMAT HWMON_ID_PREFIX "%d" @@ -28,9 +30,35 @@ struct hwmon_device { const char *name; struct device dev; + const struct hwmon_chip_info *chip; + + struct attribute_group group; + const struct attribute_group **groups; }; + #define to_hwmon_device(d) container_of(d, struct hwmon_device, dev) +struct hwmon_device_attribute { + struct device_attribute dev_attr; + const struct hwmon_ops *ops; + enum hwmon_sensor_types type; + u32 attr; + int index; +}; + +#define to_hwmon_attr(d) \ + container_of(d, struct hwmon_device_attribute, dev_attr) + +/* + * Thermal zone information + * In addition to the reference to the hwmon device, + * also provides the sensor index. + */ +struct hwmon_thermal_data { + struct hwmon_device *hwdev; /* Reference to hwmon device */ + int index; /* sensor index */ +}; + static ssize_t show_name(struct device *dev, struct device_attribute *attr, char *buf) { @@ -78,25 +106,286 @@ static struct class hwmon_class = { static DEFINE_IDA(hwmon_ida); -/** - * hwmon_device_register_with_groups - register w/ hwmon - * @dev: the parent device - * @name: hwmon name attribute - * @drvdata: driver data to attach to created device - * @groups: List of attribute groups to create - * - * hwmon_device_unregister() must be called when the device is no - * longer needed. - * - * Returns the pointer to the new device. - */ -struct device * -hwmon_device_register_with_groups(struct device *dev, const char *name, - void *drvdata, - const struct attribute_group **groups) +/* Thermal zone handling */ + +#if IS_REACHABLE(CONFIG_THERMAL) && defined(CONFIG_THERMAL_OF) +static int hwmon_thermal_get_temp(void *data, int *temp) +{ + struct hwmon_thermal_data *tdata = data; + struct hwmon_device *hwdev = tdata->hwdev; + int ret; + long t; + + ret = hwdev->chip->ops->read(&hwdev->dev, hwmon_temp, hwmon_temp_input, + tdata->index, &t); + if (ret < 0) + return ret; + + *temp = t; + + return 0; +} + +static struct thermal_zone_of_device_ops hwmon_thermal_ops = { + .get_temp = hwmon_thermal_get_temp, +}; + +static int hwmon_thermal_add_sensor(struct device *dev, + struct hwmon_device *hwdev, int index) +{ + struct hwmon_thermal_data *tdata; + + tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL); + if (!tdata) + return -ENOMEM; + + tdata->hwdev = hwdev; + tdata->index = index; + + devm_thermal_zone_of_sensor_register(&hwdev->dev, index, tdata, + &hwmon_thermal_ops); + + return 0; +} +#else +static int hwmon_thermal_add_sensor(struct device *dev, + struct hwmon_device *hwdev, int index) +{ + return 0; +} +#endif /* IS_REACHABLE(CONFIG_THERMAL) && defined(CONFIG_THERMAL_OF) */ + +/* sysfs attribute management */ + +static ssize_t hwmon_attr_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + long val; + int ret; + + ret = hattr->ops->read(dev, hattr->type, hattr->attr, hattr->index, + &val); + if (ret < 0) + return ret; + + return sprintf(buf, "%ld\n", val); +} + +static ssize_t hwmon_attr_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + ret = hattr->ops->write(dev, hattr->type, hattr->attr, hattr->index, + val); + if (ret < 0) + return ret; + + return count; +} + +static int hwmon_attr_base(enum hwmon_sensor_types type) +{ + if (type == hwmon_in) + return 0; + return 1; +} + +static struct attribute *hwmon_genattr(struct device *dev, + const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, + int index, + const char *template, + const struct hwmon_ops *ops) +{ + struct hwmon_device_attribute *hattr; + struct device_attribute *dattr; + struct attribute *a; + umode_t mode; + char *name; + + /* The attribute is invisible if there is no template string */ + if (!template) + return ERR_PTR(-ENOENT); + + mode = ops->is_visible(drvdata, type, attr, index); + if (!mode) + return ERR_PTR(-ENOENT); + + if ((mode & S_IRUGO) && !ops->read) + return ERR_PTR(-EINVAL); + if ((mode & S_IWUGO) && !ops->write) + return ERR_PTR(-EINVAL); + + if (type == hwmon_chip) { + name = (char *)template; + } else { + name = devm_kzalloc(dev, strlen(template) + 16, GFP_KERNEL); + if (!name) + return ERR_PTR(-ENOMEM); + scnprintf(name, strlen(template) + 16, template, + index + hwmon_attr_base(type)); + } + + hattr = devm_kzalloc(dev, sizeof(*hattr), GFP_KERNEL); + if (!hattr) + return ERR_PTR(-ENOMEM); + + hattr->type = type; + hattr->attr = attr; + hattr->index = index; + hattr->ops = ops; + + dattr = &hattr->dev_attr; + dattr->show = hwmon_attr_show; + dattr->store = hwmon_attr_store; + + a = &dattr->attr; + sysfs_attr_init(a); + a->name = name; + a->mode = mode; + + return a; +} + +static const char * const hwmon_chip_attr_templates[] = { + [hwmon_chip_temp_reset_history] = "temp_reset_history", + [hwmon_chip_update_interval] = "update_interval", + [hwmon_chip_alarms] = "alarms", +}; + +static const char * const hwmon_temp_attr_templates[] = { + [hwmon_temp_input] = "temp%d_input", + [hwmon_temp_type] = "temp%d_type", + [hwmon_temp_lcrit] = "temp%d_lcrit", + [hwmon_temp_lcrit_hyst] = "temp%d_lcrit_hyst", + [hwmon_temp_min] = "temp%d_min", + [hwmon_temp_min_hyst] = "temp%d_min_hyst", + [hwmon_temp_max] = "temp%d_max", + [hwmon_temp_max_hyst] = "temp%d_max_hyst", + [hwmon_temp_crit] = "temp%d_crit", + [hwmon_temp_crit_hyst] = "temp%d_crit_hyst", + [hwmon_temp_emergency] = "temp%d_emergency", + [hwmon_temp_emergency_hyst] = "temp%d_emergency_hyst", + [hwmon_temp_alarm] = "temp%d_alarm", + [hwmon_temp_lcrit_alarm] = "temp%d_lcrit_alarm", + [hwmon_temp_min_alarm] = "temp%d_min_alarm", + [hwmon_temp_max_alarm] = "temp%d_max_alarm", + [hwmon_temp_crit_alarm] = "temp%d_crit_alarm", + [hwmon_temp_emergency_alarm] = "temp%d_emergency_alarm", + [hwmon_temp_fault] = "temp%d_fault", + [hwmon_temp_offset] = "temp%d_offset", + [hwmon_temp_label] = "temp%d_label", + [hwmon_temp_lowest] = "temp%d_lowest", + [hwmon_temp_highest] = "temp%d_highest", + [hwmon_temp_reset_history] = "temp%d_reset_history", +}; + +static const char * const *__templates[] = { + [hwmon_chip] = hwmon_chip_attr_templates, + [hwmon_temp] = hwmon_temp_attr_templates, +}; + +static const int __templates_size[] = { + [hwmon_chip] = ARRAY_SIZE(hwmon_chip_attr_templates), + [hwmon_temp] = ARRAY_SIZE(hwmon_temp_attr_templates), +}; + +static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) +{ + int i, n; + + for (i = n = 0; info->config[i]; i++) + n += hweight32(info->config[i]); + + return n; +} + +static int hwmon_genattrs(struct device *dev, + const void *drvdata, + struct attribute **attrs, + const struct hwmon_ops *ops, + const struct hwmon_channel_info *info) +{ + const char * const *templates; + int template_size; + int i, aindex = 0; + + if (info->type >= ARRAY_SIZE(__templates)) + return -EINVAL; + + templates = __templates[info->type]; + template_size = __templates_size[info->type]; + + for (i = 0; info->config[i]; i++) { + u32 attr_mask = info->config[i]; + u32 attr; + + while (attr_mask) { + struct attribute *a; + + attr = __ffs(attr_mask); + attr_mask &= ~BIT(attr); + if (attr >= template_size) + return -EINVAL; + a = hwmon_genattr(dev, drvdata, info->type, attr, i, + templates[attr], ops); + if (IS_ERR(a)) { + if (PTR_ERR(a) != -ENOENT) + return PTR_ERR(a); + continue; + } + attrs[aindex++] = a; + } + } + return aindex; +} + +static struct attribute ** +__hwmon_create_attrs(struct device *dev, const void *drvdata, + const struct hwmon_chip_info *chip) +{ + int ret, i, aindex = 0, nattrs = 0; + struct attribute **attrs; + + for (i = 0; chip->info[i]; i++) + nattrs += hwmon_num_channel_attrs(chip->info[i]); + + if (nattrs == 0) + return ERR_PTR(-EINVAL); + + attrs = devm_kcalloc(dev, nattrs + 1, sizeof(*attrs), GFP_KERNEL); + if (!attrs) + return ERR_PTR(-ENOMEM); + + for (i = 0; chip->info[i]; i++) { + ret = hwmon_genattrs(dev, drvdata, &attrs[aindex], chip->ops, + chip->info[i]); + if (ret < 0) + return ERR_PTR(ret); + aindex += ret; + } + + return attrs; +} + +static struct device * +__hwmon_device_register(struct device *dev, const char *name, void *drvdata, + const struct hwmon_chip_info *chip, + const struct attribute_group **groups) { struct hwmon_device *hwdev; - int err, id; + struct device *hdev; + int i, j, err, id; /* Do not accept invalid characters in hwmon name attribute */ if (name && (!strlen(name) || strpbrk(name, "-* \t\n"))) @@ -112,28 +401,128 @@ hwmon_device_register_with_groups(struct device *dev, const char *name, goto ida_remove; } + hdev = &hwdev->dev; + + if (chip && chip->ops->is_visible) { + struct attribute **attrs; + int ngroups = 2; + + if (groups) + for (i = 0; groups[i]; i++) + ngroups++; + + hwdev->groups = devm_kcalloc(dev, ngroups, sizeof(*groups), + GFP_KERNEL); + if (!hwdev->groups) + return ERR_PTR(-ENOMEM); + + attrs = __hwmon_create_attrs(dev, drvdata, chip); + if (IS_ERR(attrs)) { + err = PTR_ERR(attrs); + goto free_hwmon; + } + + hwdev->group.attrs = attrs; + ngroups = 0; + hwdev->groups[ngroups++] = &hwdev->group; + + if (groups) { + for (i = 0; groups[i]; i++) + hwdev->groups[ngroups++] = groups[i]; + } + + hdev->groups = hwdev->groups; + } else { + hdev->groups = groups; + } + hwdev->name = name; - hwdev->dev.class = &hwmon_class; - hwdev->dev.parent = dev; - hwdev->dev.groups = groups; - hwdev->dev.of_node = dev ? dev->of_node : NULL; - dev_set_drvdata(&hwdev->dev, drvdata); - dev_set_name(&hwdev->dev, HWMON_ID_FORMAT, id); - err = device_register(&hwdev->dev); + hdev->class = &hwmon_class; + hdev->parent = dev; + hdev->of_node = dev ? dev->of_node : NULL; + hwdev->chip = chip; + dev_set_drvdata(hdev, drvdata); + dev_set_name(hdev, HWMON_ID_FORMAT, id); + err = device_register(hdev); if (err) - goto free; + goto free_hwmon; + + if (chip && chip->ops->is_visible && chip->ops->read && + chip->info[0]->type == hwmon_chip && + (chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) { + const struct hwmon_channel_info **info = chip->info; + + for (i = 1; info[i]; i++) { + if (info[i]->type != hwmon_temp) + continue; + + for (j = 0; info[i]->config[j]; j++) { + if (!chip->ops->is_visible(drvdata, hwmon_temp, + hwmon_temp_input, j)) + continue; + if (info[i]->config[j] & HWMON_T_INPUT) + hwmon_thermal_add_sensor(dev, hwdev, j); + } + } + } - return &hwdev->dev; + return hdev; -free: +free_hwmon: kfree(hwdev); ida_remove: ida_simple_remove(&hwmon_ida, id); return ERR_PTR(err); } + +/** + * hwmon_device_register_with_groups - register w/ hwmon + * @dev: the parent device + * @name: hwmon name attribute + * @drvdata: driver data to attach to created device + * @groups: List of attribute groups to create + * + * hwmon_device_unregister() must be called when the device is no + * longer needed. + * + * Returns the pointer to the new device. + */ +struct device * +hwmon_device_register_with_groups(struct device *dev, const char *name, + void *drvdata, + const struct attribute_group **groups) +{ + return __hwmon_device_register(dev, name, drvdata, NULL, groups); +} EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups); /** + * hwmon_device_register_with_info - register w/ hwmon + * @dev: the parent device + * @name: hwmon name attribute + * @drvdata: driver data to attach to created device + * @info: Pointer to hwmon chip information + * @groups - pointer to list of driver specific attribute groups + * + * hwmon_device_unregister() must be called when the device is no + * longer needed. + * + * Returns the pointer to the new device. + */ +struct device * +hwmon_device_register_with_info(struct device *dev, const char *name, + void *drvdata, + const struct hwmon_chip_info *chip, + const struct attribute_group **groups) +{ + if (chip && (!chip->ops || !chip->info)) + return ERR_PTR(-EINVAL); + + return __hwmon_device_register(dev, name, drvdata, chip, groups); +} +EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); + +/** * hwmon_device_register - register w/ hwmon * @dev: the device to register * @@ -211,6 +600,48 @@ error: } EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_groups); +/** + * devm_hwmon_device_register_with_info - register w/ hwmon + * @dev: the parent device + * @name: hwmon name attribute + * @drvdata: driver data to attach to created device + * @info: Pointer to hwmon chip information + * @groups - pointer to list of driver specific attribute groups + * + * Returns the pointer to the new device. The new device is automatically + * unregistered with the parent device. + */ +struct device * +devm_hwmon_device_register_with_info(struct device *dev, const char *name, + void *drvdata, + const struct hwmon_chip_info *chip, + const struct attribute_group **groups) +{ + struct device **ptr, *hwdev; + + if (!dev) + return ERR_PTR(-EINVAL); + + ptr = devres_alloc(devm_hwmon_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + hwdev = hwmon_device_register_with_info(dev, name, drvdata, chip, + groups); + if (IS_ERR(hwdev)) + goto error; + + *ptr = hwdev; + devres_add(dev, ptr); + + return hwdev; + +error: + devres_free(ptr); + return hwdev; +} +EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_info); + static int devm_hwmon_match(struct device *dev, void *res, void *data) { struct device **hwdev = res; diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 09354f6c1d63..52e56d71d742 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -14,9 +14,147 @@ #ifndef _HWMON_H_ #define _HWMON_H_ +#include <linux/bitops.h> + struct device; struct attribute_group; +enum hwmon_sensor_types { + hwmon_chip, + hwmon_temp, + hwmon_in, + hwmon_curr, + hwmon_power, + hwmon_energy, +}; + +enum hwmon_chip_attributes { + hwmon_chip_temp_reset_history, + hwmon_chip_register_tz, + hwmon_chip_update_interval, + hwmon_chip_alarms, +}; + +#define HWMON_C_TEMP_RESET_HISTORY BIT(hwmon_chip_temp_reset_history) +#define HWMON_C_IN_RESET_HISTORY BIT(hwmon_chip_in_reset_history) +#define HWMON_C_REGISTER_TZ BIT(hwmon_chip_register_tz) +#define HWMON_C_UPDATE_INTERVAL BIT(hwmon_chip_update_interval) +#define HWMON_C_ALARMS BIT(hwmon_chip_alarms) + +enum hwmon_temp_attributes { + hwmon_temp_input = 0, + hwmon_temp_type, + hwmon_temp_lcrit, + hwmon_temp_lcrit_hyst, + hwmon_temp_min, + hwmon_temp_min_hyst, + hwmon_temp_max, + hwmon_temp_max_hyst, + hwmon_temp_crit, + hwmon_temp_crit_hyst, + hwmon_temp_emergency, + hwmon_temp_emergency_hyst, + hwmon_temp_alarm, + hwmon_temp_lcrit_alarm, + hwmon_temp_min_alarm, + hwmon_temp_max_alarm, + hwmon_temp_crit_alarm, + hwmon_temp_emergency_alarm, + hwmon_temp_fault, + hwmon_temp_offset, + hwmon_temp_label, + hwmon_temp_lowest, + hwmon_temp_highest, + hwmon_temp_reset_history, +}; + +#define HWMON_T_INPUT BIT(hwmon_temp_input) +#define HWMON_T_TYPE BIT(hwmon_temp_type) +#define HWMON_T_LCRIT BIT(hwmon_temp_lcrit) +#define HWMON_T_LCRIT_HYST BIT(hwmon_temp_lcrit_hyst) +#define HWMON_T_MIN BIT(hwmon_temp_min) +#define HWMON_T_MIN_HYST BIT(hwmon_temp_min_hyst) +#define HWMON_T_MAX BIT(hwmon_temp_max) +#define HWMON_T_MAX_HYST BIT(hwmon_temp_max_hyst) +#define HWMON_T_CRIT BIT(hwmon_temp_crit) +#define HWMON_T_CRIT_HYST BIT(hwmon_temp_crit_hyst) +#define HWMON_T_EMERGENCY BIT(hwmon_temp_emergency) +#define HWMON_T_EMERGENCY_HYST BIT(hwmon_temp_emergency_hyst) +#define HWMON_T_MIN_ALARM BIT(hwmon_temp_min_alarm) +#define HWMON_T_MAX_ALARM BIT(hwmon_temp_max_alarm) +#define HWMON_T_CRIT_ALARM BIT(hwmon_temp_crit_alarm) +#define HWMON_T_EMERGENCY_ALARM BIT(hwmon_temp_emergency_alarm) +#define HWMON_T_FAULT BIT(hwmon_temp_fault) +#define HWMON_T_OFFSET BIT(hwmon_temp_offset) +#define HWMON_T_LABEL BIT(hwmon_temp_label) +#define HWMON_T_LOWEST BIT(hwmon_temp_lowest) +#define HWMON_T_HIGHEST BIT(hwmon_temp_highest) +#define HWMON_T_RESET_HISTORY BIT(hwmon_temp_reset_history) + +/** + * struct hwmon_ops - hwmon device operations + * @is_visible: Callback to return attribute visibility. Mandatory. + * Parameters are: + * @const void *drvdata: + * Pointer to driver-private data structure passed + * as argument to hwmon_device_register_with_info(). + * @type: Sensor type + * @attr: Sensor attribute + * @channel: + * Channel number + * The function returns the file permissions. + * If the return value is 0, no attribute will be created. + * @read: Read callback. Optional. If not provided, attributes + * will not be readable. + * Parameters are: + * @dev: Pointer to hardware monitoring device + * @type: Sensor type + * @attr: Sensor attribute + * @channel: + * Channel number + * @val: Pointer to returned value + * The function returns 0 on success or a negative error number. + * @write: Write callback. Optional. If not provided, attributes + * will not be writable. + * Parameters are: + * @dev: Pointer to hardware monitoring device + * @type: Sensor type + * @attr: Sensor attribute + * @channel: + * Channel number + * @val: Value to write + * The function returns 0 on success or a negative error number. + */ +struct hwmon_ops { + umode_t (*is_visible)(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel); + int (*read)(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val); + int (*write)(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val); +}; + +/** + * Channel information + * @type: Channel type. + * @config: Pointer to NULL-terminated list of channel parameters. + * Use for per-channel attributes. + */ +struct hwmon_channel_info { + enum hwmon_sensor_types type; + const u32 *config; +}; + +/** + * Chip configuration + * @ops: Pointer to hwmon operations. + * @info: Null-terminated list of channel information. + */ +struct hwmon_chip_info { + const struct hwmon_ops *ops; + const struct hwmon_channel_info **info; +}; + struct device *hwmon_device_register(struct device *dev); struct device * hwmon_device_register_with_groups(struct device *dev, const char *name, @@ -26,6 +164,16 @@ struct device * devm_hwmon_device_register_with_groups(struct device *dev, const char *name, void *drvdata, const struct attribute_group **groups); +struct device * +hwmon_device_register_with_info(struct device *dev, + const char *name, void *drvdata, + const struct hwmon_chip_info *info, + const struct attribute_group **groups); +struct device * +devm_hwmon_device_register_with_info(struct device *dev, + const char *name, void *drvdata, + const struct hwmon_chip_info *info, + const struct attribute_group **groups); void hwmon_device_unregister(struct device *dev); void devm_hwmon_device_unregister(struct device *dev); |