跳到主要内容

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用户使用说明
pwm0GPIO220、GPIO75可用
pwm1GPIO221、GPIO78可用
pwm2GPIO222、GPIO83可用
pwm3GPIO223、GPIO85保留
pwm4GPIO224、GPIO87可用
pwm5GPIO225、GPIO89可用
pwm6GPIO226、GPIO90可用
pwm7GPIO227、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
40000PWM 初始化周期,单位 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

相关属性说明:

属性说明
periodPWM 周期,单位 ns
duty_cycle高电平时间,单位 ns,必须小于或等于 period
enablePWM 开启或关闭,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_num0 或 1
channel0、1、2
period_ns20 ns ~ 40000 ns
duty_ns0 ~ period_ns

entrytable 配置

entrytable 提供在开机时的一路 PWM 配置入口。在 AIModule 上,该入口用于风扇 PWM 输出控制。m1000_entry_XXX.xlsxboard__cap_info_entry 中可配置如下字段:

namedescriptionsizevalue
fan_enable风扇控制开关11
pwm_channelPWM 输出 channel16
fan_pwm_duty风扇控制 PWM 占空比150
fan_pwm_freq风扇控制 PWM 频率,单位 Hz4100000

字段说明:

  • 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_NamePower_DomainFunction_0Function_1Function_2Function_3Default FunctionDefault Pull TypeDefault DriveStrengthGroup_Name
GPIO_090VDDIO1GPIO_090PWM_OUT6Function_2PD8mAFan Ctrl

内核配置

Linux 内核需要使能 PWM framework、M1000 PWM controller 驱动以及对应 consumer 驱动。通常需要在kernel的menuconfig里关注如下配置项:

CONFIG_PWM=y
CONFIG_PWM_SYSFS=y

如果通过自定义 consumer 驱动使用 PWM,还需要将该驱动编入内核或编译为模块,并确保设备树 compatible 与驱动 of_match_table 匹配。

调试方法说明

PWM 调试建议按以下顺序进行:

  1. 确认设备树中 PWM controller 的 status = "okay"
  2. 确认gpio_config.xlsx 已将目标 GPIO 配置为对应 PWM function。
  3. 使用 sysfs 或 U-Boot pwm 命令配置 periodduty_cycle 并使能输出。
  4. 使用示波器测量输出 pin,确认频率和占空比符合预期。
  5. 若使用 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/pwmNpwmN 是该 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.xlsxDefault Function 配置。以 pwm6 输出到 GPIO90 为例,需要将 GPIO90 的默认功能配置为 PWM_OUT6,即 Function_2