// SPDX-License-Identifier: GPL-2.0-or-later /* * hwmon interface for the ACPI Fan driver. * * Copyright (C) 2024 Armin Wolf */ #include #include #include #include #include #include #include #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); }