Skip to content

Commit 9b96f82

Browse files
Gerhard Engledergroeck
authored andcommitted
hwmon: Add KEBA fan controller support
The KEBA fan controller is found in the system FPGA of KEBA PLC devices. It detects if the fan is removed or blocked. For fans with tacho signal the monitoring of the speed of the fan is supported. It also supports to regulate the speed of fans with PWM input. The auxiliary device for this driver is instantiated by the cp500 misc driver. Signed-off-by: Gerhard Engleder <eg@keba.com> Link: https://lore.kernel.org/r/20250425194823.54664-1-gerhard@engleder-embedded.com [groeck: Added various missing "break;" statements] Signed-off-by: Guenter Roeck <linux@roeck-us.net>
1 parent 8fcefe7 commit 9b96f82

File tree

5 files changed

+297
-0
lines changed

5 files changed

+297
-0
lines changed

Documentation/hwmon/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ Hardware Monitoring Kernel Drivers
107107
k10temp
108108
k8temp
109109
kbatt
110+
kfan
110111
lan966x
111112
lineage-pem
112113
lm25066

Documentation/hwmon/kfan.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.. SPDX-License-Identifier: GPL-2.0
2+
3+
Kernel driver kfan
4+
==================
5+
6+
Supported chips:
7+
8+
* KEBA fan controller (IP core in FPGA)
9+
10+
Prefix: 'kfan'
11+
12+
Authors:
13+
14+
Gerhard Engleder <eg@keba.com>
15+
Petar Bojanic <boja@keba.com>
16+
17+
Description
18+
-----------
19+
20+
The KEBA fan controller is an IP core for FPGAs, which monitors the health
21+
and controls the speed of a fan. The fan is typically used to cool the CPU
22+
and the whole device. E.g., the CP500 FPGA includes this IP core to monitor
23+
and control the fan of PLCs and the corresponding cp500 driver creates an
24+
auxiliary device for the kfan driver.
25+
26+
This driver provides information about the fan health to user space.
27+
The user space shall be informed if the fan is removed or blocked.
28+
Additionally, the speed in RPM is reported for fans with tacho signal.
29+
30+
For fan control PWM is supported. For PWM 255 equals 100%. None-regulable
31+
fans can be turned on with PWM 255 and turned off with PWM 0.
32+
33+
====================== ==== ===================================================
34+
Attribute R/W Contents
35+
====================== ==== ===================================================
36+
fan1_fault R Fan fault
37+
fan1_input R Fan tachometer input (in RPM)
38+
pwm1 RW Fan target duty cycle (0..255)
39+
====================== ==== ===================================================

drivers/hwmon/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,16 @@ config SENSORS_KBATT
345345
This driver can also be built as a module. If so, the module
346346
will be called kbatt.
347347

348+
config SENSORS_KFAN
349+
tristate "KEBA fan controller support"
350+
depends on KEBA_CP500
351+
help
352+
This driver supports the fan controller found in KEBA system
353+
FPGA devices.
354+
355+
This driver can also be built as a module. If so, the module
356+
will be called kfan.
357+
348358
config SENSORS_FAM15H_POWER
349359
tristate "AMD Family 15h processor power"
350360
depends on X86 && PCI && CPU_SUP_AMD

drivers/hwmon/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ obj-$(CONFIG_SENSORS_JC42) += jc42.o
111111
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
112112
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
113113
obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
114+
obj-$(CONFIG_SENSORS_KFAN) += kfan.o
114115
obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
115116
obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o
116117
obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o

