Commit 9b96f82c authored by Gerhard Engleder's avatar Gerhard Engleder Committed by Guenter Roeck
Browse files

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: default avatarGerhard 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: default avatarGuenter Roeck <linux@roeck-us.net>
parent 8fcefe78
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@ Hardware Monitoring Kernel Drivers
   k10temp
   k8temp
   kbatt
   kfan
   lan966x
   lineage-pem
   lm25066
+39 −0
Original line number Diff line number Diff line
.. 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)
====================== ==== ===================================================
+10 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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

drivers/hwmon/kfan.c

0 → 100644
+246 −0
Original line number Diff line number Diff line
// 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");