Linux pwm_fan 风扇驱动

  • Post author:
  • Post category:linux




前言

本文分析风扇驱动,驱动主要功能是控制以及计算风扇转速,添加进Linux Thermal 核心层;



设备树

常用设备树如下:

	pwm-fan {
		compatible = "pwm-fan";
		cooling-min-state = <0>;
		cooling-max-state = <3>;
		#cooling-cells = <2>;
		pwms = <&sl28cpld_pwm0 0 4000000>;
		cooling-levels = <1 128 192 255>;
	};

根据文档 pwm-fan.txt 可知字段含义:

compatible : “pwm-fan” 与驱动匹配名字;

pwms : 控制风扇使用的 PWM;

cooling-levels : PWM duty周期值,0 – 255,与 thermal 的 cooling states 有关;


thermal 模块相关资料

cooling-min-state

cooling-max-state : 最小最大状态

还有一些可选配置:

fan-supply: 电源适配器相关的设置

interrupts : 指定的中断,用来计算风扇转速的

pulses-per-revolution: 将每转风扇的转速表脉冲定义为整数,这个值必须大于0;



源码分析

static const struct of_device_id of_pwm_fan_match[] = {
	// 用来匹配设备树的属性
	{ .compatible = "pwm-fan", },
	// 结尾空字段,必须
	{},
};
MODULE_DEVICE_TABLE(of, of_pwm_fan_match);

static struct platform_driver pwm_fan_driver = {
	.probe		= pwm_fan_probe,
	.shutdown	= pwm_fan_shutdown,
	.driver	= {
		.name		= "pwm-fan",
		.pm		= &pwm_fan_pm,
		.of_match_table	= of_pwm_fan_match,
	},
};

驱动加载,与设备树匹配,会调用probe函数


