Linux pwm子系统分析1(基于Linux6.6)---系统框架介绍
一、pwm子系统框架
如下即为pwm子系统的系统框架,大致可以分为如下几个方面:
- pwm子系统接口层,提供pwm的request、free(类似于gpio_request、gpio_free);pwm的使能与去使能;pwm 配置(配置pwm的占空比等)等接口
- 上面pwm子系统接口层通过pwm号在pwm_tree基数树中找到对应的pwm_device(所有注册的pwm device均会添加至pwm_tree中,另外一个pwm_chip可包含多个pwm_device,因此pwm_chip与pwm_device之间也存在关联),并借助pwm_chip提供的方法配置pwm控制器,实现pwm配置等操作;
- pwm_chip层主要对应一个pwm控制器,一个pwm控制器可包含多个pwm_device,针对一个pwm_chip,提供了访问pwm 控制器的方法,通过pwm_chip提供的方法,实现对pwm 控制器的配置;
- pwm器件即是对应的pwm控制器。
以上即为pwm子系统的框架,另外pwm子系统也借鉴了gpio子系统的实现流程(export、unexport)。针对pwm_chip,也借助device子系统以及sysfs子系统接口,为注册的pwm_chip对应struct device类型成员实现export、unexport属性(即sys下文件,如/sys/class/pwm/pwmchipX/export文件),而应用程序通过将该pwm chip对应的pwm号设置到export中,则export对应的store函数即会为该pwm device创建对应struct device类型变量,并为该device变量创建duty、period_ns、enable等属性参数,从而应用程序即可实现pwm的控制(这个和操作gpio是一样的)。
Linux 中的 PWM(脉宽调制)子系统为控制硬件(如 LED、马达、蜂鸣器等)提供了一个标准化的接口。PWM 是一种通过调节信号的占空比来控制设备输出的方式,因此它通常用于精确控制设备的功率或亮度。
Linux PWM 子系统的框架设计使得开发者能够统一访问 PWM 控制器,并允许通过标准接口管理多个 PWM 输出。下面是 Linux PWM 子系统的框架和工作原理的详细解释。
1. PWM 子系统的结构
Linux 的 PWM 子系统的核心是 pwm
子系统,它通过提供一个抽象层,允许用户和驱动程序通过简单的 API 来控制硬件 PWM 控制器。整个系统框架大致如下:
+-----------------------------------+
| User Space (Applications) |
|-----------------------------------|
| sysfs interfaces (e.g., pwm chip) |
+-----------------------------------+
|
|
v
+-----------------------------------+
| PWM Subsystem (Kernel) |
|-----------------------------------|
| 1. pwm_device |
| 2. pwm_chip |
| 3. PWM API (pwm_request, pwm_config)|
|-----------------------------------|
|
|
v
+-----------------------------------+
| Hardware Layer |
|-----------------------------------|
| PWM Hardware Controller |
+-----------------------------------+
2. PWM 子系统的主要组件
PWM 控制器(PWM Chip)
每个硬件平台通常包含一个或多个 PWM 控制器,每个控制器被视为一个 pwm_chip
结构。在 Linux 中,PWM 控制器是作为设备驱动程序(Device Driver)进行管理的,每个 pwm_chip
结构包含了所有与硬件控制器相关的信息。
关键字段:
base
: PWM 控制器的基础地址。npwm
: PWM 控制器支持的最大通道数。of_pwm_n_cells
: 用于解析设备树中的 PWM 信息的单元数。request
: 请求 PWM 控制的函数。free
: 释放 PWM 控制的函数。apply
: 应用 PWM 配置的函数。
通常,每个硬件平台的驱动程序都会提供一个 pwm_chip
结构,来表示该平台的 PWM 控制器。
PWM 设备(pwm_device)
每个通过 pwm_chip
管理的 PWM 控制器可以提供多个 PWM 通道(即每个通道可以控制一个独立的设备,如 LED、风扇等)。每个控制通道通过 pwm_device
结构表示。用户空间或驱动程序通过 pwm_device
与特定的 PWM 通道进行交互。
pwm_device
结构包含以下信息:
chip
: 指向对应pwm_chip
的指针。channel
: PWM 通道编号。enabled
: 标识该 PWM 是否已启用。duty_cycle
: 设置 PWM 信号的占空比。
PWM 驱动(Driver)
PWM 驱动程序负责硬件平台的初始化、PWM 控制器的配置、启用和禁用 PWM 输出,以及与硬件交互的细节。驱动程序将会向内核注册 PWM 控制器,并暴露 API 给上层使用。
PWM API(用户接口)
Linux 内核为开发者提供了一组 PWM API,允许驱动程序或用户空间程序控制 PWM。常用的 API 包括:
-
pwm_request(int pwm_id, const char *label)
: 请求一个 PWM 控制通道。通过通道 ID 和标签来标识。 -
pwm_free(struct pwm_device *pwm)
: 释放 PWM 通道。 -
pwm_config(struct pwm_device *pwm, unsigned long duty_ns, unsigned long period_ns)
: 配置 PWM 信号的占空比和周期。 -
pwm_enable(struct pwm_device *pwm)
: 启用 PWM 输出。 -
pwm_disable(struct pwm_device *pwm)
: 禁用 PWM 输出。
二、pwm相关数据结构说明
上面说明了pwm 子系统的框架,从数据结构、接口说明pwm子系统是如何实现上述一中所说的框架的。如下图,pwm子系统对外提供系统接口,供内核其他子系统调用。
- pwm子系统接口提供了pwm的使能、去使能、pwm配置(占空比、频率等属性);
- 上述1中的接口一般传递pwm device或者pwm号,获取到pwm device,而pwm device中则包含指向其pwm_chip的指针,从而找到pwm chip,并通过pwm chip的ops接口,实现与pwm控制器的通信;
- pwm chip中主要包括该pwm chip的pwm base index、pwm num、pwm 操作接口(enable、disable、pwm config、pwm request、pwm free),这基本上和gpio子系统中gpio_chip的成员类似;
- pwm_ops主要包括enable、disable、pwm config、pwm request、pwm free这几个接口。
include/linux/pwm.h
/**
* struct pwm_ops - PWM controller operations
* @request: optional hook for requesting a PWM
* @free: optional hook for freeing a PWM
* @capture: capture and report PWM signal
* @apply: atomically apply a new PWM config
* @get_state: get the current PWM state. This function is only
* called once per PWM device when the PWM chip is
* registered.
* @owner: helps prevent removal of modules exporting active PWMs
*/
struct pwm_ops {
int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_capture *result, unsigned long timeout);
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);
struct module *owner;
};
/**
* struct pwm_chip - abstract a PWM controller
* @dev: device providing the PWMs
* @ops: callbacks for this PWM controller
* @base: number of first PWM controlled by this chip
* @npwm: number of PWMs controlled by this chip
* @of_xlate: request a PWM device given a device tree PWM specifier
* @of_pwm_n_cells: number of cells expected in the device tree PWM specifier
* @list: list node for internal use
* @pwms: array of PWM devices allocated by the framework
*/
struct pwm_chip {
struct device *dev;
const struct pwm_ops *ops;
int base;
unsigned int npwm;
struct pwm_device * (*of_xlate)(struct pwm_chip *chip,
const struct of_phandle_args *args);
unsigned int of_pwm_n_cells;
/* only used internally by the PWM framework */
struct list_head list;
struct pwm_device *pwms;
};
/*
* struct pwm_state - state of a PWM channel
* @period: PWM period (in nanoseconds)
* @duty_cycle: PWM duty cycle (in nanoseconds)
* @polarity: PWM polarity
* @enabled: PWM enabled status
* @usage_power: If set, the PWM driver is only required to maintain the power
* output but has more freedom regarding signal form.
* If supported, the signal can be optimized, for example to
* improve EMI by phase shifting individual channels.
*/
struct pwm_state {
u64 period;
u64 duty_cycle;
enum pwm_polarity polarity;
bool enabled;
bool usage_power;
};
/**
* struct pwm_device - PWM channel object
* @label: name of the PWM device
* @flags: flags associated with the PWM device
* @hwpwm: per-chip relative index of the PWM device
* @pwm: global index of the PWM device
* @chip: PWM chip providing this PWM device
* @chip_data: chip-private data associated with the PWM device
* @args: PWM arguments
* @state: last applied state
* @last: last implemented state (for PWM_DEBUG)
*/
struct pwm_device {
const char *label;
unsigned long flags;
unsigned int hwpwm;
unsigned int pwm;
struct pwm_chip *chip;
void *chip_data;
struct pwm_args args;
struct pwm_state state;
struct pwm_state last;
};
如下即为这几个数据结构的关联,此处不对数据结构中每一个成员做详细说明,该子系统相对来说还是和gpio 子系统很像的。
三、pwm chip 驱动开发流程说明
Pwm chip驱动的开发流程相对也比较简单,下面简要说明:
- 创建platform device,用于存储该pwm chip相关的配置参数,如pwm base index、pwm num、pwm操作相关的寄存器参数等等;
- 创建platform driver,在该driver的probe接口中完成pwm chip的注册,主要包括:
- 申请struct pwm_chip类型的内存空间;
- 实现struct pwm_ops中各成员接口,主要实现与pwm 控制器的通信;
- 调用pwmchip_add,完成pwm chip的添加。
实现以上几步,即可完成pwm chip的注册。
在 Linux 中,开发一个 PWM 控制器驱动通常涉及几个步骤。这些步骤包括硬件初始化、PWM 控制器的注册、设备树支持(如果需要)、驱动程序的编写、以及与上层应用或内核子系统的交互。以下是开发 PWM 控制器驱动的详细流程。
1. 理解硬件平台和 PWM 控制器
在开始开发 PWM 驱动程序之前,首先需要对目标硬件平台的 PWM 控制器有充分的理解。PWM 控制器通常包含以下几个部分:
- PWM 信号的输出引脚。
- 配置周期和占空比的寄存器。
- 使能和禁用 PWM 输出的控制寄存器。
- 中断处理(如果适用)。
此外,要了解如何在硬件中配置 PWM 通道,硬件文档或 datasheet 是非常重要的参考资料。
2. 准备设备树支持
大部分嵌入式系统使用设备树(Device Tree)来描述硬件信息,包括 PWM 控制器。设备树提供了硬件的描述,允许内核自动识别并初始化相关硬件。
通常,PWM 控制器的设备树节点会像下面这样进行描述:
dts
pwm1: pwm@40000000 {
compatible = "my_pwm_controller";
#pwm-cells = <2>;
pwm-names = "pwm1";
status = "okay";
};
compatible
字段描述了设备的兼容性字符串,用来指示该 PWM 控制器的驱动。#pwm-cells
描述了如何通过设备树来传递 PWM 控制器的参数。通常,PWM 通道和占空比/周期信息需要两个单元。pwm-names
用来为每个 PWM 通道命名。
3. 编写 PWM 控制器驱动
PWM 控制器驱动需要遵循一定的架构,以便与 Linux 内核的 PWM 子系统交互。主要步骤包括:
定义 pwm_chip
结构
pwm_chip
结构体是表示 PWM 控制器的核心,它包含了该控制器的初始化和控制方法。一个完整的 pwm_chip
结构会包括以下信息:
struct pwm_chip {
struct device *dev; /* 控制器的设备结构 */
unsigned int npwm; /* PWM 通道数量 */
int (*request)(struct pwm_chip *chip, struct pwm_device *pwm); /* 请求 PWM 通道 */
void (*free)(struct pwm_chip *chip, struct pwm_device *pwm); /* 释放 PWM 通道 */
int (*config)(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns); /* 配置 PWM */
void (*enable)(struct pwm_chip *chip, struct pwm_device *pwm); /* 启用 PWM 输出 */
void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm); /* 禁用 PWM 输出 */
};
需要实现的关键方法:
request
: 申请一个 PWM 通道。free
: 释放 PWM 通道。config
: 配置 PWM 通道的周期和占空比。enable
: 启用 PWM 输出。disable
: 禁用 PWM 输出。
实现 pwm_device
结构
pwm_device
是一个表示 PWM 输出通道的结构体。它通常由 pwm_chip
来管理。pwm_device
包含了具体 PWM 输出的属性,如占空比、周期等。
struct pwm_device {
struct pwm_chip *chip; /* 指向 PWM 控制器的指针 */
unsigned int hwpwm; /* 硬件 PWM 通道编号 */
bool enabled; /* 是否启用 */
unsigned long duty_cycle; /* 占空比 */
unsigned long period; /* 周期 */
};
初始化 pwm_chip
和 pwm_device
在驱动的初始化过程中,通常需要初始化 pwm_chip
和 pwm_device
结构。pwm_chip
结构通过 PWM 控制器的硬件接口来管理 PWM 通道,而每个 pwm_device
代表一个通道。
例如:
static int my_pwm_chip_probe(struct platform_device *pdev) {
struct pwm_chip *chip;
struct pwm_device *pwm;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->dev = &pdev->dev;
chip->npwm = 4; /* 假设该 PWM 控制器有 4 个通道 */
/* 初始化 PWM 控制器的方法 */
chip->request = my_pwm_chip_request;
chip->free = my_pwm_chip_free;
chip->config = my_pwm_chip_config;
chip->enable = my_pwm_chip_enable;
chip->disable = my_pwm_chip_disable;
/* 注册 PWM 控制器 */
return pwmchip_add(chip);
}
设备注册与解绑
注册 PWM 控制器设备时,使用 pwmchip_add()
函数,将 pwm_chip
结构添加到内核中。解绑时,使用 pwmchip_remove()
函数注销设备。
static int my_pwm_chip_remove(struct platform_device *pdev) {
struct pwm_chip *chip = platform_get_drvdata(pdev);
pwmchip_remove(chip);
return 0;
}
4. 实现 PWM 控制方法
驱动的核心是如何实现对硬件 PWM 控制器的控制。以下是一些常见的方法:
请求 PWM 通道
pwm_request
方法在设备启动时由内核调用,它应该验证并初始化 PWM 通道。
static int my_pwm_chip_request(struct pwm_chip *chip, struct pwm_device *pwm) {
/* 初始化 PWM 通道 */
return 0;
}
配置 PWM 信号
pwm_config
方法设置 PWM 的占空比和周期。例如,设置周期为 1000 毫秒,占空比为 500 毫秒:
static int my_pwm_chip_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) {
/* 配置 PWM 控制器的硬件寄存器 */
return 0;
}
启用和禁用 PWM 输出
pwm_enable
和 pwm_disable
方法控制 PWM 输出的启用和禁用。
static void my_pwm_chip_enable(struct pwm_chip *chip, struct pwm_device *pwm) {
/* 启用 PWM 输出 */
}
static void my_pwm_chip_disable(struct pwm_chip *chip, struct pwm_device *pwm) {
/* 禁用 PWM 输出 */
}
5. 注册与解绑设备
在 probe
和 remove
函数中,分别调用 pwmchip_add()
和 pwmchip_remove()
来注册和注销 PWM 控制器。
static struct platform_driver my_pwm_driver = {
.driver = {
.name = "my_pwm_driver",
.of_match_table = my_pwm_of_match,
},
.probe = my_pwm_chip_probe,
.remove = my_pwm_chip_remove,
};
6. 使用 sysfs 接口
为了让用户空间可以访问和控制 PWM,通常会通过 sysfs
提供一个接口。这些接口通常会暴露给用户空间应用,用于设置占空比、周期、启用/禁用等。
static ssize_t pwm_duty_cycle_show(struct device *dev, struct device_attribute *attr, char *buf) {
return sprintf(buf, "%lu\n", pwm->duty_cycle);
}
static ssize_t pwm_duty_cycle_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
/* 解析并设置占空比 */
return count;
}
static DEVICE_ATTR(duty_cycle, 0644, pwm_duty_cycle_show, pwm_duty_cycle_store);
7. 测试与调试
开发完 PWM 驱动后,需要进行全面的测试和调试,确保 PWM 控制器的功能正常。这可以通过:
- 使用
dmesg
查看内核日志。 - 使用
sysfs
接口手动配置 PWM。 - 通过应用程序测试 PWM 信号的输出效果。