Linux驱动分析(PWM子系统部分关键源码分析,结合具体功能实现)

一、引言

众所周知,当我们给一块烧有Linux内核的板子上电时,一般先由u-boot进行内核启动前的准备工作,然后是内核加载启动,此时我们会看到屏幕上滚动的内核启动信息。在内核启动过程中也完成了众多驱动的初始化工作,pwm子系统的驱动也在此时被加载(本文基于jetson nano,bsp包中有此驱动)。得益于内核提供给应用层的调试接口,我们得以轻松的验证驱动代码是否功能完好,而不需要花费时间于应用层的测试代码。对于pwm,在应用层路径如下图所示:
在这里插入图片描述
在应用层可以使用如下命令控制pwm输出目标波形:

echo 0 > /sys/class/pwm/pwmchip0/export //导出pwm0
echo 20000000 >/sys/class/pwm/pwmchip0/pwm0/period //设置pwm周期(单位ns)
cat /sys/class/pwm/pwmchip0/pwm0/period  //查看设置的周期值
echo 10000000 >/sys/class/pwm/pwmchip0/pwm0/duty_cycle //设置pwm 高电平的时间 -> 占空比  =duty_cycle/period=50%
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable  //使能pwm3

一些对应效果如下:
fig2
可以看出,当使用命令 echo 0 > export,目录/pwmchip0 下多了一个pwm0,意味着此时pwm0被成功导出,然后进入pwm0目录,可以看到有众多和pwm相关的一些属性,应用层可以通过读/写这些文件来获得目标pwm波形。

二、源码分析

(1)/driver/pwm/sysfs.c (支持应用层对设备读写操作,pwm子系统的顶层,最接近应用层)
  • 部分功能实现函数:
////读period时的回调函数( cat /sys/class/pwm/pwmchip0/pwm0/period 时回调)
static ssize_t period_show(struct device *child,
			   struct device_attribute *attr,
			   char *buf)
{
	const struct pwm_device *pwm = child_to_pwm_device(child);
	struct pwm_state state;

	pwm_get_state(pwm, &state);

	return sprintf(buf, "%u\n", state.period);
}
//写period时的回调函数(echo 20000000 >/sys/class/pwm/pwmchip0/pwm0/period 时回调它)
static ssize_t period_store(struct device *child,
                struct device_attribute *attr,
                const char *buf, size_t size)
{
    state.period = val;
    ret = pwm_apply_state(pwm, &state); //应用新状态到设备中
    return ret ? : size;
}

...

/*用于处理将 PWM 设备导出到 sysfs 的请求。它从输入缓冲区读取 PWM 设备的编号,
将其转换为无符号整数,然后从 pwm_chip 中请求相应的 PWM 设备,并将其导出到 sysfs;
store代表写,对应 echo 3 > /sys/class/pwm/pwmchip0/export*/
static ssize_t export_store(struct device *parent,
			    struct device_attribute *attr,
			    const char *buf, size_t len)
{
	struct pwm_chip *chip = dev_get_drvdata(parent);
	struct pwm_device *pwm;
	unsigned int hwpwm;
	int ret;

    /*调用 kstrtouint 函数,将输入缓冲区 buf 中的字符串转换为无符号整数,并存储在 hwpwm 中*/
	ret = kstrtouint(buf, 0, &hwpwm);
	if (ret < 0)
		return ret;

	if (hwpwm >= chip->npwm)
		return -ENODEV;

    /*调用 pwm_request_from_chip 函数,从 chip 中请求编号为 hwpwm 的 PWM 设备,并将其存储在 pwm 中*/
	pwm = pwm_request_from_chip(chip, hwpwm, "sysfs");
	if (IS_ERR(pwm))
		return PTR_ERR(pwm);

    /*调用 pwm_export_child 函数,将 pwm 导出到 sysfs*/
	ret = pwm_export_child(parent, pwm);
	if (ret < 0)
		pwm_put(pwm);

	return ret ? : len;
}
/*关联属性文件export 的属性dev_attr_unexport和 其回调函数 export_store;
"_WO"表示只写 */
static DEVICE_ATTR_WO(export);  

  • 功能实现函数与应用层文件操作进行关联
/*使用 DEVICE_ATTR 宏定义属性,并将这些属性与相应的 show 和 store 函数关联起来;
 * 从而管理每个属性的 读/写 特性;
 * "_RW"表示可读可写,"_RO"表示只读,"_WO"表示只写
 */
