mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

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>
246 lines
5 KiB
C
246 lines
5 KiB
C
// 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");
|