3. Linux PWM子系统架构:PWM核心层、PWM控制器驱动、PWM用户空间接口
在嵌入式Linux系统中,PWM(脉冲宽度调制)背光调节并非由单个驱动文件独立完成,而是依托一套层次分明的子系统架构。理解这套架构是开发稳定、可移植背光驱动的基石。本章将深入剖析Linux PWM子系统的三层模型:PWM核心层、PWM控制器驱动以及PWM用户空间接口,并结合电子书阅读器的背光场景进行实战解读。
3.1 PWM子系统三层架构总览
下图展示了PWM子系统的完整软件栈。从底层的硬件寄存器到顶层的用户空间控制,每一层都承担着明确的职责。
3.2 PWM核心层:驱动骨架与API
PWM核心层位于 drivers/pwm/core.c,是整个子系统的中枢。它定义了两个核心数据结构:pwm_chip 和 pwm_device。
3.2.1 核心数据结构
| 数据结构 | 作用 | 关键成员 |
|---|---|---|
| pwm_chip | 代表一个PWM控制器硬件实例,管理一组PWM通道 | dev (设备指针), ops (操作函数集), npwm (通道数), pwms (pwm_device数组) |
| pwm_device | 代表一个独立的PWM输出通道 | chip (所属控制器), pwm (通道索引), state (当前状态), label (使用者标签) |
| pwm_state | 描述PWM输出的完整状态 | period (周期ns), duty_cycle (占空比ns), polarity (极性), enabled (使能标志) |
3.2.2 核心API函数
核心层向消费者驱动(如背光驱动)提供以下关键API:
pwm_get() / pwm_put():获取/释放一个PWM设备的引用。通常基于设备树中的pwms属性进行查找。pwm_apply_state():核心函数,用于将pwm_state配置应用到硬件。内部会调用控制器驱动的.apply()回调。pwm_config():便捷函数,用于设置周期和占空比。pwm_enable() / pwm_disable():使能或禁用PWM输出。pwm_set_polarity():设置PWM极性(正常或反转)。
pwm_config()、pwm_enable() 等旧API已被标记为弃用,推荐统一使用 pwm_apply_state() 进行原子化配置。背光驱动开发时应优先采用新API。
3.3 PWM控制器驱动:硬件操作实现
控制器驱动是真正与硬件寄存器打交道的部分。每个SoC厂商(如全志、NXP、Rockchip)都需要为其PWM模块实现一个驱动,并填充 struct pwm_ops 结构体。
3.3.1 pwm_ops 结构体
struct pwm_ops {
int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state);
int (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state);
// ... 其他可选回调
};
3.3.2 关键回调函数职责
| 回调 | 触发时机 | 驱动实现要点 |
|---|---|---|
.request() |
消费者调用 pwm_get() 时 |
初始化通道时钟、引脚复用,确保硬件可用 |
.free() |
消费者调用 pwm_put() 时 |
关闭时钟、释放引脚,将通道复位到安全状态 |
.apply() |
消费者调用 pwm_apply_state() 时 |
根据 pwm_state 设置硬件寄存器:周期、占空比、极性、使能位。这是最核心的回调 |
.get_state() |
系统启动或状态同步时 | 从硬件寄存器读取当前配置,填充 pwm_state 返回给核心层 |
3.3.3 控制器驱动注册流程
一个典型的PWM控制器驱动在 probe 函数中完成以下步骤:
- 获取硬件资源: 映射寄存器基地址、获取时钟、配置引脚复用。
- 初始化 pwm_chip: 设置
npwm(通道数)、ops、dev等字段。 - 调用
pwmchip_add(): 将pwm_chip注册到核心层,核心层会为其分配pwm_device并加入全局列表。 - 设备树匹配: 驱动通过
of_match_table与设备节点匹配,解析如时钟频率、通道配置等平台数据。
pwm-sun4i.c 和 pwm-rockchip.c。阅读这些源码时,重点关注 .apply() 函数中如何计算分频系数和占空比寄存器值。
3.4 PWM用户空间接口:sysfs与背光控制
除了通过内核驱动(如 pwm-backlight)控制PWM,Linux还提供了用户空间接口,方便调试或直接控制。最常用的接口是 sysfs。
3.4.1 sysfs 接口路径
当PWM控制器驱动注册成功后,会在 /sys/class/pwm/ 下创建目录。例如:
/sys/class/pwm/pwmchip0/ # 第一个PWM控制器
├── npwm # 该控制器支持的PWM通道数(只读)
├── export # 向用户空间导出PWM通道(写通道号)
├── unexport # 取消导出PWM通道(写通道号)
└── pwm0/ # 导出后的PWM通道0目录
├── period # 周期(单位:纳秒)
├── duty_cycle # 占空比(单位:纳秒)
├── polarity # 极性:normal 或 inversed
├── enable # 使能:1 开启,0 关闭
└── power/ # 电源管理相关
3.4.2 用户空间调光示例
以下命令演示了如何通过sysfs直接控制PWM输出,实现背光调节:
# 1. 导出PWM通道0
echo 0 > /sys/class/pwm/pwmchip0/export
# 2. 设置周期为 20ms(50Hz,适合背光)
echo 20000000 > /sys/class/pwm/pwmchip0/pwm0/period
# 3. 设置占空比为 10ms(50% 亮度)
echo 10000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
# 4. 设置极性(通常为 normal)
echo normal > /sys/class/pwm/pwmchip0/pwm0/polarity
# 5. 使能PWM输出
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
# 6. 调节亮度:将占空比改为 2ms(10% 亮度)
echo 2000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
3.4.3 用户空间接口与背光驱动的协作
在电子书阅读器产品中,通常不会直接通过sysfs控制背光,而是由 pwm-backlight 驱动管理。该驱动在内部调用 pwm_apply_state(),同时也会在 /sys/class/backlight/ 下创建标准的背光接口(如 brightness、max_brightness)。用户空间的调光服务(如 Android 的 Light HAL 或 Weston 的背光插件)通过操作这些标准接口,间接控制PWM。
- 解耦: 背光驱动无需知道PWM寄存器细节,控制器驱动无需关心背光曲线。
- 标准化: 用户空间通过统一的
/sys/class/backlight/接口控制,应用层代码可跨平台。 - 可调试: sysfs 接口为开发初期提供了直接操作PWM的能力,方便验证硬件时序。
3.5 本章小结
Linux PWM子系统的三层架构——核心层、控制器驱动层、用户空间接口层——为背光调节提供了清晰、可扩展的软件基础。核心层通过 pwm_chip 和 pwm_device 管理硬件资源,控制器驱动通过 pwm_ops 实现硬件操作,而用户空间接口(sysfs)和标准背光类驱动则提供了灵活的控制途径。掌握这一架构,是后续编写和调试背光驱动的关键前提。