一、引言
众所周知,当我们给一块烧有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
一些对应效果如下:
可以看出,当使用命令 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子系统在驱动中的具体配置函数。