PWM驱动与调节器框架详解
1. PWM驱动
PWM(脉冲宽度调制)驱动在嵌入式系统中有着广泛的应用,下面将详细介绍PWM驱动的相关内容。
1.1 PWM控制器绑定
在设备树(DT)中绑定PWM控制器时,
#pwm-cells
是最重要的属性,它表示用于表示该控制器的PWM设备的单元数量。如果
struct pwm_chip
结构中的
of_xlate
钩子未设置,
pwm-cells
必须设置为2;否则,应设置为与
of_pwm_n_cells
相同的值。
以下是一个i.MX6 SoC的PWM控制器节点示例:
pwm3: pwm@02088000 {
#pwm-cells = <2>;
compatible = "fsl,imx6q-pwm", "fsl,imx27-pwm";
reg = <0x02088000 0x4000>;
interrupts = <0 85 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6QDL_CLK_IPG>,
<&clks IMX6QDL_CLK_PWM3>;
clock-names = "ipg", "per";
status = "disabled";
};
对应的假PWM驱动节点如下:
fake_pwm: pwm@0 {
#pwm-cells = <2>;
compatible = "packt,fake-pwm";
/*
* Our driver does not use resource
* neither mem, IRQ, nor Clock)
*/
};
1.2 PWM消费者接口
消费者是实际使用PWM通道的设备,PWM通道在内核中由
struct pwm_device
结构表示。在kernel v4.7之前,该结构如下:
struct pwm_device {
const char *label;
unsigned long flags;
unsigned int hwpwm;
unsigned int pwm;
struct pwm_chip *chip;
void *chip_data;
unsigned int period; /* in nanoseconds */
unsigned int duty_cycle; /* in nanoseconds */
enum pwm_polarity polarity;
};
各字段含义如下:
-
label
:PWM设备的名称。
-
flags
:与PWM设备关联的标志。
-
hwpwm
:PWM设备相对于芯片的相对索引。
-
pwm
:PWM设备的系统全局索引。
-
chip
:提供此PWM设备的PWM芯片。
-
chip_data
:与此PWM设备关联的芯片私有数据。
自kernel v4.7起,结构变为:
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;
};
其中:
-
args
:表示与板相关的PWM参数,通常从PWM查找表或设备树中获取,代表用户希望在此PWM设备上使用的初始配置。
-
state
:表示当前PWM通道的状态。
struct pwm_args
和
struct pwm_state
结构如下:
struct pwm_args {
unsigned int period; /* Device's nitial period */
enum pwm_polarity polarity;
};
struct pwm_state {
unsigned int period; /* PWM period (in nanoseconds) */
unsigned int duty_cycle; /* PWM duty cycle (in nanoseconds) */
enum pwm_polarity polarity; /* PWM polarity */
bool enabled; /* PWM enabled status */
}
PWM框架在Linux的发展过程中经历了一些变化,消费者接口可分为两个版本:
-
旧版本
:使用
pwm_request()
和
pwm_free()
来请求和释放PWM设备。
-
新版本(推荐)
:使用
pwm_get()
和
pwm_put()
函数。
pwm_get()
以消费者设备和通道名称作为参数请求PWM设备,
pwm_put()
以要释放的PWM设备作为参数。还有管理变体
devm_pwm_get()
和
devm_pwm_put()
。
struct pwm_device *pwm_get(struct device *dev, const char *con_id)
void pwm_put(struct pwm_device *pwm)
需要注意的是,
pwm_request()/pwm_get()
和
pwm_free()/pwm_put()
不能在原子上下文中调用,因为PWM核心使用了可能会休眠的互斥锁。
请求PWM设备后,需要使用
pwm_config()
进行配置:
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
使用
pwm_enable()
和
pwm_disable()
来启动和停止PWM输出:
int pwm_enable(struct pwm_device *pwm)
void pwm_disable(struct pwm_device *pwm)
pwm_enable()
成功时返回0,失败时返回负错误码。
以下是一个驱动PWM LED的消费者代码示例:
static void pwm_led_drive(struct pwm_device *pwm,
struct private_data *priv)
{
/* Configure the PWM, applying a period and duty cycle */
pwm_config(pwm, priv->duty, priv->pwm_period);
/* Start toggling */
pwm_enable(pchip->pwmd);
[...] /* Do some work */
/* And then stop toggling*/
pwm_disable(pchip->pwmd);
}
1.3 PWM客户端绑定
PWM设备可以从以下方式分配给消费者:
- 设备树
- ACPI
- 板初始化文件中的静态查找表
这里推荐使用设备树绑定。绑定PWM消费者(客户端)到其驱动时,需要提供与之关联的控制器的句柄。建议将PWM属性命名为
pwms
,还可以提供可选属性
pwm-names
来命名
pwms
属性中列出的每个PWM设备。如果未提供
pwm-names
属性,则将使用用户节点的名称作为备用。
以下是一个基于PWM的背光设备示例:
pwm: pwm {
#pwm-cells = <2>;
};
[...]
bl: backlight {
pwms = <&pwm 0 5000000>;
pwm-names = "backlight";
};
pwms
中的
0
对应于相对于控制器的PWM索引,
5000000
表示周期(纳秒)。在这个例子中,指定
pwm-names
是多余的,因为
backlight
名称会作为备用。驱动需要调用:
static int my_consummer_probe(struct platform_device *pdev)
{
struct pwm_device *pwm;
pwm = pwm_get(&pdev->dev, "backlight");
if (IS_ERR(pwm)) {
pr_info("unable to request PWM, trying legacy API\n");
/*
* Some drivers use the legacy API as fallback, in order
* to request a PWM ID, global to the system
* pwm = pwm_request(global_pwm_id, "pwm beeper");
*/
}
[...]
return 0;
}
1.4 使用sysfs接口操作PWM
PWM核心的sysfs根路径是
/sys/class/pwm/
,这是用户空间管理PWM设备的方式。每个添加到系统的PWM控制器/芯片会在sysfs根路径下创建一个
pwmchipN
目录,其中
N
是PWM芯片的基址。该目录包含以下文件:
-
npwm
:只读文件,显示该芯片支持的PWM通道数量。
-
Export
:只写文件,用于导出PWM通道以供sysfs使用。
-
Unexport
:只写文件,用于从sysfs中取消导出PWM通道。
PWM通道编号从0到
pwm<n-1>
,这些编号是芯片本地的。每个PWM通道导出会在
pwmchipN
中创建一个
pwmX
目录,该目录包含以下文件:
-
Period
:可读写文件,用于获取/设置PWM信号的总周期(纳秒)。
-
duty_cycle
:可读写文件,用于获取/设置PWM信号的占空比(纳秒),值必须小于周期。
-
Polarity
:可读写文件,仅当PWM设备的芯片支持极性反转时使用,最好在PWM未启用时更改极性,接受的值为字符串
normal
或
inversed
。
-
Enable
:可读写文件,用于启用(开始切换)/禁用(停止切换)PWM信号,接受的值为
0
(禁用)和
1
(启用)。
以下是通过sysfs接口从用户空间使用PWM的示例:
1. 启用PWM:
# echo 1 > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/enable
- 设置PWM周期:
# echo <value in nanoseconds> > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/period
- 设置PWM占空比(值必须小于PWM周期):
# echo <value in nanoseconds> > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/duty_cycle
- 禁用PWM:
# echo 0 > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/enable
2. 调节器框架
调节器是为其他设备供电的电子设备,由调节器供电的设备称为消费者。大多数调节器可以启用和禁用其输出,有些还可以控制输出电压或电流。Linux调节器框架旨在接口和控制电压和电流调节器,分为以下四个独立接口:
- 调节器驱动接口,用于调节器PMIC驱动,其结构可在
include/linux/regulator/driver.h
中找到。
- 消费者接口,用于设备驱动。
- 机器接口,用于板配置。
- sysfs接口,用于用户空间。
2.1 PMIC/生产者驱动接口
生产者是产生调节后的电压或电流的设备,即PMIC(电源管理集成电路),可用于电源排序、电池管理、DC-DC转换或简单的电源开关(开/关)。它在软件控制下调节输入电源的输出功率。
该接口需要包含以下头文件:
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>
2.2 驱动数据结构
以下是调节器框架使用的数据结构介绍。
2.2.1 描述结构
内核通过
struct regulator_desc
结构描述PMIC提供的每个调节器,该结构包含调节器的固定属性。例如,Intersil的ISL6271A是一个具有三个独立调节输出的PMIC,其驱动中应该有三个
regulator_desc
实例。
struct regulator_desc {
const char *name;
const char *of_match;
int id;
unsigned n_voltages;
const struct regulator_ops *ops;
int irq;
enum regulator_type type;
struct module *owner;
unsigned int min_uV;
unsigned int uV_step;
};
各字段含义如下:
-
name
:调节器的名称。
-
of_match
:用于在DT中标识调节器的名称。
-
id
:调节器的数字标识符。
-
owner
:提供调节器的模块,应设置为
THIS_MODULE
。
-
type
:指示调节器是电压调节器还是电流调节器,值可以是
REGULATOR_VOLTAGE
或
REGULATOR_CURRENT
,其他值将导致调节器注册失败。
-
n_voltages
:该调节器可用的选择器数量,表示调节器可以输出的数值,对于固定输出电压,
n_voltages
应设置为1。
-
min_uV
:该调节器可以提供的最小电压值,即最低选择器给出的电压。
-
uV_step
:每个选择器的电压增量。
-
ops
:调节器操作表,指向调节器可以支持的一组操作回调。
-
irq
:调节器的中断号。
2.2.2 约束结构
当PMIC向消费者暴露调节器时,需要使用
struct regulation_constraints
结构为该调节器施加一些标称限制,这是调节器驱动和消费者驱动之间的一种契约。
struct regulation_constraints {
const char *name;
/* voltage output range (inclusive) - for voltage control */
int min_uV;
int max_uV;
int uV_offset;
/* current output range (inclusive) - for current control */
int min_uA;
int max_uA;
/* valid regulator operating modes for this machine */
unsigned int valid_modes_mask;
/* valid operations for regulator on this machine */
unsigned int valid_ops_mask;
struct regulator_state state_disk;
struct regulator_state state_mem;
struct regulator_state state_standby;
suspend_state_t initial_state; /* suspend state to set at init */
/* mode to set on startup */
unsigned int initial_mode;
/* constraint flags */
unsigned always_on:1; /* regulator never off when system is on */
unsigned boot_on:1; /* bootloader/firmware enabled regulator */
unsigned apply_uV:1; /* apply uV constraint if min == max */
};
各字段含义如下:
-
min_uV
、
min_uA
、
max_uA
和
max_uV
:消费者可以设置的最小电压/电流值。
-
uV_offset
:应用于消费者电压的偏移量,以补偿电压降。
-
valid_modes_mask
和
valid_ops_mask
:分别是消费者可以配置/执行的模式/操作的掩码。
-
always_on
:如果调节器不应被禁用,则应设置该标志。
-
boot_on
:如果调节器在系统初始启动时启用,则应设置该标志。如果调节器未由硬件或引导加载程序启用,则在应用约束时将启用它。
-
name
:用于显示目的的约束描述性名称。
-
apply_uV
:初始化时应用电压约束。
-
input_uV
:当该调节器由另一个调节器供电时的输入电压。
-
state_disk
、
state_mem
和
state_standby
:定义系统在磁盘模式、内存模式或待机模式下挂起时调节器的状态。
-
initial_state
:默认设置的挂起状态。
-
initial_mode
:启动时设置的模式。
2.2.3 初始化数据结构
有两种方式将
regulator_init_data
传递给驱动:通过板初始化文件中的平台数据或使用
of_get_regulator_init_data
函数从设备树节点中获取。
struct regulator_init_data {
struct regulation_constraints constraints;
/* optional regulator machine specific init */
int (*regulator_init)(void *driver_data);
void *driver_data; /* core does not touch this */
};
各字段含义如下:
-
constraints
:调节器约束。
-
regulator_init
:核心注册调节器时在给定时刻调用的可选回调。
-
driver_data
:传递给
regulator_init
的数据。
2.3 向板文件中填充初始化数据
这种方法是在驱动或板文件中填充约束数组,并将其作为平台数据的一部分。以下是基于Intersil的ISL6271A的示例:
static struct regulator_init_data isl_init_data[] = {
[0] = {
.constraints = {
.name = "Core Buck",
.min_uV = 850000,
.max_uV = 1600000,
.valid_modes_mask = REGULATOR_MODE_NORMAL
| REGULATOR_MODE_STANDBY,
.valid_ops_mask = REGULATOR_CHANGE_MODE
| REGULATOR_CHANGE_STATUS,
},
},
[1] = {
.constraints = {
.name = "LDO1",
.min_uV = 1100000,
.max_uV = 1100000,
.always_on = true,
.valid_modes_mask = REGULATOR_MODE_NORMAL
| REGULATOR_MODE_STANDBY,
.valid_ops_mask = REGULATOR_CHANGE_MODE
| REGULATOR_CHANGE_STATUS,
},
},
[2] = {
.constraints = {
.name = "LDO2",
.min_uV = 1300000,
.max_uV = 1300000,
.always_on = true,
.valid_modes_mask = REGULATOR_MODE_NORMAL
| REGULATOR_MODE_STANDBY,
.valid_ops_mask = REGULATOR_CHANGE_MODE
| REGULATOR_CHANGE_STATUS,
},
},
};
这种方法现已弃用,推荐使用设备树方法。
2.4 向设备树中填充初始化数据
为了从设备树中提取初始化数据,需要引入
struct of_regulator_match
结构:
struct of_regulator_match {
const char *name;
void *driver_data;
struct regulator_init_data *init_data;
struct device_node *of_node;
const struct regulator_desc *desc;
};
在设备树中,每个PMIC节点应该有一个名为
regulators
的子节点,其中需要将该PMIC提供的每个调节器声明为一个专用子节点。调节器节点可以定义以下标准化属性:
-
regulator-name
:用于调节器输出的描述性名称。
-
regulator-min-microvolt
:消费者可以设置的最小电压。
-
regulator-max-microvolt
:消费者可以设置的最大电压。
-
regulator-microvolt-offset
:应用于电压的偏移量,以补偿电压降。
-
regulator-min-microamp
:消费者可以设置的最小电流。
-
regulator-max-microamp
:消费者可以设置的最大电流。
-
regulator-always-on
:布尔值,指示调节器是否不应被禁用。
-
regulator-boot-on
:引导加载程序/固件启用的调节器。
-
<name>-supply
:指向父电源/调节器节点的句柄。
-
regulator-ramp-delay
:调节器的斜坡延迟(uV/uS)。
以下是ISL6271A驱动的设备树条目示例:
isl6271a@3c {
compatible = "isl6271a";
reg = <0x3c>;
interrupts = <0 86 0x4>;
/* supposing our regulator is powered by another regulator */
in-v1-supply = <&some_reg>;
[...]
regulators {
reg1: core_buck {
regulator-name = "Core Buck";
regulator-min-microvolt = <850000>;
regulator-max-microvolt = <1600000>;
};
reg2: ldo1 {
regulator-name = "LDO1";
regulator-min-microvolt = <1100000>;
regulator-max-microvolt = <1100000>;
regulator-always-on;
};
reg3: ldo2 {
regulator-name = "LDO2";
regulator-min-microvolt = <1300000>;
regulator-max-microvolt = <1300000>;
regulator-always-on;
};
};
};
通过以上内容,我们详细介绍了PWM驱动和调节器框架的相关知识,包括数据结构、接口和使用方法等。掌握这些知识可以帮助我们更好地开发和优化相关驱动程序。
PWM驱动与调节器框架详解
3. 总结与回顾
PWM驱动和调节器框架在嵌入式系统中起着至关重要的作用。对于PWM驱动,我们了解到它涵盖了控制器绑定、消费者接口、客户端绑定以及sysfs接口操作等多个方面:
-
PWM控制器绑定
:关键在于
#pwm-cells
属性,其值的设定取决于
of_xlate
钩子是否设置。
-
PWM消费者接口
:经历了版本的演变,新版本推荐使用
pwm_get()
和
pwm_put()
函数,请求设备后需进行配置和启动/停止操作。
-
PWM客户端绑定
:推荐使用设备树绑定,通过
pwms
和
pwm-names
属性进行配置。
-
sysfs接口操作
:提供了用户空间管理PWM设备的方式,通过读写不同文件实现对PWM通道的控制。
而调节器框架则主要分为四个接口,其中PMIC/生产者驱动接口涉及多个数据结构:
-
描述结构
:
struct regulator_desc
用于描述调节器的固定属性。
-
约束结构
:
struct regulation_constraints
为调节器施加标称限制。
-
初始化数据结构
:
struct regulator_init_data
可通过板文件或设备树传递初始化数据。
下面通过一个表格来对比PWM驱动和调节器框架的关键信息:
| 类别 | 关键元素 | 说明 |
| ---- | ---- | ---- |
| PWM驱动 |
#pwm-cells
| 控制器绑定中重要属性,决定PWM设备单元数量 |
| |
pwm_get()
/
pwm_put()
| 消费者接口新版本函数,用于请求和释放PWM设备 |
| |
pwms
/
pwm-names
| 客户端绑定中用于指定PWM设备和名称 |
| |
/sys/class/pwm/
| sysfs接口根路径,用于用户空间管理PWM设备 |
| 调节器框架 |
struct regulator_desc
| 描述调节器固定属性的数据结构 |
| |
struct regulation_constraints
| 为调节器施加限制的约束结构 |
| |
struct regulator_init_data
| 传递初始化数据的数据结构 |
| |
regulators
子节点 | 设备树中PMIC节点下用于声明调节器的子节点 |
4. 实际应用与流程示例
为了更好地理解PWM驱动和调节器框架的实际应用,下面分别给出一个PWM驱动控制LED和调节器框架配置电源的流程示例。
4.1 PWM驱动控制LED流程
graph LR
A[初始化PWM设备] --> B[请求PWM设备]
B --> C[配置PWM参数]
C --> D[启动PWM输出]
D --> E[执行其他操作]
E --> F[停止PWM输出]
F --> G[释放PWM设备]
具体操作步骤如下:
1.
初始化PWM设备
:在代码中包含必要的头文件,定义相关数据结构。
2.
请求PWM设备
:使用
pwm_get()
函数请求PWM设备。
struct pwm_device *pwm;
pwm = pwm_get(&pdev->dev, "backlight");
if (IS_ERR(pwm)) {
pr_info("unable to request PWM, trying legacy API\n");
// 尝试旧版本API
}
-
配置PWM参数
:使用
pwm_config()函数配置PWM的占空比和周期。
pwm_config(pwm, priv->duty, priv->pwm_period);
-
启动PWM输出
:使用
pwm_enable()函数启动PWM输出。
pwm_enable(pwm);
- 执行其他操作 :在PWM输出启动后,可以执行其他相关操作。
-
停止PWM输出
:使用
pwm_disable()函数停止PWM输出。
pwm_disable(pwm);
-
释放PWM设备
:使用
pwm_put()函数释放PWM设备。
pwm_put(pwm);
4.2 调节器框架配置电源流程
graph LR
A[初始化调节器数据] --> B[查找调节器节点]
B --> C[提取初始化数据]
C --> D[注册调节器]
D --> E[设备请求电源]
E --> F[调节器提供电源]
F --> G[设备使用电源]
G --> H[设备释放电源]
H --> I[调节器停止供电]
具体操作步骤如下:
1.
初始化调节器数据
:定义
struct regulator_desc
、
struct regulation_constraints
和
struct regulator_init_data
等数据结构,并进行初始化。
struct regulator_desc my_regulator_desc = {
.name = "my_regulator",
.of_match = "my_regulator_match",
.id = 0,
.n_voltages = 1,
.min_uV = 1000000,
.uV_step = 100000,
// 其他字段初始化
};
struct regulation_constraints my_constraints = {
.name = "my_constraints",
.min_uV = 1000000,
.max_uV = 2000000,
// 其他字段初始化
};
struct regulator_init_data my_init_data = {
.constraints = my_constraints,
// 其他字段初始化
};
-
查找调节器节点
:在设备树中查找PMIC节点及其
regulators子节点。 -
提取初始化数据
:使用
of_get_regulator_init_data函数从设备树中提取初始化数据。
struct of_regulator_match my_match;
// 初始化my_match
of_get_regulator_init_data(of_node, my_match.desc, &my_match.init_data);
- 注册调节器 :将调节器注册到系统中。
// 注册调节器的代码
- 设备请求电源 :设备驱动使用相关函数请求调节器提供电源。
struct regulator *my_regulator;
my_regulator = regulator_get(&pdev->dev, "my_regulator");
if (IS_ERR(my_regulator)) {
pr_err("Failed to get regulator\n");
}
- 调节器提供电源 :调节器根据请求提供电源。
- 设备使用电源 :设备使用调节器提供的电源进行工作。
- 设备释放电源 :设备使用完毕后,释放电源。
regulator_put(my_regulator);
- 调节器停止供电 :调节器停止向设备供电。
通过以上的总结、对比和实际应用流程示例,我们对PWM驱动和调节器框架有了更深入的理解。在实际开发中,可以根据具体需求灵活运用这些知识,开发出高效、稳定的嵌入式系统。
1010

被折叠的 条评论
为什么被折叠?



