Linux PWM 驱动

发布时间:2026/10/11 17:18:42
Linux PWM 驱动
一、PWM驱动简析1、设备树下的PWM控制器节点IMX6ULL 有 8 路 PWM 输出因此对应 8 个 PWM 控制器所有在设备树下就有 8 个 PWM 控制器节点。这 8 路 PWM 都属于 I.MX6ULL 的 AIPS-1 域但是在设备树 imx6ull.dtsi 中分为了两部分PWM1~PWM4 在一起PWM5~PWM8 在一起这是 8 路 PWM 并没有全部放到一起。这 8 路 PWM 的设备树节点内容都是一样的除了 reg 属性不同 (毕竟不同的控制器其地址范围不同)。本章实验我们使用 GPIO1_IO04 这个引脚来完成 PWM 实验而 GPIO1_IO04 就是 PWM3 的输出引脚所以这里我们就以 PWM3 为例进行讲解imx6ull.dtsi 代码 73.1.1 pwm3 节点信息如下1 pwm3: pwm02088000 { 2 compatible fsl,imx6ul-pwm, fsl,imx27-pwm; 3 reg 0x02088000 0x4000; 4 interrupts GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH; 5 clocks clks IMX6UL_CLK_PWM3, 6 clks IMX6UL_CLK_PWM3; 7 clock-names ipg, per; 8 #pwm-cells 2; 9 };第 2 行compatible 属性值有两个 “fsl,imx6ul-pwm” 和 “fsl,imx27-pwm”所以在整个 Linux 源码里面搜索这两个字符串即可找到 I.MX6ULL 的 PWM 驱动文件这个文件就是 drivers/pwm/pwm-imx.c。2、PWM 子系统Linux 内核提供了个 PWM 子系统框架编写 PWM 驱动的时候一定要符合这个框架。PWM 子系统的核心是pwm_chip结构体定义在文件include/linux/pwm.h中定义如下1 struct pwm_chip { 2 struct device *dev; 3 struct list_head list; 4 const struct pwm_ops *ops; 5 int base; 6 unsigned int npwm; 7 struct pwm_device *pwms; 8 struct pwm_device * (*of_xlate)(struct pwm_chip *pc, 9 const struct of_phandle_args *args); 10 unsigned int of_pwm_n_cells; 11 bool can_sleep; 12 };第 4 行pwm_ops结构体就是 PWM 外设的各种操作函数集合编写 PWM 外设驱动的时候需要开发人员实现。pwm_ops结构体也定义在 pwm.h 头文件中定义如下1 struct pwm_ops { 2 int (*request)(struct pwm_chip *chip, //请求PWM 3 struct pwm_device *pwm); 4 void (*free)(struct pwm_chip *chip, //释放PWM 5 struct pwm_device *pwm); 6 int (*config)(struct pwm_chip *chip, //配置PWM周期和占空比 7 struct pwm_device *pwm, 8 int duty_ns, int period_ns); 9 int (*set_polarity)(struct pwm_chip *chip, //设置PWM极性 10 struct pwm_device *pwm, 11 enum pwm_polarity polarity); 12 int (*enable)(struct pwm_chip *chip, //使能PWM 13 struct pwm_device *pwm); 14 void (*disable)(struct pwm_chip *chip, //关闭PWM 15 struct pwm_device *pwm); 16 struct module *owner; 17 };pwm_ops中的这些函数不一定全部实现但是像config、enable和disable这些肯定是需要实现的否则的话打开 / 关闭 PWM设置 PWM 的占空比这些就没操作了。PWM 子系统驱动的核心就是初始化pwm_chip结构体各成员变量然后向内核注册初始化完成以后的pwm_chip。这里就要用到pwmchip_add函数此函数定义在drivers/pwm/core.c文件中函数原型如下int pwmchip_add(struct pwm_chip *chip)函数参数和返回值含义如下chip要向内核注册的 pwm_chip。返回值0 成功负数 失败。卸载 PWM 驱动的时候需要将前面注册的 pwm_chip 从内核移除掉这里要用到pwmchip_remove函数函数原型如下int pwmchip_remove(struct pwm_chip *chip)函数参数和返回值含义如下chip要移除的 pwm_chip。返回值0 成功负数 失败。3、PWM 驱动源码分析我们简单分析一下 Linux 内核自带的 I.MX6ULL PWM 驱动驱动文件前面都说了是 pwm-imx.c 这个文件。打开这个文件可以看到这是一个标准的平台设备驱动文件如下所示1 static const struct of_device_id imx_pwm_dt_ids[] { 2 { .compatible fsl,imx1-pwm, .data imx_pwm_data_v1, }, 3 { .compatible fsl,imx27-pwm, .data imx_pwm_data_v2, }, 4 { /* sentinel */ } 5 }; 6 7 ...... 8 static struct platform_driver imx_pwm_driver { 9 .driver { 10 .name imx-pwm, 11 .of_match_table imx_pwm_dt_ids, 12 }, 13 .probe imx_pwm_probe, 14 .remove imx_pwm_remove, 15 }; 16 17 module_platform_driver(imx_pwm_driver);第 3 行当设备树节点的 compatible 属性值为 “fsl,imx27-pwm” 的话就会匹配此驱动注意后面的.data为imx_pwm_data_v2这是一个imx_pwm_data类型的结构体变量内容如下1 static struct imx_pwm_data imx_pwm_data_v2 { 2 .config imx_pwm_config_v2, 3 .set_enable imx_pwm_set_enable_v2, 4 };imx_pwm_config_v2函数就是最终操作 I.MX6ULL 的 PWM 外设寄存器进行实际配置的函数。imx_pwm_set_enable_v2就是具体使能 PWM 的函数。第 14 行当设备树节点和驱动匹配以后imx_pwm_probe函数就会执行。imx_pwm_probe函数如下 (有缩减)1 static int imx_pwm_probe(struct platform_device *pdev) 2 { 3 const struct of_device_id *of_id 4 of_match_device(imx_pwm_dt_ids, pdev-dev); 5 const struct imx_pwm_data *data; 6 struct imx_chip *imx; 7 struct resource *r; 8 int ret 0; 9 10 if (!of_id) 11 return -ENODEV; 12 13 imx devm_kzalloc(pdev-dev, sizeof(*imx), GFP_KERNEL); 14 if (imx NULL) 15 return -ENOMEM; ...... 31 imx-chip.ops imx_pwm_ops; 32 imx-chip.dev pdev-dev; 33 imx-chip.base -1; 34 imx-chip.npwm 1; 35 imx-chip.can_sleep true; 36 37 r platform_get_resource(pdev, IORESOURCE_MEM, 0); 38 imx-mmio_base devm_ioremap_resource(pdev-dev, r); 39 if (IS_ERR(imx-mmio_base)) 40 return PTR_ERR(imx-mmio_base); 41 42 data of_id-data; 43 imx-config >所以我们需要在设备树里面添加 GPIO1_IO04 的引脚信息以及 PWM3控制器对应的节点信息。1.1、添加 GPIO1_IO04 引脚信息打开 imx6ull-alientek-emmc.dts 文件在 iomuxc 节点下添加 GPIO1_IO04 的引脚信息如下所示1 pinctrl_pwm3: pwm3grp { 2 fsl,pins 3 MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110b0 4 ; 5 };1.2、向 pwm3 节点追加信息前面已经讲过了imx6ull.dtsi 文件中已经有了 “pwm3” 节点但是还不能直接使用需要在 imx6ull-alientek-emmc.dts 文件中向 pwm3 节点追加一些内容在 imx6ull-alientek-emmc.dts 文件中加入如下所示内容1 pwm3 { 2 pinctrl-names default; 3 pinctrl-0 pinctrl_pwm3; 4 clocks clks IMX6UL_CLK_PWM3, 5 clks IMX6UL_CLK_PWM3; 6 status okay; 7 };第 3 行pinctrl-0 属性指定 PWM3 所使用的输出引脚对应的 pinctrl 节点这里设置为之前代码中的 pinctrl_pwm3。第 4 和 5 行设置时钟第 4 行设置 ipg 时钟第 5 行设置 per 时钟。有些 pwm 节点默认时钟源是 IMX6UL_CLK_DUMMY这里我们需要将其改为对应的时钟比如这里设置为 IMX6UL_CLK_PWM3。PWM1~PWM8 分别对应 IMX6UL_CLK_PWM1~ IMX6UL_CLK_PWM8。1.3、屏蔽掉其他复用的 IO检查一下设备树中有没有其他外设用到 GPIO1_IO04如果有的话需要屏蔽掉注意不能只屏蔽掉 GPIO1_IO04 的 pinctrl 配置信息也要搜索一下 “gpio1 4”看看有没有哪里用到用到的话也要屏蔽掉。设备树修改完成以后重新编译设备树然后使用新的设备树启动系统。2、使能 PWM 驱动打开 Linux 内核配置界面按照如下路径找到配置项- Device Drivers - Pulse-Width Modulation (PWM) Support - * i.MX PWM support配置如图所示三、PWM 驱动测试使用新的设备树启动系统然后将开发板 GPIO1_IO04 引脚连接到示波器上通过示波器来查看 PWM 波形图。我们可以直接在用户层来配置 PWM进入目录/sys/class/pwm中如图所示图中 pwmchip0~pwmchip7 对应 I.MX6ULL 的 PWM1~PWM8所以我们需要用到 pwmchip2。1、调出 pwmchip2 的 pwm0 子目录首先需要调出 pwmchip2 下的 pwm0 目录否则后续就没法操作了输入如下命令echo 0 /sys/class/pwm/pwmchip2/export执行完成会在 pwmchip2 目录下生成一个名为 “pwm0” 的子目录如图所示2、使能 PWM3输入如下命令使能 PWM3echo 1 /sys/class/pwm/pwmchip2/pwm0/enable3、设置 PWM3 的频率注意这里设置的是周期值单位为 ns比如 20KHz 频率的周期就是 50000ns输入如下命令echo 50000 /sys/class/pwm/pwmchip2/pwm0/period4、设置 PWM3 的占空比这里不能直接设置占空比而是设置的一个周期的 ON 时间也就是高电平时间比如 20KHz 频率下 20% 占空比的 ON 时间就是 10000输入如下命令echo 10000 /sys/class/pwm/pwmchip2/pwm0/duty_cycle设置完成使用示波器查看波形是否正确正确的话如图所示从图可以看出此时 PWM 频率为 20KHz占空比为 20%与我们设置的一致。如果要修改频率或者占空比的话一定要注意这两者时间值比如 20KHz 频率的周期值为 50000ns那么你在调整占空比的时候 ON 时间就不能设置大于 50000否则就会提示你参数无效。四、PWM 背光设置有时候我们需要在某个外设上添加 PWM 功能比如LCD 的背光控制就是 PWM 来完成的接下来我们就以 PWM 背光控制为例学习一下如何在其他外设上添加 PWM 功能。首先肯定是设备树描述直接看 linux 内核里面关于 backlight (背光) 的绑定文档路径为Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt此文档描述了如何创建 backlight 节点来使用 linux 内核自带的 pwm 背光驱动。必要的属性如下compatible内容必须为pwm-backlight通过这个可以匹配到内核自带的 PWM 背光驱动驱动文件为drivers/video/backlight/pwm_bl.c这里就不去分析驱动源码了。pwms此属性指定背光使用哪一路 PWM以及 PWM 相关的属性。brightness-levels背光等级数组范围 0~255对应占空比为 0%~100%。数组内的值必须从 0 开始也就是 0% 占空比最后一个值必须是 255也就是 100% 占空比。数组中间值的个数以及值大小可以自行定义。default-brightness-level默认的背光等级也就是 brightness-levels 属性中第几个值注意这里是索引编号不是具体的数值power-supply支持的电压此属性可以不需要。打开imx6ull-alpha-emmc.dts找到如下所示节点内容1 backlight { 2 compatible pwm-backlight; 3 pwms pwm1 0 5000000; 4 brightness-levels 0 4 8 16 32 64 128 255; 5 default-brightness-level 7; 6 status okay; 7 };第 2 行compatible 属性必须为pwm-backlight。第 3 行pwms 属性指定背光所使用的 pwm 通道第一个参数指定使用 pwm1由于 I.MX6ULL 的 PWM 只有一个通道因此这里为 0。最后一个参数是 PWM 周期单位为 ns这里 PWM 周期为 5000000ns频率为 200Hz。第 4 行背光等级数组一共 8 个等级索引编号从 0 到 7。第 5 行背光默认处于第 7 等级也就是 255为 100% 占空比。关于 pwm 做背光控制就讲解到这里pwm 做其他外设某个功能的时候要具体问题具体分析。觉得有帮助的话打赏一下呗。。