static DEVICE_ATTR_RW(period); 
static DEVICE_ATTR_RW(duty_cycle);
static DEVICE_ATTR_RW(enable);
static DEVICE_ATTR_RW(polarity);
static DEVICE_ATTR_RO(capture);
static DEVICE_ATTR_RW(ramp_time);
static DEVICE_ATTR_RW(double_period);
static DEVICE_ATTR_RW(capture_window_length);
static DEVICE_ATTR_WO(export);
static DEVICE_ATTR_WO(unexport);
static DEVICE_ATTR_RO(npwm);

//将这些属性放入一个属性数组中,以便内核可以创建相应的 sysfs 文件。例如:
static struct attribute *pwm_attrs[] = {
    &dev_attr_period.attr,
    &dev_attr_duty_cycle.attr,
    &dev_attr_enable.attr,
    &dev_attr_polarity.attr,
    &dev_attr_capture.attr,
    &dev_attr_ramp_time.attr,
    &dev_attr_double_period.attr,
    &dev_attr_capture_window_length.attr,
    NULL
};

//使用 ATTRIBUTE_GROUPS 宏定义属性组,以便内核可以管理这些属性。例如:
ATTRIBUTE_GROUPS(pwm);
ATTRIBUTE_GROUPS(pwm_chip);

//在定义设备类时,将属性组与设备类关联起来。例如:
static struct class pwm_class = {
    .name = "pwm",
    .owner = THIS_MODULE,
    .dev_groups = pwm_chip_groups,
};

//在导出和注册设备时,内核会根据属性组创建相应的 sysfs 文件。例如:
parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip, "pwmchip%d", chip->base);
(2)/driver/pwm/core.c (核心功能实现,pwm子系统的中间层)
  • pwm芯片注册及初始化
int pwmchip_add(struct pwm_chip *chip)
{
	/*执行实际的注册操作,包括分配 PWM 设备、初始化设备状态、
	*将设备插入树结构和链表中,并导出到 sysfs
	*/
	return pwmchip_add_with_polarity(chip, PWM_POLARITY_NORMAL);
}
/*宏将 pwmchip_add 函数导出,使其可以被其他内核模块使用,
 *但仅限于那些具有 GPL 兼容许可证的模块
 */
EXPORT_SYMBOL_GPL(pwmchip_add);
  • 请求一个 PWM 设备
struct pwm_device *pwm_request(int pwm, const char *label)
{
	struct pwm_device *dev;
	int err;

	if (pwm < 0 || pwm >= MAX_PWMS)//检查传入的 pwm 参数是否在有效范围内
		return ERR_PTR(-EINVAL);

	mutex_lock(&pwm_lock);//获取 pwm_lock 互斥锁

	dev = pwm_to_device(pwm);//根据传入的 pwm 参数,从 pwm_tree 中查找对应的 pwm_device 结构体
	if (!dev) {
		dev = ERR_PTR(-EPROBE_DEFER);
		goto out;
	}

	err = pwm_device_request(dev, label);//调用 pwm_device_request 函数,请求 pwm_device
	if (err < 0)
		dev = ERR_PTR(err);

out:
	mutex_unlock(&pwm_lock);//释放 pwm_lock 互斥锁

	return dev;
}
  • 从指定的 PWM 芯片中请求一个 PWM 设备
struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
					 unsigned int index,
					 const char *label)
{
	struct pwm_device *pwm;
	int err;

	if (!chip || index >= chip->npwm)
		return ERR_PTR(-EINVAL);

	mutex_lock(&pwm_lock);
	pwm = &chip->pwms[index];//获取指定编号的 pwm_device 结构体

	err = pwm_device_request(pwm, label);//调用 pwm_device_request 函数,请求 PWM 设备
	if (err < 0)
		pwm = ERR_PTR(err);

	mutex_unlock(&pwm_lock);
	return pwm;
}
  • 其他重要函数
/*pwm_apply_state 函数用于原子地应用新的 PWM 设备状态。
 *它会根据传入的 state 参数更新 PWM 设备的状态,并确保这些更改是线程安全的
 */
 int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state);

/*pwm_adjust_config 函数根据设备树或 PWM 查找表提供的 PWM 参数调整当前的 PWM 配置。
 *它会获取当前的 PWM 状态和参数,并根据参数调整 PWM 的周期、占空比和极性
 */
 int pwm_adjust_config(struct pwm_device *pwm);

