linux/drivers/hwmon/kfan.c

247 lines
5 KiB
C
Raw Permalink Normal View History

// 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");