aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Phillips <david@yeah.nah.nz>2021-05-22 17:31:09 +1200
committerDavid Phillips <david@yeah.nah.nz>2021-05-22 17:31:09 +1200
commitcb1873c3270b9f0891a9bc0faebb45b23f3cb37b (patch)
tree6ac01f4a25a2c4b623f0cf01bcbef37b275a5e72
parent9ea2d46ebe5713cbdabb3ae028bb2fc27e93c00d (diff)
downloadcds9k-cb1873c3270b9f0891a9bc0faebb45b23f3cb37b.tar.xz
Add driver for cds9k fan block
-rw-r--r--Makefile1
-rw-r--r--README.md8
-rw-r--r--cds9k-fan.c148
3 files changed, 154 insertions, 3 deletions
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 <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/hwmon.h>
+
+#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 <david@yeah.nah.nz>");
+MODULE_DESCRIPTION("LED driver for the CDS9K board controller");
+MODULE_LICENSE("GPL v2");