/*根据设备树节点查找并返回对应的 PWM 芯片。
 *如果找不到对应的 PWM 芯片,则返回一个包含 -EPROBE_DEFER 错误码的错误指针
 */
static struct pwm_chip *of_node_to_pwmchip(struct device_node *np)
{
	struct pwm_chip *chip;
	mutex_lock(&pwm_lock);
	/*使用 list_for_each_entry 宏遍历 pwm_chips 列表中的每一个 pwm_chip 结构体。
	 *对于每一个 pwm_chip 结构体,
	 *检查其 dev 成员是否不为空,并且 dev->of_node 是否等于传入的设备树节点 np。
	 *如果找到匹配的 pwm_chip 结构体,则释放互斥锁并返回该 pwm_chip 结构体的指针。
	 */
	list_for_each_entry(chip, &pwm_chips, list)
		if (chip->dev && chip->dev->of_node == np) {
			mutex_unlock(&pwm_lock);
			return chip;
		}
	mutex_unlock(&pwm_lock);
	return ERR_PTR(-EPROBE_DEFER);
}
  • 通过设备树节点请求一个 PWM 设备
struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id)
{
	struct pwm_device *pwm = NULL;
	struct of_phandle_args args;
	struct pwm_chip *pc;
	int index = 0;
	int err;

	/*如果 con_id 不为空,则在设备树节点 np 中
	 *查找 pwm-names 属性中与 con_id 匹配的字符串,并获取其索引。
	 */
	if (con_id) {
		index = of_property_match_string(np, "pwm-names", con_id);
		if (index < 0)
			return ERR_PTR(index);
	}

	/*解析设备树节点 np 的 pwms 属性,获取 PWM 芯片和 PWM 设备的索引。
	 *如果解析失败,则返回一个包含错误码的错误指针。
	 */
	err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index,
					 &args);
	if (err) {
		pr_debug("%s(): can't parse \"pwms\" property\n", __func__);
		return ERR_PTR(err);
	}

	/*根据设备树节点 np 查找对应的 PWM 芯片。
	 *如果找不到对应的 PWM 芯片,则返回一个包含 -EPROBE_DEFER 错误码的错误指针。
	 */
	pc = of_node_to_pwmchip(args.np);
	if (IS_ERR(pc)) {
		pr_debug("%s(): PWM chip not found\n", __func__);
		pwm = ERR_CAST(pc);
		goto put;
	}

	/*检查设备树节点 np 中的 pwms 属性中的 #pwm-cells 是否
	 *与 PWM 芯片 pc 中的 of_pwm_n_cells 相匹配。
	 *如果不匹配,则返回一个包含 -EINVAL 错误码的错误指针。
	 */
	if (args.args_count != pc->of_pwm_n_cells) {
		pr_debug("%s: wrong #pwm-cells for %s\n", np->full_name,
			 args.np->full_name);
		pwm = ERR_PTR(-EINVAL);
		goto put;
	}
	//调用 PWM 芯片 pc 的 of_xlate 方法,解析设备树节点 np 中的 pwms 属性。
	pwm = pc->of_xlate(pc, &args);
	if (IS_ERR(pwm))
		goto put;

	/*
	 * If a consumer name was not given, try to look it up from the
	 * "pwm-names" property if it exists. Otherwise use the name of
	 * the user device node.
	 */
	if (!con_id) {
		err = of_property_read_string_index(np, "pwm-names", index,
						    &con_id);
		if (err < 0)
			con_id = np->name;
	}

	pwm->label = con_id;

put:
	of_node_put(args.np);//释放设备树节点引用

	return pwm;
}
(3)/include/linux/pwm.h(头文件)
  • 控制器操作函数(只给出了接口,待实现)
/**
 * struct pwm_ops - PWM 控制器操作, 其中 config、enable 和 disable 必须实现
 * @request: 请求 PWM 的可选钩子
 * @free: 释放 PWM 的可选钩子
 * @config: 配置此 PWM 的占空比和周期长度
 * @set_polarity: 配置此 PWM 的极性
 * @capture: 捕获并报告 PWM 信号
 * @enable: 启用 PWM 输出切换
 * @disable: 禁用 PWM 输出切换
 * @apply: 原子地应用新的 PWM 配置。state 参数应根据实际硬件配置进行调整(如果近似周期或占空比值,state 应反映出来)
 * @get_state: 获取当前 PWM 状态。此函数仅在注册 PWM 芯片时调用一次。
 */
