05、sysyfs 操作 pwm 原理讲解
sysfs操作PWM的原理可以这样理解:
当Linux系统启动时,PWM控制器的驱动程序会自动在/sys/class/pwm
目录下创建对应的虚拟文件。这些文件就像"控制面板"的入口,每个文件对应PWM的一个具体功能(比如启用/禁用、周期、占空比等)。
具体过程分三步:
- 暴露设备:驱动加载时,内核在
/sys/class/pwm
下生成类似pwmchip0
的目录,里面包含export
等基础文件 - 选择通道:当你向
export
文件写入数字(如"0"),内核就会在pwmchip0
下自动生成pwm0
子目录,这个数字对应具体的PWM硬件通道 - 设置参数:修改
pwm0
目录下的period
(周期)、duty_cycle
(占空比)和enable
(启用)文件时,系统会自动将数值转换为对应的硬件寄存器配置,最终改变PWM波形输出
::: 为什么能通过文件控制硬件? 因为这些文件背后都连接着内核的驱动程序:
当你写入数值时(如echo 1000000 > period
),系统会调用驱动里的回调函数,把数值转换为对应的硬件寄存器设置
当你读取文件时(如cat period
),系统会从硬件寄存器中读取当前值返回
这就像在和硬件玩"传话游戏":你的文件操作是"指令传话",sysfs是"传话通道",内核驱动是"翻译员",最终把你的指令准确传递给硬件芯片。 :::
一、pwmchip_add
在Linux内核的PWM驱动代码中,有一个叫做pwmchip_add的函数。它的主要任务是把PWM控制器设备添加到PWM系统里。当Rockchip平台的PWM控制器执行rockchip_pwm_probe函数时候,就会调用这个pwmchip_add函数来完成注册。
/**
* pwmchip_add() - register a new PWM chip
* @chip: the PWM chip to add
*
* Register a new PWM chip. If chip->base < 0 then a dynamically assigned base
* will be used. The initial polarity for all channels is normal.
*
* Returns: 0 on success or a negative error code on failure.
*/
int pwmchip_add(struct pwm_chip *chip)
{
return pwmchip_add_with_polarity(chip, PWM_POLARITY_NORMAL);
}
EXPORT_SYMBOL_GPL(pwmchip_add);
2
3
4
5
6
7
8
9
10
11
12
13
14
其中 pwmchip_add_with_polarity 定义如下:
/**
* pwmchip_add_with_polarity() - register a new PWM chip
* @chip: the PWM chip to add
* @polarity: initial polarity of PWM channels
*
* Register a new PWM chip. If chip->base < 0 then a dynamically assigned base
* will be used. The initial polarity for all channels is specified by the
* @polarity parameter.
*
* Returns: 0 on success or a negative error code on failure.
*/
int pwmchip_add_with_polarity(struct pwm_chip *chip,
enum pwm_polarity polarity)
{
struct pwm_device *pwm;
unsigned int i;
int ret;
// 检查 chip 结构体的有效性
if (!chip || !chip->dev || !chip->ops || !chip->npwm)
return -EINVAL;
// 检查 pwm_ops 结构体的有效性
if (!pwm_ops_check(chip->ops))
return -EINVAL;
// 获取全局 pwm_lock 互斥锁
mutex_lock(&pwm_lock);
// 为 chip 分配 PWM 设备索引号
ret = alloc_pwms(chip->base, chip->npwm);
if (ret < 0)
goto out;
// 动态分配 chip->npwm 个 pwm_device 结构体
chip->pwms = kcalloc(chip->npwm, sizeof(*pwm), GFP_KERNEL);
if (!chip->pwms) {
ret = -ENOMEM;
goto out;
}
// 保存分配的 PWM 设备索引号
chip->base = ret;
// 初始化每个 PWM 设备
for (i = 0; i < chip->npwm; i++) {
pwm = &chip->pwms[i];
pwm->chip = chip;
pwm->pwm = chip->base + i;
pwm->hwpwm = i;
pwm->state.polarity = polarity;
pwm->state.output_type = PWM_OUTPUT_FIXED;
// 如果 chip->ops->get_state 存在,则获取当前 PWM 设备的状态
if (chip->ops->get_state)
chip->ops->get_state(chip, pwm, &pwm->state);
// 将 PWM 设备添加到全局 PWM 设备树中
radix_tree_insert(&pwm_tree, pwm->pwm, pwm);
}
// 标记分配的 PWM 设备为已使用
bitmap_set(allocated_pwms, chip->base, chip->npwm);
// 将 PWM 控制器添加到全局 PWM 控制器链表
INIT_LIST_HEAD(&chip->list);
list_add(&chip->list, &pwm_chips);
ret = 0;
// 如果内核开启了 Device Tree 支持,则注册 PWM 控制器到 Device Tree
if (IS_ENABLED(CONFIG_OF))
of_pwmchip_add(chip);
out:
// 释放全局 pwm_lock 互斥锁
mutex_unlock(&pwm_lock);
// 如果添加成功,则在 sysfs 中导出 PWM 控制器
if (!ret)
pwmchip_sysfs_export(chip);
return ret;
}
EXPORT_SYMBOL_GPL(pwmchip_add_with_polarity);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
第69行调用了pwmchip_sysfs_export函数,该函数的作用是将PWM控制器添加到内核的sysfs系统中。sysfs对PWM设备的所有文件操作权限和接口暴露,都是由这个函数来初始化配置的。这个函数的具体实现代码位于"drivers/pwm/sysfs.c"文件里。
二、pwmchip_sysfs_export
void pwmchip_sysfs_export(struct pwm_chip *chip)
{
struct device *parent;
/*
* 如果 device_create() 失败,pwm_chip 仍然可以被内核使用,只是没有被导出到 sysfs
*/
parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip,
"pwmchip%d", chip->base);
if (IS_ERR(parent)) {
dev_warn(chip->dev,
"device_create failed for pwm_chip sysfs export\n");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
第8行的device_create()函数负责在系统文件中创建设备节点。它会生成一个名为pwmchip%d的目录,这个目录位于/sys/class/pwm文件夹下,如图所示。
在PWM驱动中,设备创建函数的第一个参数使用了名为pwm_class
的PWM设备类。这个类在驱动启动时被注册到内核:
// 驱动启动时执行的函数,负责注册PWM设备类
static int __init pwm_sysfs_init(void)
{
return class_register(&pwm_class); // 注册pwm_class类
}
2
3
4
5
pwm_class
类的定义如下:
struct class pwm_class = {
name: "pwm", // 类名称设为"pwm"
owner: THIS_MODULE, // 声明属于当前模块
dev_groups: pwm_chip_groups // 绑定属性组
};
2
3
4
5
这里的dev_groups
绑定了三个关键属性,这些属性对应sysfs文件系统中的特殊文件:
// 定义三个属性
static struct attribute *pwm_chip_attrs[ ] = {
&dev_attr_export.attr, // 对应export文件
&dev_attr_unexport.attr, // 对应unexport文件
&dev_attr_npwm.attr, // 对应npwm文件
NULL,
};
// 将属性数组包装成属性组
ATTRIBUTE_GROUPS(pwm_chip);
2
3
4
5
6
7
8
9
10
11
12
这些属性的作用:
- export 文件:当用户向这个文件写入通道号时,会触发
export_store
函数,完成PWM通道的导出操作。 - unexport 文件:用于取消已导出的PWM通道。
- npwm 文件:显示当前PWM芯片支持的最大通道数量。
::: 简单来说:
export
文件像一个控制开关,用户通过写入通道号(如echo 0 > export
)就能激活对应的PWM通道
这些功能都通过对应的函数(如export_store
)在后台处理请求
所有功能都通过/sys/class/pwm目录下的文件提供给用户空间使用 :::
3.1、export_store
export_store 函数内容如下所示
static ssize_t export_store(struct device *parent,
struct device_attribute *attr,
const char *buf, size_t len)
{
// 从父设备获取 PWM 芯片对象
struct pwm_chip *chip = dev_get_drvdata(parent);
struct pwm_device *pwm;
unsigned int hwpwm;// 要导出的 PWM 通道号
int ret;
// 将用户输入的 PWM 通道号转换为整数
ret = kstrtouint(buf, 0, &hwpwm);
if (ret < 0)
return ret;
// 检查要导出的 PWM 通道号是否超出 PWM 芯片的支持范围
if (hwpwm >= chip->npwm)
return -ENODEV;
// 请求使用指定的 PWM 通道
pwm = pwm_request_from_chip(chip, hwpwm, "sysfs");
if (IS_ERR(pwm))
return PTR_ERR(pwm);
// 将 PWM 通道导出到 sysfs 文件系统
ret = pwm_export_child(parent, pwm);
if (ret < 0)
pwm_put(pwm);// 如果导出失败,释放 PWM 通道
// 返回写入的字节数,或者负错误码
return ret ? : len;
}
static DEVICE_ATTR_WO(export);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
这个函数让用户通过系统文件(sysfs)导出 PWM 通道。具体步骤如下:
获取 PWM 控制器:从父设备获取 PWM 控制器对象。
解析用户输入:把用户写入的通道号(字符串)转换成数字。
检查通道号是否合法:
- 如果通道号超过控制器支持的数量,返回错误。
申请使用 PWM 通道:
- 尝试获取用户指定的 PWM 通道,如果失败返回错误。
导出到系统文件:
- 调用
pwm_export_child
函数,将 PWM 通道注册到系统文件中。
- 调用
处理结果:
- 如果导出成功,返回成功;如果失败,释放通道并返回错误。
3.2、pwm_export_child
pwm_export_child 函数同样定义在 drivers/pwm/sysfs.c 文件中, 具体内容如下所示:
static int pwm_export_child(struct device *parent, struct pwm_device *pwm)
{
struct pwm_export *export;
int ret;
// 检查 PWM 设备是否已经被导出
if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags))
return -EBUSY;
// 分配 pwm_export 结构体
export = kzalloc(sizeof(*export), GFP_KERNEL);
if (!export) {
clear_bit(PWMF_EXPORTED, &pwm->flags);
return -ENOMEM;
}
// 初始化 pwm_export 结构体
export->pwm = pwm;
mutex_init(&export->lock);
export->child.release = pwm_export_release;
export->child.parent = parent;
export->child.devt = MKDEV(0, 0);
export->child.groups = pwm_groups; //方法
dev_set_name(&export->child, "pwm%u", pwm->hwpwm);
// 注册 sysfs 设备节点
ret = device_register(&export->child);
if (ret) {
clear_bit(PWMF_EXPORTED, &pwm->flags);
put_device(&export->child);
export = NULL;
return ret;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
pwm_export_child
函数的作用
这个函数负责将 PWM 通道实际注册到系统文件中:
检查是否已被导出:
- 如果该通道已经被导出过(已被使用),返回“设备忙碌”错误。
分配内存:
- 为 PWM 导出结构体分配内存,如果分配失败则返回错误。
初始化结构体:
- 设置结构体中的 PWM 对象、父设备、属性组等信息。
- 设备名称格式为
pwmX
(X是通道号)。
注册到系统文件:
- 将 PWM 设备注册到系统文件中,生成对应的文件节点。
错误处理:
- 如果注册失败,释放内存并标记通道未导出。
3.3、PWM 属性组
其中 PWM 属性组结构体定义如下所示:
static struct attribute *pwm_attrs[] = {
&dev_attr_period.attr, // 周期属性
&dev_attr_duty_cycle.attr, // 占空比属性
#ifdef CONFIG_PWM_ROCKCHIP_ONESHOT
&dev_attr_oneshot_count.attr, // 单次脉冲计数属性
#endif
&dev_attr_enable.attr, // 使能属性
&dev_attr_polarity.attr, // 极性属性
&dev_attr_capture.attr, // 捕获属性
NULL // 属性列表结束标志
};
ATTRIBUTE_GROUPS(pwm);
2
3
4
5
6
7
8
9
10
11
12
PWM 属性组的作用
属性组定义了 PWM 设备在系统文件中可操作的参数,每个参数对应一个文件。用户可以通过读写这些文件控制 PWM:
- 周期 (
**period**
):设置 PWM 波形的总周期。 - 占空比 (
**duty_cycle**
):设置高电平持续时间。 - 单次脉冲计数 (
**oneshot_count**
):(特定硬件支持)设置单次脉冲次数。 - 使能 (
**enable**
):启用或禁用 PWM 输出。 - 极性 (
**polarity**
):设置高电平或低电平有效。 - 捕获 (
**capture**
):读取外部 PWM 信号的参数(如输入模式)。
::: export_store
是用户通过写入文件触发导出操作的入口。
pwm_export_child
负责具体注册设备到系统文件。
属性组定义了用户可操作的 PWM 参数,每个参数对应一个文件节点,方便用户直接读写控制。 :::