Linux之PWM风扇驱动
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
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 DD'Notes!
评论