hwmon: (pwm-fan) Add fan speed register support
authorPhil Elwell <phil@raspberrypi.com>
Tue, 11 Jul 2023 09:17:29 +0000 (10:17 +0100)
committerDom Cobley <popcornmix@gmail.com>
Mon, 19 Feb 2024 11:34:52 +0000 (11:34 +0000)
Some platforms include a fan-speed register that reports RPM directly
as an alternative to counting interrupts from the fan tachometer input.
Add support for reading a register at a given offset (rpm-offset) within
a block declared in another node (rpm-regmap). This indirection allows
the usual address mapping to be performed, and for address sharing with
another driver.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>
drivers/hwmon/pwm-fan.c

index 6e4516c..4d6f26e 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/of.h>
+#include <linux/of_address.h>
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
 #include <linux/regulator/consumer.h>
@@ -51,6 +52,9 @@ struct pwm_fan_ctx {
        ktime_t sample_start;
        struct timer_list rpm_timer;
 
+       void __iomem *rpm_regbase;
+       unsigned int rpm_offset;
+
        unsigned int pwm_value;
        unsigned int pwm_fan_state;
        unsigned int pwm_fan_max_state;
@@ -61,6 +65,10 @@ struct pwm_fan_ctx {
        struct hwmon_channel_info fan_channel;
 };
 
+static const u32 rpm_reg_channel_config[] = {
+       HWMON_F_INPUT, 0
+};
+
 /* This handler assumes self resetting edge triggered interrupt. */
 static irqreturn_t pulse_handler(int irq, void *dev_id)
 {
@@ -335,7 +343,10 @@ static int pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
                }
                return -EOPNOTSUPP;
        case hwmon_fan:
-               *val = ctx->tachs[channel].rpm;
+               if (ctx->rpm_regbase)
+                       *val = (long)readl(ctx->rpm_regbase + ctx->rpm_offset);
+               else
+                       *val = ctx->tachs[channel].rpm;
                return 0;
 
        default:
@@ -470,6 +481,7 @@ static void pwm_fan_cleanup(void *__ctx)
        /* Switch off everything */
        ctx->enable_mode = pwm_disable_reg_disable;
        pwm_fan_power_off(ctx);
+       iounmap(ctx->rpm_regbase);
 }
 
 static int pwm_fan_probe(struct platform_device *pdev)
@@ -542,10 +554,23 @@ static int pwm_fan_probe(struct platform_device *pdev)
                return ret;
 
        ctx->tach_count = platform_irq_count(pdev);
+       if (ctx->tach_count == 0) {
+               struct device_node *rpm_node;
+
+               rpm_node = of_parse_phandle(dev->of_node, "rpm-regmap", 0);
+               if (rpm_node)
+                       ctx->rpm_regbase = of_iomap(rpm_node, 0);
+       }
+
        if (ctx->tach_count < 0)
                return dev_err_probe(dev, ctx->tach_count,
                                     "Could not get number of fan tachometer inputs\n");
-       dev_dbg(dev, "%d fan tachometer inputs\n", ctx->tach_count);
+       if (IS_ERR(ctx->rpm_regbase))
+               return dev_err_probe(dev, PTR_ERR(ctx->rpm_regbase),
+                                    "Could not get rpm reg\n");
+
+       dev_dbg(dev, "%d fan tachometer inputs, %d rpm regmap\n", ctx->tach_count,
+               !!ctx->rpm_regbase);
 
        if (ctx->tach_count) {
                channel_count++;        /* We also have a FAN channel. */
@@ -562,12 +587,24 @@ static int pwm_fan_probe(struct platform_device *pdev)
                if (!fan_channel_config)
                        return -ENOMEM;
                ctx->fan_channel.config = fan_channel_config;
+       } else if (ctx->rpm_regbase) {
+               channel_count++;        /* We also have a FAN channel. */
+               ctx->fan_channel.type = hwmon_fan;
+               ctx->fan_channel.config = rpm_reg_channel_config;
+
+               if (of_property_read_u32(pdev->dev.of_node, "rpm-offset", &ctx->rpm_offset)) {
+                       dev_err(&pdev->dev, "unable to read 'rpm-offset'");
+                       ret = -EINVAL;
+                       goto error;
+               }
        }
 
        channels = devm_kcalloc(dev, channel_count + 1,
                                sizeof(struct hwmon_channel_info *), GFP_KERNEL);
-       if (!channels)
-               return -ENOMEM;
+       if (!channels) {
+               ret = -ENOMEM;
+               goto error;
+       }
 
        channels[0] = HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE);
 
@@ -610,6 +647,8 @@ static int pwm_fan_probe(struct platform_device *pdev)
                mod_timer(&ctx->rpm_timer, jiffies + HZ);
 
                channels[1] = &ctx->fan_channel;
+       } else if (ctx->rpm_regbase) {
+               channels[1] = &ctx->fan_channel;
        }
 
        ctx->info.ops = &pwm_fan_hwmon_ops;
@@ -619,12 +658,13 @@ static int pwm_fan_probe(struct platform_device *pdev)
                                                     ctx, &ctx->info, NULL);
        if (IS_ERR(hwmon)) {
                dev_err(dev, "Failed to register hwmon device\n");
-               return PTR_ERR(hwmon);
+               ret = PTR_ERR(hwmon);
+               goto error;
        }
 
        ret = pwm_fan_of_get_cooling_data(dev, ctx);
        if (ret)
-               return ret;
+               goto error;
 
        ctx->pwm_fan_state = ctx->pwm_fan_max_state;
        if (IS_ENABLED(CONFIG_THERMAL)) {
@@ -635,12 +675,17 @@ static int pwm_fan_probe(struct platform_device *pdev)
                        dev_err(dev,
                                "Failed to register pwm-fan as cooling device: %d\n",
                                ret);
-                       return ret;
+                       goto error;
                }
                ctx->cdev = cdev;
        }
 
        return 0;
+
+error:
+       if (ctx->rpm_regbase)
+               iounmap(ctx->rpm_regbase);
+       return ret;
 }
 
 static void pwm_fan_shutdown(struct platform_device *pdev)