struct pwm_ops {
	int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
	void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
	int (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
		      int duty_ns, int period_ns);
	int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm,
			    enum pwm_polarity polarity);
	int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm,
		       struct pwm_capture *result, unsigned long timeout);
	int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm);
	void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);
	int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,
		     struct pwm_state *state);
	void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
			  struct pwm_state *state);
};
  • pwm控制器的结构体定义
struct pwm_chip {
    struct device *dev; //继承自设备模型
    struct list_head list; //用于将 pwm_chip 结构体链接到一个链表中。该链表通常用于管理系统中的所有 PWM 控制器
    /*指向 pwm_ops 结构体的指针,
     *pwm_ops 结构体包含了 PWM 控制器的各种操作函数,在上一点已经提到 */
    const struct pwm_ops *ops;
    int base;	//控制器管理的第一个 PWM 通道的编号
    unsigned int npwm; //该控制器管理的 PWM 通道数量
    struct pwm_device *pwms; //对应 pwm0,pwm2 ...
    struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
                                    const struct of_phandle_args *args);
    unsigned int of_pwm_n_cells;
    bool can_sleep;
};

[补充]几个结构体之间的继承关系如下图:
在这里插入图片描述

(4)/driver/pwm/pwm-tegra.c (偏硬件配置,pwm子系统的底层)

典型的驱动模块框架,分析如下:

  • pwm控制器 操作函数的实现
/*tegra_pwm_config 函数的作用是配置 Tegra PWM 控制器的占空比和周期。
 *具体来说,它根据传入的占空比和周期参数,计算并设置 PWM 控制器的寄存器值,
 *以实现所需的 PWM 信号输出。
 */
 static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
                 int duty_ns, int period_ns)
                 
/*启用 Tegra PWM 控制器的指定 PWM 通道。
 *它会启用与 PWM 通道相关的时钟,并设置相应的寄存器位以启用 PWM 输出
 */
static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)

/*禁用 Tegra PWM 控制器的指定 PWM 通道*/
static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
  • 实例化 pwm-ops 结构体(填表思想)
static const struct pwm_ops tegra_pwm_ops = {
     .config = tegra_pwm_config,
     .enable = tegra_pwm_enable,
     .disable = tegra_pwm_disable,
     .owner = THIS_MODULE,
 };
  • pwm初始化和卸载函数,模块加载时调用
static int tegra_pwm_probe(struct platform_device *pdev)
 static int tegra_pwm_remove(struct platform_device *pdev)
  • 定义of_device_id 结构体数组 tegra_pwm_of_match
/*主要作用:
 *当内核解析设备树时,它会使用 tegra_pwm_of_match 数组中的兼容性字符串来匹配设备树节点。
 *如果找到匹配的节点,内核会将该节点与相应的驱动程序关联起来
 */
static const struct of_device_id tegra_pwm_of_match[] = {
    { .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_soc },
    { .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_soc },
    { .compatible = "nvidia,tegra194-pwm", .data = &tegra194_pwm_soc },
    { }
};
  • 平台驱动结构体 及其注册
static struct platform_driver tegra_pwm_driver = {
    .driver = {
        .name = "tegra-pwm",
        .of_match_table = tegra_pwm_of_match, //上面定义的结构体数组
        .pm = &tegra_pwm_pm_ops, //电源管理
    },
    .probe = tegra_pwm_probe, //初始化函数
    .remove = tegra_pwm_remove, //卸载函数
};

/*使用 module_platform_driver 宏注册平台驱动,
 *使其在内核模块加载时自动注册,在模块卸载时自动注销
 */
module_platform_driver(tegra_pwm_driver);

三、总结

以上内容大致分析了jetson nano的pwm子系统在应用层和内核层的使用和有关源码。首先,就暴露给应用层的文件操作来说,这极大方便了驱动开发过程中的调试。其次通过驱动源码的分析,我们可以大致了解整个驱动的框架,也有助于我们加深驱动源码的理解。本文虽然花费较多笔墨用于源码分析,但侧重于代码框架,对代码实现的分析较少,因此下一步将深入学习pwm子系统在驱动中的具体配置函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值