linux/drivers/acpi/fan_hwmon.c

// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * hwmon interface for the ACPI Fan driver.
 *
 * Copyright (C) 2024 Armin Wolf <[email protected]>
 */

#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/limits.h>
#include <linux/types.h>
#include <linux/units.h>

#include "fan.h"

/* Returned when the ACPI fan does not support speed reporting */
#define FAN_SPEED_UNAVAILABLE	U32_MAX
#define FAN_POWER_UNAVAILABLE	U32_MAX

static struct acpi_fan_fps *acpi_fan_get_current_fps(struct acpi_fan *fan, u64 control)
{
	unsigned int i;

	for (i = 0; i < fan->fps_count; i++) {
		if (fan->fps[i].control == control)
			return &fan->fps[i];
	}

	return NULL;
}

static umode_t acpi_fan_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
					 u32 attr, int channel)
{
	const struct acpi_fan *fan = drvdata;
	unsigned int i;

	switch (type) {
	case hwmon_fan:
		switch (attr) {
		case hwmon_fan_input:
			return 0444;
		case hwmon_fan_target:
			/*
			 * When in fine grain control mode, not every fan control value
			 * has an associated fan performance state.
			 */
			if (fan->fif.fine_grain_ctrl)
				return 0;

			return 0444;
		default:
			return 0;
		}
	case hwmon_power:
		switch (attr) {
		case hwmon_power_input:
			/*
			 * When in fine grain control mode, not every fan control value
			 * has an associated fan performance state.
			 */
			if (fan->fif.fine_grain_ctrl)
				return 0;

			/*
			 * When all fan performance states contain no valid power data,
			 * when the associated attribute should not be created.
			 */
			for (i = 0; i < fan->fps_count; i++) {
				if (fan->fps[i].power != FAN_POWER_UNAVAILABLE)
					return 0444;
			}

			return 0;
		default:
			return 0;
		}
	default:
		return 0;
	}
}

static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
			       int channel, long *val)
{
	struct acpi_device *adev = to_acpi_device(dev->parent);
	struct acpi_fan *fan = dev_get_drvdata(dev);
	struct acpi_fan_fps *fps;
	struct acpi_fan_fst fst;
	int ret;

	ret = acpi_fan_get_fst(adev, &fst);
	if (ret < 0)
		return ret;

	switch (type) {
	case hwmon_fan:
		switch (attr) {
		case hwmon_fan_input:
			if (fst.speed == FAN_SPEED_UNAVAILABLE)
				return -ENODEV;

			if (fst.speed > LONG_MAX)
				return -EOVERFLOW;

			*val = fst.speed;
			return 0;
		case hwmon_fan_target:
			fps = acpi_fan_get_current_fps(fan, fst.control);
			if (!fps)
				return -EIO;

			if (fps->speed > LONG_MAX)
				return -EOVERFLOW;

			*val = fps->speed;
			return 0;
		default:
			return -EOPNOTSUPP;
		}
	case hwmon_power:
		switch (attr) {
		case hwmon_power_input:
			fps = acpi_fan_get_current_fps(fan, fst.control);
			if (!fps)
				return -EIO;

			if (fps->power == FAN_POWER_UNAVAILABLE)
				return -ENODEV;

			if (fps->power > LONG_MAX / MICROWATT_PER_MILLIWATT)
				return -EOVERFLOW;

			*val = fps->power * MICROWATT_PER_MILLIWATT;
			return 0;
		default:
			return -EOPNOTSUPP;
		}
	default:
		return -EOPNOTSUPP;
	}
}

static const struct hwmon_ops acpi_fan_hwmon_ops = {
	.is_visible = acpi_fan_hwmon_is_visible,
	.read = acpi_fan_hwmon_read,
};

static const struct hwmon_channel_info * const acpi_fan_hwmon_info[] = {
	HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_TARGET),
	HWMON_CHANNEL_INFO(power, HWMON_P_INPUT),
	NULL
};

static const struct hwmon_chip_info acpi_fan_hwmon_chip_info = {
	.ops = &acpi_fan_hwmon_ops,
	.info = acpi_fan_hwmon_info,
};

int devm_acpi_fan_create_hwmon(struct acpi_device *device)
{
	struct acpi_fan *fan = acpi_driver_data(device);
	struct device *hdev;

	hdev = devm_hwmon_device_register_with_info(&device->dev, "acpi_fan", fan,
						    &acpi_fan_hwmon_chip_info, NULL);
	return PTR_ERR_OR_ZERO(hdev);
}