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 /* Only acpi4 fans support fan control. */
47 if (!fan->acpi4)
48 return 0;
49
50 /*
51 * When in fine grain control mode, not every fan control value
52 * has an associated fan performance state.
53 */
54 if (fan->fif.fine_grain_ctrl)
55 return 0;
56
57 return 0444;
58 default:
59 return 0;
60 }
61 case hwmon_power:
62 switch (attr) {
63 case hwmon_power_input:
64 /* Only acpi4 fans support fan control. */
65 if (!fan->acpi4)
66 return 0;
67
68 /*
69 * When in fine grain control mode, not every fan control value
70 * has an associated fan performance state.
71 */
72 if (fan->fif.fine_grain_ctrl)
73 return 0;
74
75 /*
76 * When all fan performance states contain no valid power data,
77 * when the associated attribute should not be created.
78 */
79 for (i = 0; i < fan->fps_count; i++) {
80 if (fan->fps[i].power != FAN_POWER_UNAVAILABLE)
81 return 0444;
82 }
83
84 return 0;
85 default:
86 return 0;
87 }
88 default:
89 return 0;
90 }
91}
92
93static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
94 int channel, long *val)
95{
96 struct acpi_device *adev = to_acpi_device(dev->parent);
97 struct acpi_fan *fan = dev_get_drvdata(dev);
98 struct acpi_fan_fps *fps;
99 struct acpi_fan_fst fst;
100 int ret;
101
102 ret = acpi_fan_get_fst(device: adev, fst: &fst);
103 if (ret < 0)
104 return ret;
105
106 switch (type) {
107 case hwmon_fan:
108 switch (attr) {
109 case hwmon_fan_input:
110 if (fst.speed == FAN_SPEED_UNAVAILABLE)
111 return -ENODEV;
112
113 if (fst.speed > LONG_MAX)
114 return -EOVERFLOW;
115
116 *val = fst.speed;
117 return 0;
118 case hwmon_fan_target:
119 fps = acpi_fan_get_current_fps(fan, control: fst.control);
120 if (!fps)
121 return -EIO;
122
123 if (fps->speed > LONG_MAX)
124 return -EOVERFLOW;
125
126 *val = fps->speed;
127 return 0;
128 default:
129 return -EOPNOTSUPP;
130 }
131 case hwmon_power:
132 switch (attr) {
133 case hwmon_power_input:
134 fps = acpi_fan_get_current_fps(fan, control: fst.control);
135 if (!fps)
136 return -EIO;
137
138 if (fps->power == FAN_POWER_UNAVAILABLE)
139 return -ENODEV;
140
141 if (fps->power > LONG_MAX / MICROWATT_PER_MILLIWATT)
142 return -EOVERFLOW;
143
144 *val = fps->power * MICROWATT_PER_MILLIWATT;
145 return 0;
146 default:
147 return -EOPNOTSUPP;
148 }
149 default:
150 return -EOPNOTSUPP;
151 }
152}
153
154static const struct hwmon_ops acpi_fan_hwmon_ops = {
155 .is_visible = acpi_fan_hwmon_is_visible,
156 .read = acpi_fan_hwmon_read,
157};
158
159static const struct hwmon_channel_info * const acpi_fan_hwmon_info[] = {
160 HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_TARGET),
161 HWMON_CHANNEL_INFO(power, HWMON_P_INPUT),
162 NULL
163};
164
165static const struct hwmon_chip_info acpi_fan_hwmon_chip_info = {
166 .ops = &acpi_fan_hwmon_ops,
167 .info = acpi_fan_hwmon_info,
168};
169
170int devm_acpi_fan_create_hwmon(struct acpi_device *device)
171{
172 struct acpi_fan *fan = acpi_driver_data(d: device);
173 struct device *hdev;
174
175 hdev = devm_hwmon_device_register_with_info(dev: &device->dev, name: "acpi_fan", drvdata: fan,
176 info: &acpi_fan_hwmon_chip_info, NULL);
177 return PTR_ERR_OR_ZERO(ptr: hdev);
178}
179