static int pwm_fan_probe(struct platform_device *pdev)
{
	struct thermal_cooling_device *cdev;
	struct device *dev = &pdev->dev;
	struct pwm_fan_ctx *ctx;
	struct device *hwmon;
	int ret;
	struct pwm_state state = { };
	u32 ppr = 2;
	// 分配结构体内存,devm_ 函数族,内存自动释放,不用再手动显示释放了
	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
	if (!ctx)
		return -ENOMEM;
	// 互斥锁初始化
	mutex_init(&ctx->lock);
	// 获取设备树中指定的 PWM节点
	ctx->pwm = devm_of_pwm_get(dev, dev->of_node, NULL);
	if (IS_ERR(ctx->pwm))
		return dev_err_probe(dev, PTR_ERR(ctx->pwm), "Could not get PWM\n");
	// 设置驱动私有数据,以后可以通过 pdev 得到 ctx
	platform_set_drvdata(pdev, ctx);
	// 获取中断号,用来计算转速
	ctx->irq = platform_get_irq_optional(pdev, 0);
	if (ctx->irq == -EPROBE_DEFER)
		return ctx->irq;
	// 获取电源相关节点
	ctx->reg_en = devm_regulator_get_optional(dev, "fan");
	if (IS_ERR(ctx->reg_en)) {
		if (PTR_ERR(ctx->reg_en) != -ENODEV)
			return PTR_ERR(ctx->reg_en);
		// 没有置空
		ctx->reg_en = NULL;
	} else {
		// 成功就使能,供电
		ret = regulator_enable(ctx->reg_en);
		if (ret) {
			dev_err(dev, "Failed to enable fan supply: %d\n", ret);
			return ret;
		}
		ret = devm_add_action_or_reset(dev, pwm_fan_regulator_disable,
					       ctx->reg_en);
		if (ret)
			return ret;
	}
	// pwm 初始值,最大转速 255
	ctx->pwm_value = MAX_PWM;
	// 根据 pwm 设备中的 args 参数设置 pwm 中的 state
	pwm_init_state(ctx->pwm, &state);
	/*
	 * __set_pwm assumes that MAX_PWM * (period - 1) fits into an unsigned
	 * long. Check this here to prevent the fan running at a too low
	 * frequency.
	 */
	 // 检查周期频率
	if (state.period > ULONG_MAX / MAX_PWM + 1) {
		dev_err(dev, "Configured period too big\n");
		return -EINVAL;
	}

	/* Set duty cycle to maximum allowed and enable PWM output */
	// state 指向的是 pwm->state
	state.duty_cycle = ctx->pwm->args.period - 1;
	state.enabled = true;
	// 此函数会调用 pwm 的 apply函数, 进行寄存器配置,配置周期频率等
	ret = pwm_apply_state(ctx->pwm, &state);
	if (ret) {
		dev_err(dev, "Failed to configure PWM: %d\n", ret);
		return ret;
	}
	// 初始化定时器,用于计算风扇转速,处理函数 sample_timer
	timer_setup(&ctx->rpm_timer, sample_timer, 0);
	// 复位操作
	ret = devm_add_action_or_reset(dev, pwm_fan_pwm_disable, ctx);
	if (ret)
		return ret;
	// 获取设备树中的 pulses-per-revolution 参数,如果设置了
	of_property_read_u32(dev->of_node, "pulses-per-revolution", &ppr);
	ctx->pulses_per_revolution = ppr;
	if (!ctx->pulses_per_revolution) {
		dev_err(dev, "pulses-per-revolution can't be zero.\n");
		return -EINVAL;
	}

	if (ctx->irq > 0) {
		// 如果获取中断号成功,注册中断服务程序 pulse_handler
		ret = devm_request_irq(dev, ctx->irq, pulse_handler, 0,
				       pdev->name, ctx);
		if (ret) {
			dev_err(dev, "Failed to request interrupt: %d\n", ret);
			return ret;
		}
		// 记录初始时间值
		ctx->sample_start = ktime_get();
		// 定时器周期一秒,一秒后触发,调用处理函数 sample_timer
		mod_timer(&ctx->rpm_timer, jiffies + HZ);
	}
	// 注册 hwmon 设备,会在文件系统中 /sys/class/hwmon/  生成文件 
	hwmon = devm_hwmon_device_register_with_groups(dev, "pwmfan",
						       ctx, pwm_fan_groups);
	if (IS_ERR(hwmon)) {
		dev_err(dev, "Failed to register hwmon device\n");
		return PTR_ERR(hwmon);
	}
	// 获取设备树中指定的 cooling-levels
	ret = pwm_fan_of_get_cooling_data(dev, ctx);
	if (ret)
		return ret;
	// 设置风扇为最大状态
	ctx->pwm_fan_state = ctx->pwm_fan_max_state;
	if (IS_ENABLED(CONFIG_THERMAL)) {
		// 注册风扇为 cooling device,后面可以与温度传感器结合,根据具体温度设置不同的转速状态
		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;
		thermal_cdev_update(cdev);
	}

	return 0;
}

Linux thermal 使用的操作接口,对 ctx->pwm_fan_state 进行读写操作:

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 irqreturn_t pulse_handler(int irq, void *dev_id)
{
	struct pwm_fan_ctx *ctx = dev_id;
	// 原子量进行加一,风扇没转一圈产生两次中断
	atomic_inc(&ctx->pulses);

	return IRQ_HANDLED;
}
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 pulses;
	// delta 为计数周期,理论上为 1S
	if (delta) {
		// 定时器处理周期为一秒,pulses 为一秒内中断的次数
		pulses = atomic_read(&ctx->pulses);
		// 将 ctx->pulses 处理的次数减掉,理论上清零
		atomic_sub(pulses, &ctx->pulses);
		// 计算风扇转速
		// pulse / ctx->pulses_per_revolution 表示转了几圈,
		// 比如,风扇转了两圈,一圈两次中断,pulse 则为 4, ctx->pulses_per_revolution为2
		// delta / 1000 表示时间,单位秒, delta 单位毫秒
		// (pulse / ctx->pulses_per_revolution) /  (delta / 1000) 表示一秒转多少圈
		// 最后计算出 rpm ,每分钟的转速
		ctx->rpm = (unsigned int)(pulses * 1000 * 60) /
			(ctx->pulses_per_revolution * delta);
		// 重新计时
		ctx->sample_start = ktime_get();
	}
	// 重新设置定时器, 1S后触发
	mod_timer(&ctx->rpm_timer, jiffies + HZ);
}



使用

最后,可以使用应用程序 sensors 获取风扇转速,此时设备树中必须把风扇作为 cooling device用于 thermal中。



版权声明:本文为u014443578原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。