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>
This commit is contained in:
Gerhard Engleder 2025-04-25 21:48:23 +02:00 committed by Guenter Roeck
parent 8fcefe7812
commit 9b96f82c78
5 changed files with 297 additions and 0 deletions

View File

@ -107,6 +107,7 @@ Hardware Monitoring Kernel Drivers
k10temp
k8temp
kbatt
kfan
lan966x
lineage-pem
lm25066

View File

@ -0,0 +1,39 @@
.. SPDX-License-Identifier: GPL-2.0
Kernel driver kfan
==================
Supported chips:
* KEBA fan controller (IP core in FPGA)
Prefix: 'kfan'
Authors:
Gerhard Engleder <eg@keba.com>
Petar Bojanic <boja@keba.com>
Description
-----------
The KEBA fan controller is an IP core for FPGAs, which monitors the health
and controls the speed of a fan. The fan is typically used to cool the CPU
and the whole device. E.g., the CP500 FPGA includes this IP core to monitor
and control the fan of PLCs and the corresponding cp500 driver creates an
auxiliary device for the kfan driver.
This driver provides information about the fan health to user space.
The user space shall be informed if the fan is removed or blocked.
Additionally, the speed in RPM is reported for fans with tacho signal.
For fan control PWM is supported. For PWM 255 equals 100%. None-regulable
fans can be turned on with PWM 255 and turned off with PWM 0.
====================== ==== ===================================================
Attribute R/W Contents
====================== ==== ===================================================
fan1_fault R Fan fault
fan1_input R Fan tachometer input (in RPM)
pwm1 RW Fan target duty cycle (0..255)
====================== ==== ===================================================

View File

@ -345,6 +345,16 @@ config SENSORS_KBATT
This driver can also be built as a module. If so, the module
will be called kbatt.
config SENSORS_KFAN
tristate "KEBA fan controller support"
depends on KEBA_CP500
help
This driver supports the fan controller found in KEBA system
FPGA devices.
This driver can also be built as a module. If so, the module
will be called kfan.
config SENSORS_FAM15H_POWER
tristate "AMD Family 15h processor power"
depends on X86 && PCI && CPU_SUP_AMD

View File

@ -111,6 +111,7 @@ obj-$(CONFIG_SENSORS_JC42) += jc42.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
obj-$(CONFIG_SENSORS_KFAN) += kfan.o
obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o
obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o

246
drivers/hwmon/kfan.c Normal file
View File

