]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
hwmon: Add KEBA fan controller support
authorGerhard Engleder <eg@keba.com>
Fri, 25 Apr 2025 19:48:23 +0000 (21:48 +0200)
committerGuenter Roeck <linux@roeck-us.net>
Tue, 13 May 2025 01:05:54 +0000 (18:05 -0700)
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>
Documentation/hwmon/index.rst
Documentation/hwmon/kfan.rst [new file with mode: 0644]
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/kfan.c [new file with mode: 0644]

index 946b8a266d8940ee31aec48f0357e8f4901aeb19..983aa0233e4a765d028a9936b7f79a58eb5647ad 100644 (file)
@@ -107,6 +107,7 @@ Hardware Monitoring Kernel Drivers
    k10temp
    k8temp
    kbatt
+   kfan
    lan966x
    lineage-pem
    lm25066
diff --git a/Documentation/hwmon/kfan.rst b/Documentation/hwmon/kfan.rst
new file mode 100644 (file)
index 0000000..ce02ddd
--- /dev/null
@@ -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)
+====================== ==== ===================================================
index 80e277448c71bd04ebda9784e430b730a8bba3a0..01b21fe55ccde2169ab41e074a6dcc256e4e7d8d 100644 (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
index 50df221f67c2fbeb8cf567839149da43d1b09cfb..c34a0ae658e0d76681b32a5aa69b46757eb261b0 100644 (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
diff --git a/drivers/hwmon/kfan.c b/drivers/hwmon/kfan.c
new file mode 100644 (file)
index 0000000..f353acb
--- /dev/null
@@ -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");