Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.14.15.
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * hwmon interface for the ACPI Fan driver.
  4 *
  5 * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
  6 */
  7
  8#include <linux/acpi.h>
  9#include <linux/device.h>
 10#include <linux/err.h>
 11#include <linux/hwmon.h>
 12#include <linux/limits.h>
 13#include <linux/types.h>
 14#include <linux/units.h>
 15
 16#include "fan.h"
 17
 18/* Returned when the ACPI fan does not support speed reporting */
 19#define FAN_SPEED_UNAVAILABLE	U32_MAX
 20#define FAN_POWER_UNAVAILABLE	U32_MAX
 21
 22static struct acpi_fan_fps *acpi_fan_get_current_fps(struct acpi_fan *fan, u64 control)
 23{
 24	unsigned int i;
 25
 26	for (i = 0; i < fan->fps_count; i++) {
 27		if (fan->fps[i].control == control)
 28			return &fan->fps[i];
 29	}
 30
 31	return NULL;
 32}
 33
 34static umode_t acpi_fan_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
 35					 u32 attr, int channel)
 36{
 37	const struct acpi_fan *fan = drvdata;
 38	unsigned int i;
 39
 40	switch (type) {
 41	case hwmon_fan:
 42		switch (attr) {
 43		case hwmon_fan_input:
 44			return 0444;
 45		case hwmon_fan_target:
 46			/*
 47			 * When in fine grain control mode, not every fan control value
 48			 * has an associated fan performance state.
 49			 */
 50			if (fan->fif.fine_grain_ctrl)
 51				return 0;
 52
 53			return 0444;
 54		default:
 55			return 0;
 56		}
 57	case hwmon_power:
 58		switch (attr) {
 59		case hwmon_power_input:
 60			/*
 61			 * When in fine grain control mode, not every fan control value
 62			 * has an associated fan performance state.
 63			 */
 64			if (fan->fif.fine_grain_ctrl)
 65				return 0;
 66
 67			/*
 68			 * When all fan performance states contain no valid power data,
 69			 * when the associated attribute should not be created.
 70			 */
 71			for (i = 0; i < fan->fps_count; i++) {
 72				if (fan->fps[i].power != FAN_POWER_UNAVAILABLE)
 73					return 0444;
 74			}
 75
 76			return 0;
 77		default:
 78			return 0;
 79		}
 80	default:
 81		return 0;
 82	}
 83}
 84
 85static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
 86			       int channel, long *val)
 87{
 88	struct acpi_device *adev = to_acpi_device(dev->parent);
 89	struct acpi_fan *fan = dev_get_drvdata(dev);
 90	struct acpi_fan_fps *fps;
 91	struct acpi_fan_fst fst;
 92	int ret;
 93
 94	ret = acpi_fan_get_fst(adev, &fst);
 95	if (ret < 0)
 96		return ret;
 97
 98	switch (type) {
 99	case hwmon_fan:
100		switch (attr) {
101		case hwmon_fan_input:
102			if (fst.speed == FAN_SPEED_UNAVAILABLE)
103				return -ENODEV;
104
105			if (fst.speed > LONG_MAX)
106				return -EOVERFLOW;
107
108			*val = fst.speed;
109			return 0;
110		case hwmon_fan_target:
111			fps = acpi_fan_get_current_fps(fan, fst.control);
112			if (!fps)
113				return -EIO;
114
115			if (fps->speed > LONG_MAX)
116				return -EOVERFLOW;
117
118			*val = fps->speed;
119			return 0;
120		default:
121			return -EOPNOTSUPP;
122		}
123	case hwmon_power:
124		switch (attr) {
125		case hwmon_power_input:
126			fps = acpi_fan_get_current_fps(fan, fst.control);
127			if (!fps)
128				return -EIO;
129
130			if (fps->power == FAN_POWER_UNAVAILABLE)
131				return -ENODEV;
132
133			if (fps->power > LONG_MAX / MICROWATT_PER_MILLIWATT)
134				return -EOVERFLOW;
135
136			*val = fps->power * MICROWATT_PER_MILLIWATT;
137			return 0;
138		default:
139			return -EOPNOTSUPP;
140		}
141	default:
142		return -EOPNOTSUPP;
143	}
144}
145
146static const struct hwmon_ops acpi_fan_hwmon_ops = {
147	.is_visible = acpi_fan_hwmon_is_visible,
148	.read = acpi_fan_hwmon_read,
149};
150
151static const struct hwmon_channel_info * const acpi_fan_hwmon_info[] = {
152	HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_TARGET),
153	HWMON_CHANNEL_INFO(power, HWMON_P_INPUT),
154	NULL
155};
156
157static const struct hwmon_chip_info acpi_fan_hwmon_chip_info = {
158	.ops = &acpi_fan_hwmon_ops,
159	.info = acpi_fan_hwmon_info,
160};
161
162int devm_acpi_fan_create_hwmon(struct acpi_device *device)
163{
164	struct acpi_fan *fan = acpi_driver_data(device);
165	struct device *hdev;
166
167	hdev = devm_hwmon_device_register_with_info(&device->dev, "acpi_fan", fan,
168						    &acpi_fan_hwmon_chip_info, NULL);
169	return PTR_ERR_OR_ZERO(hdev);
170}