drivers/hwmon/kfan.c

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (C) 2025 KEBA Industrial Automation GmbH
4+
*
5+
* Driver for KEBA fan controller FPGA IP core
6+
*
7+
*/
8+
9+
#include <linux/hwmon.h>
10+
#include <linux/io.h>
11+
#include <linux/module.h>
12+
#include <linux/device.h>
13+
#include <linux/auxiliary_bus.h>
14+
#include <linux/misc/keba.h>
15+
16+
#define KFAN "kfan"
17+
18+
#define KFAN_CONTROL_REG 0x04
19+
20+
#define KFAN_STATUS_REG 0x08
21+
#define KFAN_STATUS_PRESENT 0x01
22+
#define KFAN_STATUS_REGULABLE 0x02
23+
#define KFAN_STATUS_TACHO 0x04
24+
#define KFAN_STATUS_BLOCKED 0x08
25+
26+
#define KFAN_TACHO_REG 0x0c
27+
28+
#define KFAN_DEFAULT_DIV 2
29+
30+
struct kfan {
31+
void __iomem *base;
32+
bool tacho;
33+
bool regulable;
34+
35+
/* hwmon API configuration */
36+
u32 fan_channel_config[2];
37+
struct hwmon_channel_info fan_info;
38+
u32 pwm_channel_config[2];
39+
struct hwmon_channel_info pwm_info;
40+
const struct hwmon_channel_info *info[3];
41+
struct hwmon_chip_info chip;
42+
};
43+
44+
static bool kfan_get_fault(struct kfan *kfan)
45+
{
46+
u8 status = ioread8(kfan->base + KFAN_STATUS_REG);
47+
48+
if (!(status & KFAN_STATUS_PRESENT))
49+
return true;
50+
51+
if (!kfan->tacho && (status & KFAN_STATUS_BLOCKED))
52+
return true;
53+
54+
return false;
55+
}
56+
57+
static unsigned int kfan_count_to_rpm(u16 count)
58+
{
59+
if (count == 0 || count == 0xffff)
60+
return 0;
61+
62+
return 5000000UL / (KFAN_DEFAULT_DIV * count);
63+
}
64+
65+
static unsigned int kfan_get_rpm(struct kfan *kfan)
66+
{
67+
unsigned int rpm;
68+
u16 count;
69+
70+
count = ioread16(kfan->base + KFAN_TACHO_REG);
71+
rpm = kfan_count_to_rpm(count);
72+
73+
return rpm;
74+
}
75+
76+
static unsigned int kfan_get_pwm(struct kfan *kfan)
77+
{
78+
return ioread8(kfan->base + KFAN_CONTROL_REG);
79+
}
80+
81+
static int kfan_set_pwm(struct kfan *kfan, long val)
82+
{
83+
if (val < 0 || val > 0xff)
84+
return -EINVAL;
85+
86+
/* if none-regulable, then only 0 or 0xff can be written */
87+
if (!kfan->regulable && val > 0)
88+
val = 0xff;
89+
90+
iowrite8(val, kfan->base + KFAN_CONTROL_REG);
91+
92+
return 0;
93+
}
94+
95+
static int kfan_write(struct device *dev, enum hwmon_sensor_types type,
96+
u32 attr, int channel, long val)
97+
{
98+
struct kfan *kfan = dev_get_drvdata(dev);
99+
100+
switch (type) {
101+
case hwmon_pwm:
102+
switch (attr) {
103+
case hwmon_pwm_input:
104+
return kfan_set_pwm(kfan, val);
105+
default:
106+
break;
107+
}
108+
break;
109+
default:
110+
break;
111+
}
112+
113+
return -EOPNOTSUPP;
114+
}
115+
116+
static int kfan_read(struct device *dev, enum hwmon_sensor_types type,
117+
u32 attr, int channel, long *val)
118+
{
119+
struct kfan *kfan = dev_get_drvdata(dev);
120+
121+
switch (type) {
122+
case hwmon_fan:
123+
switch (attr) {
124+
case hwmon_fan_fault:
125+
*val = kfan_get_fault(kfan);
126+
return 0;
127+
case hwmon_fan_input:
128+
*val = kfan_get_rpm(kfan);
129+
return 0;
130+
default:
131+
break;
132+
}
133+
break;
134+
case hwmon_pwm:
135+
switch (attr) {
136+
case hwmon_pwm_input:
137+
*val = kfan_get_pwm(kfan);
138+
return 0;
139+
default:
140+
break;
141+
}
142+
break;
143+
default:
144+
break;
145+
}
146+
147+
return -EOPNOTSUPP;
148+
}
149+
150+
static umode_t kfan_is_visible(const void *data, enum hwmon_sensor_types type,
151+
u32 attr, int channel)
152+
{
153+
switch (type) {
154+
case hwmon_fan:
155+
switch (attr) {
156+
case hwmon_fan_input:
157+
return 0444;
158+
case hwmon_fan_fault:
159+
return 0444;
160+
default:
161+
break;
162+
}
163+
break;
164+
case hwmon_pwm:
165+
switch (attr) {
166+
case hwmon_pwm_input:
167+
return 0644;
168+
default:
169+
break;
170+
}
171+
break;
172+
default:
173+
break;
174+
}
175+
176+
return 0;
177+
}
178+
179+
static const struct hwmon_ops kfan_hwmon_ops = {
180+
.is_visible = kfan_is_visible,
181+
.read = kfan_read,
182+
.write = kfan_write,
183+
};
184+
185+
static int kfan_probe(struct auxiliary_device *auxdev,
186+
const struct auxiliary_device_id *id)
187+
{
188+
struct keba_fan_auxdev *kfan_auxdev =
189+
container_of(auxdev, struct keba_fan_auxdev, auxdev);
190+
struct device *dev = &auxdev->dev;
191+
struct device *hwmon_dev;
192+
struct kfan *kfan;
193+
u8 status;
194+
195+
kfan = devm_kzalloc(dev, sizeof(*kfan), GFP_KERNEL);
196+
if (!kfan)
197+
return -ENOMEM;
198+
199+
kfan->base = devm_ioremap_resource(dev, &kfan_auxdev->io);
200+
if (IS_ERR(kfan->base))
201+
return PTR_ERR(kfan->base);
202+
203+
status = ioread8(kfan->base + KFAN_STATUS_REG);
204+
if (status & KFAN_STATUS_REGULABLE)
205+
kfan->regulable = true;
206+
if (status & KFAN_STATUS_TACHO)
207+
kfan->tacho = true;
208+
209+
/* fan */
210+
kfan->fan_channel_config[0] = HWMON_F_FAULT;
211+
if (kfan->tacho)
212+
kfan->fan_channel_config[0] |= HWMON_F_INPUT;
213+
kfan->fan_info.type = hwmon_fan;
214+
kfan->fan_info.config = kfan->fan_channel_config;
215+
kfan->info[0] = &kfan->fan_info;
216+
217+
/* PWM */
218+
kfan->pwm_channel_config[0] = HWMON_PWM_INPUT;
219+
kfan->pwm_info.type = hwmon_pwm;
220+
kfan->pwm_info.config = kfan->pwm_channel_config;
221+
kfan->info[1] = &kfan->pwm_info;
222+
223+
kfan->chip.ops = &kfan_hwmon_ops;
224+
kfan->chip.info = kfan->info;
225+
hwmon_dev = devm_hwmon_device_register_with_info(dev, KFAN, kfan,
226+
&kfan->chip, NULL);
227+
return PTR_ERR_OR_ZERO(hwmon_dev);
228+
}
229+
230+
static const struct auxiliary_device_id kfan_devtype_aux[] = {
231+
{ .name = "keba.fan" },
232+
{}
233+
};
234+
MODULE_DEVICE_TABLE(auxiliary, kfan_devtype_aux);
235+
236+
static struct auxiliary_driver kfan_driver_aux = {
237+
.name = KFAN,
238+
.id_table = kfan_devtype_aux,
239+
.probe = kfan_probe,
240+
};
241+
module_auxiliary_driver(kfan_driver_aux);
242+
243+
MODULE_AUTHOR("Petar Bojanic <boja@keba.com>");
244+
MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
245+
MODULE_DESCRIPTION("KEBA fan controller driver");
246+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)