mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
247 lines
5 KiB
C
247 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");
|