PWM
功能描述
M1000 内置两个 PWM controller,每个 controller 提供 4 路 PWM 输出:
- PWM controller0:pwm0 ~ pwm3
- PWM controller1:pwm4 ~ pwm7
每个 controller 的前 3 个 channel 可供用户使用,最后 1 个 channel 为保留 channel。即用户通常可使用 pwm0 ~ pwm2、pwm4 ~ pwm6;pwm3 和 pwm7 如无板级设计明确说明,不建议作为用户 PWM 输出使用。
PWM 输出频率可配置范围为 25 KHz ~ 50 MHz,占空比可配置范围为 0% ~ 100%。频率、周期和占空比的关系如下:
period_ns = 1000000000 / frequency_hz
duty_percent = duty_cycle_ns * 100 / period_ns
每路 PWM channel 可复用的 GPIO pinmux 如下:
| PWM channel | 可选 GPIO pin | 用户使用说明 |
|---|---|---|
| pwm0 | GPIO220、GPIO75 | 可用 |
| pwm1 | GPIO221、GPIO78 | 可用 |
| pwm2 | GPIO222、GPIO83 | 可用 |
| pwm3 | GPIO223、GPIO85 | 保留 |
| pwm4 | GPIO224、GPIO87 | 可用 |
| pwm5 | GPIO225、GPIO89 | 可用 |
| pwm6 | GPIO226、GPIO90 | 可用 |
| pwm7 | GPIO227、GPIO91 | 保留 |
每一路 PWM channel 只能从对应的两个 GPIO 中选择一个作为输出 pin,具体选择通过entrytable启动配置完成。
软件介绍
M1000 PWM 软件栈分为 PWM provider 和 PWM consumer 两部分。
PWM provider 是 M1000 PWM controller 驱动,负责注册 PWM controller,并提供 channel、period、duty cycle、enable 等基础控制能力。设备树中的 pwm0: pwm@700000 这类节点表示 PWM controller,不表示某一路输出 channel。
PWM consumer 是使用 PWM 输出的业务驱动,例如风扇、背光、蜂鸣器或用户自定义设备驱动。consumer 通过设备树的 pwms 属性引用 PWM controller 和 channel,并在驱动中调用 PWM framework API 配置周期、占空比和使能状态。
Linux 下可通过两种方式使用 PWM:
- sysfs:适合调试和手动验证 PWM 输出,不建议作为正式产品逻辑。
- consumer 驱动:适合正式业务场景,由内核驱动在 probe、remove、suspend、resume 等生命周期内管理 PWM。
U-Boot 阶段提供 PWM 命令用于启动阶段调试,也可通过板级启动配置固定输出参数。entrytable还独立支持在开机时配置一路 PWM参数,当前主要用于 AIModule 风扇控制。
配置流程
Linux 设备树配置
Linux 中 PWM controller 的设备树示例如下:
pwm0: pwm@700000 {
compatible = "mthreads,M1000-pwm";
reg = <0x0 0x00700000 0x0 0x1000>;
mode = <0>;
#pwm-cells = <2>;
clock-names = "peri_pwm_pclk", "peri_pwm_clk";
clocks = <&crg PERI_PWM0_PFUNC_CLK>, <&crg PERI_PWM0_MFUNC_CLK>;
reset-names = "pwm_reset";
resets = <&crg RESET_PERI_PWM0_PRST>;
interrupts = <GIC_SPI 0x10E IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
};
consumer 设备通过 pwms 属性引用 PWM controller:
pwmtest {
compatible = "mthreads,pwm-test";
pwm-names = "pwm-out";
pwms = <&pwm0 0 40000>;
duty-ns = <20000>;
status = "okay";
};
pwms = <&pwm0 0 40000> 的含义如下:
| 字段 | 含义 |
|---|---|
&pwm0 | 引用 PWM controller 节点,即 PWM provider |
0 | 使用该 controller 的 channel 0 |
40000 | PWM 初始化周期,单位 ns |
pwm-names 是 consumer 侧的连接名称,可以由用户自定义,但必须和驱动中 pwm_get() 或 devm_pwm_get() 使用的名称一致。例如设备树配置 pwm-names = "pwm-out",驱动中应使用 devm_pwm_get(dev, "pwm-out") 获取该 PWM。
M1000 PWM controller 当前使用 #pwm-cells = <2>,因此 pwms 中只表达 channel 和 period,不包含 duty cycle。占空比需要由 consumer 驱动配置,或由 consumer 自定义属性表达,例如上例中的 duty-ns = <20000>。duty-ns 不是 PWM framework 的通用 controller 属性,只有对应 consumer 驱动读取并处理该属性时才会生效。
GPIO pinmux 配置
PWM 输出前必须确保对应 GPIO 已切换到 PWM function。PWM输出一般不在内核里做时分复用,所以建议在entrytable中先绑定好所需使用的管脚。
Linux consumer 驱动使用
设备树中新增 pwmtest 节点后,系统不会自动输出 PWM。必须存在与 compatible 匹配的内核驱动,并由该驱动获取和配置 PWM。
驱动中通常使用 devm_pwm_get() 获取 struct pwm_device,再使用 pwm_config()、pwm_enable() 和 pwm_disable() 控制 PWM。示例:
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
struct m1000_pwm_test {
struct pwm_device *pwm;
};
static int m1000_pwm_test_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct m1000_pwm_test *priv;
struct pwm_args args;
u32 duty_ns;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->pwm = devm_pwm_get(dev, "pwm-out");
if (IS_ERR(priv->pwm)) {
ret = PTR_ERR(priv->pwm);
dev_err(dev, "failed to get pwm: %d\n", ret);
return ret;
}
pwm_get_args(priv->pwm, &args);
if (!args.period) {
dev_err(dev, "invalid pwm period\n");
return -EINVAL;
}
ret = of_property_read_u32(dev->of_node, "duty-ns", &duty_ns);
if (ret)
duty_ns = args.period / 2;
if (duty_ns > args.period) {
dev_err(dev, "duty-ns must not exceed period\n");
return -EINVAL;
}
ret = pwm_config(priv->pwm, duty_ns, args.period);
if (ret) {
dev_err(dev, "failed to config pwm: %d\n", ret);
return ret;
}
ret = pwm_enable(priv->pwm);
if (ret) {
dev_err(dev, "failed to enable pwm: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, priv);
return 0;
}
static int m1000_pwm_test_remove(struct platform_device *pdev)
{
struct m1000_pwm_test *priv = platform_get_drvdata(pdev);
pwm_disable(priv->pwm);
return 0;
}
static const struct of_device_id m1000_pwm_test_of_match[] = {
{ .compatible = "mthreads,pwm-test" },
{ }
};
MODULE_DEVICE_TABLE(of, m1000_pwm_test_of_match);
static struct platform_driver m1000_pwm_test_driver = {
.probe = m1000_pwm_test_probe,
.remove = m1000_pwm_test_remove,
.driver = {
.name = "m1000-pwm-test",
.of_match_table = m1000_pwm_test_of_match,
},
};
module_platform_driver(m1000_pwm_test_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("M1000 PWM test consumer driver");
sysfs 调试
进入 PWM controller 对应的 sysfs 目录:
sudo su
cd /sys/class/pwm/pwmchip0
导出对应 PWM channel,其中 n 为 channel 编号。用户可用 channel 通常为 0 ~ 2:
echo n > export
进入导出的 PWM channel 目录并配置输出:
cd /sys/class/pwm/pwmchip0/pwm1
echo 40000 > period
echo 20000 > duty_cycle
echo 1 > enable
相关属性说明:
| 属性 | 说明 |
|---|---|
period | PWM 周期,单位 ns |
duty_cycle | 高电平时间,单位 ns,必须小于或等于 period |
enable | PWM 开启或关闭,1 表示开启,0 表示关闭 |
/sys/class/pwm/pwmchipX/pwmN 中的 pwmN 表示该 pwmchipX 下的 channel N;设备树 pwm0: pwm@700000 中的 pwm0 是 PWM controller label。二者不是同一个概念。
consumer 设备树中的 pwms = <&pwm0 1 40000> 与 sysfs 中的 /sys/class/pwm/pwmchipX/pwm1 对应的是同一个 controller 下的 channel 1。需要注意,Linux 中 pwmchipX 的编号可能随注册顺序变化,不能把 pwmchip0 绝对等同于设备树 label pwm0。
调试完成后可关闭并取消导出:
echo 0 > enable
cd /sys/class/pwm/pwmchip0
echo 1 > unexport
U-Boot 设备树配置
PWM 支持在 booting 阶段配置输出参数。配置前必须严格确认板级硬件不存在冲突,例如同一 GPIO 未被其他外设占用、输出不会影响电源时序或外部器件状态。
设备树配置示例:
/* 先使能 PWM controller 设备 */
&pwm1 {
status = "okay";
};
some_device {
pwms = <&pwm1 0 40000>;
duty-ns = <20000>;
status = "okay";
};
U-Boot 提供如下 PWM 调试命令:
pwm config <pwm_dev_num> <channel> <period_ns> <duty_ns>
pwm enable <pwm_dev_num> <channel>
pwm disable <pwm_dev_num> <channel>
参数约束如下:
| 参数 | 取值范围 |
|---|---|
pwm_dev_num | 0 或 1 |
channel | 0、1、2 |
period_ns | 20 ns ~ 40000 ns |
duty_ns | 0 ~ period_ns |
entrytable 配置
entrytable 提供在开机时的一路 PWM 配置入口。在 AIModule 上,该入口用于风扇 PWM 输出控制。m1000_entry_XXX.xlsx 的 board__cap_info_entry 中可配置如下字段:
| name | description | size | value |
|---|---|---|---|
fan_enable | 风扇控制开关 | 1 | 1 |
pwm_channel | PWM 输出 channel | 1 | 6 |
fan_pwm_duty | 风扇控制 PWM 占空比 | 1 | 50 |
fan_pwm_freq | 风扇控制 PWM 频率,单位 Hz | 4 | 100000 |
字段说明:
fan_enable:风扇控制开关。设置为 1 时,当前字段组才会生效。pwm_channel:PWM 输出 channel,可用范围为 0 ~ 2、4 ~ 6。fan_pwm_duty:PWM 占空比,单位为百分比。fan_pwm_freq:PWM 频率,单位为 Hz。
entrytable 中可以直接设置占空比和频率,不需要手动换算周期时间。
如果需要指定风扇输出 pin,还需要修改 gpio_config.xlsx 中对应 GPIO 的 Default Function。例如将 GPIO90 绑定到 PWM_OUT6:
| PAD_Name | Power_Domain | Function_0 | Function_1 | Function_2 | Function_3 | Default Function | Default Pull Type | Default DriveStrength | Group_Name |
|---|---|---|---|---|---|---|---|---|---|
| GPIO_090 | VDDIO1 | GPIO_090 | PWM_OUT6 | Function_2 | PD | 8mA | Fan Ctrl |
内核配置
Linux 内核需要使能 PWM framework、M1000 PWM controller 驱动以及对应 consumer 驱动。通常需要在kernel的menuconfig里关注如下配置项:
CONFIG_PWM=y
CONFIG_PWM_SYSFS=y
如果通过自定义 consumer 驱动使用 PWM,还需要将该驱动编入内核或编译为模块,并确保设备树 compatible 与驱动 of_match_table 匹配。
调试方法说明
PWM 调试建议按以下顺序进行:
- 确认设备树中 PWM controller 的
status = "okay"。 - 确认
gpio_config.xlsx已将目标 GPIO 配置为对应 PWM function。 - 使用 sysfs 或 U-Boot
pwm命令配置period、duty_cycle并使能输出。 - 使用示波器测量输出 pin,确认频率和占空比符合预期。
- 若使用 consumer 驱动,查看
dmesg中是否存在 PWM 获取失败、pinmux 冲突或参数非法的日志。
需要注意:
duty_cycle不能大于period。period需要在硬件支持范围内,即 20 ns ~ 40000 ns。pwmchipX编号可能变化,调试时需要根据 sysfs 下实际注册顺序确认。- 同一路 PWM channel 不能同时复用到两个 GPIO。
- 保留 channel 不建议作为用户输出使用。
常见问题
Q:sysfs 中的 pwmN 与设备树中的 pwmN 是一一对应的吗?
A:不是同一个概念。设备树中的 pwm0: pwm@700000 是 PWM controller 的 label;sysfs 中 /sys/class/pwm/pwmchipX/pwmN 的 pwmN 是该 controller 下的 channel N。consumer 设备树中的 pwms = <&pwm0 1 40000> 与 sysfs 的 channel 1 对应,但 pwmchipX 的编号由内核注册顺序决定,不能固定认为 pwmchip0 一定对应设备树 label pwm0。
Q:pwm-names 是用户自定义的吗?
A:是 consumer 侧自定义名称,注意必须和驱动中获取 PWM 时使用的名称一致。例如设备树写 pwm-names = "pwm-out",驱动中应调用 devm_pwm_get(dev, "pwm-out")。
Q:pwms = <&pwm0 1 2560> 每个参数是什么意思?
A:&pwm0 表示引用 PWM controller 0,1 表示该 controller 的 channel 1,2560 表示周期为 2560 ns。
Q:如何在设备树中配置占空比?
A:M1000 PWM controller 的 #pwm-cells = <2> 只包含 channel 和 period,不包含 duty cycle。占空比不能仅靠 controller 的通用 pwms 属性配置,必须由 consumer 驱动设置。可以在 consumer 节点中增加自定义属性,例如 duty-ns = <20000>,再由对应驱动读取并调用 PWM API 配置。
Q:如何配置 PWM 的 GPIO pinmux?
A:一般在entrytable中通过 gpio_config.xlsx 的 Default Function 配置。以 pwm6 输出到 GPIO90 为例,需要将 GPIO90 的默认功能配置为 PWM_OUT6,即 Function_2。

