From cb1873c3270b9f0891a9bc0faebb45b23f3cb37b Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 22 May 2021 17:31:09 +1200 Subject: Add driver for cds9k fan block --- Makefile | 1 + README.md | 8 ++-- cds9k-fan.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 cds9k-fan.c diff --git a/Makefile b/Makefile index 8dd0872..38601bd 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ obj-m := cds9k-mfd-spi.o obj-m += cds9k-gpio.o obj-m += cds9k-led.o obj-m += cds9k-reset.o +obj-m += cds9k-fan.o obj-m += simple-reset-consumer.o SRC := $(shell pwd) diff --git a/README.md b/README.md index 69fde59..570080d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ care" and write as 0. ## IP Block: `cds9k-fan` - FIXME add description +The CDS9K fan control block allows PWM speed control of a single fan, and +readback of the fan's tachometer. ### Register 0x0: FAN\_PWM @@ -34,11 +35,12 @@ i.e. a linear scaling from 0-100% to 0x0-0xFF Mode: read-only -Read this register to determine the fan tachometer reading (FIXME units TBD). +Read this register to determine the fan tachometer reading in revolutions per +minute (RPM). | Bits | 15 - 0 | | ---- | -------- | -| Use | reserved | +| Use | fan\_rpm | ## IP Block: `cds9k-led` diff --git a/cds9k-fan.c b/cds9k-fan.c new file mode 100644 index 0000000..8a58aa0 --- /dev/null +++ b/cds9k-fan.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +#define FAN_DUTY 0x0 +#define FAN_TACH 0x1 + +struct cds9k_fan { + struct regmap *regmap; + u32 base; +}; + +static unsigned int cds9k_rpm_to_pwm(long rpm) +{ + static long max_rpm = 5000; + static unsigned int max_pwm = 255; + + rpm = clamp(rpm, (long)0, max_rpm); + + return max_pwm * rpm / max_rpm; +} + +static umode_t cds9k_fan_is_visible(const void *fan, enum hwmon_sensor_types type, u32 attr, int channel) +{ + if (channel != 0) + return 0; + + if (type == hwmon_fan && attr == hwmon_fan_input) + return 0444; + + if (type == hwmon_fan && attr == hwmon_fan_target) + return 0644; + + return 0; +} + +static int cds9k_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *value) +{ + int ret; + struct cds9k_fan *fan = dev_get_drvdata(dev); + u32 reg_offs; + unsigned int raw_value; + if (channel != 0 || type != hwmon_fan) + return -EINVAL; + + switch (attr) { + case hwmon_fan_target: + reg_offs = FAN_DUTY; + break; + case hwmon_fan_input: + reg_offs = FAN_TACH; + break; + default: + return -EINVAL; + } + + ret = regmap_read(fan->regmap, fan->base + reg_offs, &raw_value); + if (ret) + return ret; + + *value = raw_value; + return 0; +} + +static int cds9k_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long value) +{ + unsigned int target_pwm; + struct cds9k_fan *fan = dev_get_drvdata(dev); + + if (channel != 0 || type != hwmon_fan || attr != hwmon_fan_target) + return -EINVAL; + + target_pwm = cds9k_rpm_to_pwm(value); + return regmap_write(fan->regmap, fan->base + FAN_DUTY, target_pwm); +} + +static const struct hwmon_ops cds9k_fan_ops = { + .is_visible = cds9k_fan_is_visible, + .read = cds9k_fan_read, + .write = cds9k_fan_write +}; + +static const struct hwmon_channel_info *cds9k_fan_channel_info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), + HWMON_CHANNEL_INFO(fan, HWMON_F_TARGET), + NULL +}; + +static const struct hwmon_chip_info cds9k_fan_chip_info = { + .ops = &cds9k_fan_ops, + .info = cds9k_fan_channel_info +}; + +static int cds9k_fan_probe(struct platform_device *pdev) +{ + struct device *hwmon_dev; + int ret; + struct cds9k_fan *fan; + const char *label; + + fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL); + if (!fan) + return -ENOMEM; + + ret = device_property_read_u32(&pdev->dev, "reg", &fan->base); + ret |= device_property_read_string(&pdev->dev, "label", &label); + if (ret) + return -EINVAL; + + if (!pdev->dev.parent) + return -ENODEV; + + fan->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!fan->regmap) + return -ENODEV; + + dev_info(&pdev->dev, "registering hwmon device\n"); + hwmon_dev = devm_hwmon_device_register_with_info( + &pdev->dev, + label, + fan, + &cds9k_fan_chip_info, + NULL + ); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct of_device_id cds9k_fan_of_match[] = { + { .compatible = "david,cds9k-fan" }, + {} +}; +MODULE_DEVICE_TABLE(of, cds9k_fan_of_match); + +static struct platform_driver cds9k_fan = { + .driver = { + .name = "cds9k-fan", + .of_match_table = cds9k_fan_of_match, + }, + .probe = cds9k_fan_probe, +}; +module_platform_driver(cds9k_fan); + +MODULE_AUTHOR("David Phillips "); +MODULE_DESCRIPTION("LED driver for the CDS9K board controller"); +MODULE_LICENSE("GPL v2"); -- cgit v1.1