@ -0,0 +1,246 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025 KEBA Industrial Automation GmbH
*
* Driver for KEBA fan controller FPGA IP core
*
*/
#include <linux/hwmon.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/auxiliary_bus.h>
#include <linux/misc/keba.h>
#define KFAN "kfan"
#define KFAN_CONTROL_REG 0x04
#define KFAN_STATUS_REG 0x08
#define KFAN_STATUS_PRESENT 0x01
#define KFAN_STATUS_REGULABLE 0x02
#define KFAN_STATUS_TACHO 0x04
#define KFAN_STATUS_BLOCKED 0x08
#define KFAN_TACHO_REG 0x0c
#define KFAN_DEFAULT_DIV 2
struct kfan {
void __iomem *base;
bool tacho;
bool regulable;
/* hwmon API configuration */
u32 fan_channel_config[2];
struct hwmon_channel_info fan_info;
u32 pwm_channel_config[2];
struct hwmon_channel_info pwm_info;
const struct hwmon_channel_info *info[3];
struct hwmon_chip_info chip;
};
static bool kfan_get_fault(struct kfan *kfan)
{
u8 status = ioread8(kfan->base + KFAN_STATUS_REG);
if (!(status & KFAN_STATUS_PRESENT))
return true;
if (!kfan->tacho && (status & KFAN_STATUS_BLOCKED))
return true;
return false;
}
static unsigned int kfan_count_to_rpm(u16 count)
{
if (count == 0 || count == 0xffff)
return 0;
return 5000000UL / (KFAN_DEFAULT_DIV * count);
}
static unsigned int kfan_get_rpm(struct kfan *kfan)
{
unsigned int rpm;
u16 count;
count = ioread16(kfan->base + KFAN_TACHO_REG);
rpm = kfan_count_to_rpm(count);
return rpm;
}
static unsigned int kfan_get_pwm(struct kfan *kfan)
{
return ioread8(kfan->base + KFAN_CONTROL_REG);
}
static int kfan_set_pwm(struct kfan *kfan, long val)
{
if (val < 0 || val > 0xff)
return -EINVAL;
/* if none-regulable, then only 0 or 0xff can be written */
if (!kfan->regulable && val > 0)
val = 0xff;
iowrite8(val, kfan->base + KFAN_CONTROL_REG);
return 0;
}
static int kfan_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct kfan *kfan = dev_get_drvdata(dev);
switch (type) {
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
return kfan_set_pwm(kfan, val);
default:
break;
}
break;
default:
break;
}
return -EOPNOTSUPP;
}
static int kfan_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct kfan *kfan = dev_get_drvdata(dev);
switch (type) {
case hwmon_fan:
switch (attr) {
case hwmon_fan_fault:
*val = kfan_get_fault(kfan);
return 0;
case hwmon_fan_input:
*val = kfan_get_rpm(kfan);
return 0;
default:
break;
}
break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
*val = kfan_get_pwm(kfan);
return 0;
default:
break;
}
break;
default:
break;
}
return -EOPNOTSUPP;
}
static umode_t kfan_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_fan:
switch (attr) {
case hwmon_fan_input:
return 0444;
case hwmon_fan_fault:
return 0444;
default:
break;
}
break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
return 0644;
default:
break;
}
break;
default:
break;
}
return 0;
}
static const struct hwmon_ops kfan_hwmon_ops = {
.is_visible = kfan_is_visible,
.read = kfan_read,
.write = kfan_write,
};
static int kfan_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
struct keba_fan_auxdev *kfan_auxdev =
container_of(auxdev, struct keba_fan_auxdev, auxdev);
struct device *dev = &auxdev->dev;
struct device *hwmon_dev;
struct kfan *kfan;
u8 status;
kfan = devm_kzalloc(dev, sizeof(*kfan), GFP_KERNEL);
if (!kfan)
return -ENOMEM;
kfan->base = devm_ioremap_resource(dev, &kfan_auxdev->io);
if (IS_ERR(kfan->base))
return PTR_ERR(kfan->base);
status = ioread8(kfan->base + KFAN_STATUS_REG);
if (status & KFAN_STATUS_REGULABLE)
kfan->regulable = true;
if (status & KFAN_STATUS_TACHO)
kfan->tacho = true;
/* fan */
kfan->fan_channel_config[0] = HWMON_F_FAULT;
if (kfan->tacho)
kfan->fan_channel_config[0] |= HWMON_F_INPUT;
kfan->fan_info.type = hwmon_fan;
kfan->fan_info.config = kfan->fan_channel_config;
kfan->info[0] = &kfan->fan_info;
/* PWM */
kfan->pwm_channel_config[0] = HWMON_PWM_INPUT;
kfan->pwm_info.type = hwmon_pwm;
kfan->pwm_info.config = kfan->pwm_channel_config;
kfan->info[1] = &kfan->pwm_info;
kfan->chip.ops = &kfan_hwmon_ops;
kfan->chip.info = kfan->info;
hwmon_dev = devm_hwmon_device_register_with_info(dev, KFAN, kfan,
&kfan->chip, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct auxiliary_device_id kfan_devtype_aux[] = {
{ .name = "keba.fan" },
{}
};
MODULE_DEVICE_TABLE(auxiliary, kfan_devtype_aux);
static struct auxiliary_driver kfan_driver_aux = {
.name = KFAN,
.id_table = kfan_devtype_aux,
.probe = kfan_probe,
};
module_auxiliary_driver(kfan_driver_aux);
MODULE_AUTHOR("Petar Bojanic <boja@keba.com>");
MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
MODULE_DESCRIPTION("KEBA fan controller driver");
MODULE_LICENSE("GPL");