// SPDX-License-Identifier: GPL-2.0-only
/*
* hwmon.c - part of lm_sensors, Linux kernel modules for hardware monitoring
*
* This file defines the sysfs class "hwmon", for use by sensors drivers.
*
* Copyright (C) 2005 Mark M. Hoffman <mhoffman@lightlink.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/hwmon.h>
#include <linux/idr.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/thermal.h>
#define CREATE_TRACE_POINTS
#include <trace/events/hwmon.h>
#define HWMON_ID_PREFIX "hwmon"
#define HWMON_ID_FORMAT HWMON_ID_PREFIX "%d"
struct hwmon_device {
const char *name;
struct device dev;
const struct hwmon_chip_info *chip;
struct list_head tzdata;
struct attribute_group group;
const struct attribute_group **groups;
};
#define to_hwmon_device(d) container_of(d, struct hwmon_device, dev)
#define MAX_SYSFS_ATTR_NAME_LENGTH 32
struct hwmon_device_attribute {
struct device_attribute dev_attr;
const struct hwmon_ops *ops;
enum hwmon_sensor_types type;
u32 attr;
int index;
char name[MAX_SYSFS_ATTR_NAME_LENGTH];
};
#define to_hwmon_attr(d) \
container_of(d, struct hwmon_device_attribute, dev_attr)
#define to_dev_attr(a) container_of(a, struct device_attribute, attr)
/*
* Thermal zone information
*/
struct hwmon_thermal_data {
struct list_head node; /* hwmon tzdata list entry */
struct device *dev; /* Reference to hwmon device */
int index; /* sensor index */
struct thermal_zone_device *tzd;/* thermal zone device */
};
static ssize_t
name_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", to_hwmon_device(dev)->name);
}
static DEVICE_ATTR_RO(name);
static struct attribute *hwmon_dev_attrs[] = {
&dev_attr_name.attr,
NULL
};
static umode_t hwmon_dev_name_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = container_of(kobj, struct device, kobj);
if (to_hwmon_device(dev)->name == NULL)
return 0;
return attr->mode;
}
static const struct attribute_group hwmon_dev_attr_group = {
.attrs = hwmon_dev_attrs,
.is_visible = hwmon_dev_name_is_visible,
};
static const struct attribute_group *hwmon_dev_attr_groups[] = {
&hwmon_dev_attr_group,
NULL
};
static void hwmon_free_attrs(struct attribute **attrs)
{
int i;
for (i = 0; attrs[i]; i++) {
struct device_attribute *dattr = to_dev_attr(attrs[i]);
struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr);
kfree(hattr);
}
kfree(attrs);
}
static void hwmon_dev_release(struct device *dev)
{
struct hwmon_device *hwdev = to_hwmon_device(dev);
if (hwdev->group.attrs)
hwmon_free_attrs(hwdev->group.attrs);
kfree(hwdev->groups);
kfree(hwdev);
}
static struct class hwmon_class = {
.name = "hwmon",
.owner = THIS_MODULE,
.dev_groups = hwmon_dev_attr_groups,
.dev_release = hwmon_dev_release,
};
static DEFINE_IDA(hwmon_ida);
/* Thermal zone handling */
/*
* The complex conditional is necessary to avoid a cyclic dependency
* between hwmon and thermal_sys modules.
*/
#ifdef CONFIG_THERMAL_OF
static int hwmon_thermal_get_temp(void *data, int *temp)
{
struct hwmon_thermal_data *tdata = data;
struct hwmon_device *hwdev = to_hwmon_device(tdata->dev);
int ret;
long t;
ret = hwdev->chip->ops->read(tdata->dev, hwmon_temp, hwmon_temp_input,
tdata->index, &t);
if (ret < 0)
return ret;
*temp = t;
return 0;
}
static const struct thermal_zone_of_device_ops hwmon_thermal_ops = {
.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)
{
struct hwmon_device *hwdev = to_hwmon_device(dev);
struct hwmon_thermal_data *tdata;
struct thermal_zone_device *tzd;
int err;
tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL);
if (!tdata)
return -ENOMEM;
tdata->dev = dev;
tdata->index = index;
tzd = devm_thermal_zone_of_sensor_register(dev, index, tdata,
&hwmon_thermal_ops);
if (IS_ERR(tzd)) {
if (PTR_ERR(tzd) != -ENODEV)
return PTR_ERR(tzd);
dev_info(dev, "temp%d_input not attached to any thermal zone\n",
index + 1);
devm_kfree(dev, tdata);
return 0;
}
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;
}
static int hwmon_thermal_register_sensors(struct device *dev)
{
struct hwmon_device *hwdev = to_hwmon_device(dev);