Linux之PWM风扇驱动

背景

该驱动主要是用于PWM模块去驱动散热风扇,使用通用的PWM接口,只要主控Soc的PWM模块使用的是标准通用的PWM框架,则可以适用于任何主控Soc,与具体的硬件无关。

用户空间接口

驱动程序给用户空间提供了相应hwmon的sysfs接口: /sys/class/hwmon/hwmon-x/,主要是对PWM的读写操作和风扇转速的读操作:


static int pwm_fan_write(struct device *dev, enum hwmon_sensor_types type,
			 u32 attr, int channel, long val)
{
    struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
    int ret;

    if (val < 0 || val > MAX_PWM)
        return -EINVAL;

    ret = __set_pwm(ctx, val);
    if (ret)
        return ret;

    pwm_fan_update_state(ctx, val);
    return 0;
}

static int pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
			u32 attr, int channel, long *val)
{
    struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);

    switch (type) {
    case hwmon_pwm:
        *val = ctx->pwm_value;
        return 0;

    case hwmon_fan:
        *val = ctx->tachs[channel].rpm;
        return 0;

    default:
        return -ENOTSUPP;
    }
}

...

static const struct hwmon_ops pwm_fan_hwmon_ops = {
    .is_visible = pwm_fan_is_visible,
    .read = pwm_fan_read,
    .write = pwm_fan_write,
};

其中,风扇的转速主要是通过在单位时间内采样转速计中断来计算:

static void sample_timer(struct timer_list *t)
{
    struct pwm_fan_ctx *ctx = from_timer(ctx, t, rpm_timer);
    unsigned int delta = ktime_ms_delta(ktime_get(), ctx->sample_start);
    int i;

    if (delta) {
        for (i = 0; i < ctx->tach_count; i++) {
            struct pwm_fan_tach *tach = &ctx->tachs[i];
            int pulses;

            pulses = atomic_read(&tach->pulses);
            atomic_sub(pulses, &tach->pulses);
            tach->rpm = (unsigned int)(pulses * 1000 * 60) /
                (tach->pulses_per_revolution * delta);
        }

        ctx->sample_start = ktime_get();
    }

    mod_timer(&ctx->rpm_timer, jiffies + HZ);
}

风扇控制逻辑

标准方案

标准通用的方式是通过注册热管理的cooling设备来管理控制风扇,使用thermal框架实现,thermal子系统比较庞大,在这不做过多介绍了。


static const struct thermal_cooling_device_ops pwm_fan_cooling_ops = {
    .get_max_state = pwm_fan_get_max_state,
    .get_cur_state = pwm_fan_get_cur_state,
    .set_cur_state = pwm_fan_set_cur_state,
};

static int pwm_fan_probe(struct platform_device *pdev)
{
    ...
    if (IS_ENABLED(CONFIG_THERMAL)) {
        cdev = devm_thermal_of_cooling_device_register(dev,
            dev->of_node, "pwm-fan", ctx, &pwm_fan_cooling_ops);
        if (IS_ERR(cdev)) {
            ret = PTR_ERR(cdev);
            dev_err(dev,
                "Failed to register pwm-fan as cooling device: %d\n",
                ret);
            return ret;
        }
        ctx->cdev = cdev;
    }
    ...
}

相应的dts配置如下:

fan0: pwm-fan {
    compatible = "pwm-fan";
    #cooling-cells = <2>;
    pwms = <&pwm 0 10000 0>;
    cooling-levels = <0 102 170 230>;
};

thermal-zones {
    cpu_thermal: cpu-thermal {
        thermal-sensors = <&tmu 0>;
        polling-delay-passive = <0>;
        polling-delay = <0>;
        trips {
            cpu_alert1: cpu-alert1 {
                temperature = <100000>; /* millicelsius */
                hysteresis = <2000>; /* millicelsius */
                type = "passive";
            };
        };
        cooling-maps {
            map0 {
                trip = <&cpu_alert1>;
                cooling-device = <&fan0 0 1>;
            };
        };
    };
}

RK方案

不同的硬件平台可能会有些差异和修改,比如RK3588上面,就直接使用的通知链来,接收温度变化的通知,从而来通过调节pwm来闭环控制风扇的转速:


static int pwm_fan_thermal_notifier_call(struct notifier_block *nb,
					 unsigned long event, void *data)
{
    struct pwm_fan_ctx *ctx = container_of(nb, struct pwm_fan_ctx, thermal_nb);
    struct system_monitor_event_data *event_data = data;
    int state, ret;

    if (event != SYSTEM_MONITOR_CHANGE_TEMP)
        return NOTIFY_OK;

    state = pwm_fan_temp_to_state(ctx, event_data->temp);
    if (state > ctx->pwm_fan_max_state)
        return NOTIFY_BAD;
    if (state == ctx->pwm_fan_state)
        return NOTIFY_OK;

    ret = __set_pwm(ctx, ctx->pwm_fan_cooling_levels[state]);
    if (ret)
        return NOTIFY_BAD;

    ctx->pwm_fan_state = state;

    return NOTIFY_OK;
}

static int pwm_fan_register_thermal_notifier(struct device *dev,
					     struct pwm_fan_ctx *ctx)
{
    if (pwm_fan_get_thermal_trips(dev, "rockchip,temp-trips",
                        &ctx->thermal_trips))
        return -EINVAL;

    ctx->thermal_nb.notifier_call = pwm_fan_thermal_notifier_call;

    return rockchip_system_monitor_register_notifier(&ctx->thermal_nb);
}

static int pwm_fan_probe(struct platform_device *pdev)
{
    ...

    if (IS_REACHABLE(CONFIG_ROCKCHIP_SYSTEM_MONITOR) &&
        of_find_property(dev->of_node, "rockchip,temp-trips", NULL)) {
        ret = pwm_fan_register_thermal_notifier(dev, ctx);
        if (ret)
            dev_err(dev, "Failed to register thermal notifier: %d\n", ret);
        else
            ctx->thermal_notifier_is_ok = true;
        return 0;
    }

    ...
}

相应的dts配置如下:

fan: pwm-fan {
    compatible = "pwm-fan";
    #cooling-cells = <2>;
    pwms = <&pwm9 0 50000 0>;
    cooling-levels = <0 50 100 150 200 255>;
    rockchip,temp-trips = <
            50000   1
            55000   2
            60000   3
            65000   4
            70000   5
    >;
};

参考

内核源码:

  • Documentation/devicetree/bindings/hwmon/pwm-fan.txt
  • Documentation/hwmon/pwm-fan.rst
  • drivers/hwmon/pwm-fan.c