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