OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 4193|回复: 0

《I.MX6U嵌入式Linux驱动开发指南V1.0》第七十三章 Linux PWM驱动实验

[复制链接]

1117

主题

1128

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4666
金钱
4666
注册时间
2019-5-8
在线时间
1224 小时
发表于 2021-9-24 11:15:02 | 显示全部楼层 |阅读模式
1)实验平台:正点原子阿尔法Linux开发板
2)  章节摘自【正点原子】《《I.MX6U嵌入式Linux驱动开发指南V1.0》》

3)购买链接:https://detail.tmall.com/item.htm?id=609033604451
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子阿尔法Linux交流群:1027879335 QQ群.png

原子哥.jpg

微信公众号.png







第七十三章 Linux PWM驱动实验





        在裸机篇我们已经学习过了如何使用I.MX6ULL的PWM外设来实现LCD的背光调节,其实在Linux的LCD驱动实验我们也提到过I.MX6ULL的PWM背光调节,但是并没有专门的去讲解PWM部分,本章我们就来学习一下Linux下的PWM驱动开发。

73.1 PWM驱动简析
        关于PWM原理以及I.MX6ULL的PWM外设已经在裸机篇进行了详细的讲解,这里就不再赘述了,我们重点来看一下NXP原厂提供的Linux内核自带的PWM驱动。
73.1.1 设备树下的PWM控制器节点
        I.MX6ULL有8路PWM输出,因此对应8个PWM控制器,所有在设备树下就有8个PWM控制器节点。这8路PWM都属于I.MX6ULL的AIPS-1域,但是在设备树imx6ull.dtsi中分为了两部分,PWM1~PWM4在一起,PWM5~PWM8在一起,这8路PWM并没有全部放到一起,这一点一定要注意,不要以为imx6ull.dtsi没有写完整。这8路PWM的设备树节点内容都是一样的,除了reg属性不同(毕竟不同的控制器,其地址范围不同)。本章实验我们使用GPIO1_IO04这个引脚来完成PWM实验,而GPIO1_IO04就是PWM3的输出引脚,所以这里我们就以PWM3为例进行讲解,imx6ull.dtsi文件中的pwm3节点信息如下:
示例代码73.1.1.1 pwm3节点内容
  1. 1  pwm3: pwm@02088000 {
  2. 2           compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
  3. 3           reg = <0x02088000 0x4000>;
  4. 4           interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
  5. 5           clocks = <&clks IMX6UL_CLK_PWM3>,
  6. 6                       <&clks IMX6UL_CLK_PWM3>;
  7. 7           clock-names = "ipg", "per";
  8. 8           #pwm-cells = <2>;
  9. 9  };
复制代码


        第2行,compatible属性值有两个“fsl,imx6ul-pwm”和“fsl,imx27-pwm”,所以在整个Linux源码里面搜索这两个字符窜即可找到I.MX6ULL的PWM驱动文件,这个文件就是drivers/pwm/pwm-imx.c。
        关于I.MX6ULL的PWM节点更为详细的信息请参考对应的绑定文档:Documentation/devicetree/bindings/pwm/ imx-pwm.txt,这里就不去分析了。
73.1.2 PWM子系统
        Linux内核提供了个PWM子系统框架,编写PWM驱动的时候一定要符合这个框架。PWM子系统的核心是pwm_chip结构体,定义在文件include/linux/pwm.h中,定义如下:
示例代码73.1.2.1 pwm_chip结构体
  1. 1  struct pwm_chip {
  2. 2           struct device                       *dev;
  3. 3           struct list_head                    list;
  4. 4           const struct pwm_ops            *ops;
  5. 5           int                                             base;
  6. 6           unsigned int                        npwm;
  7. 7           struct pwm_device                   *pwms;
  8. 8           struct pwm_device                 * (*of_xlate)(struct pwm_chip *pc,
  9. 9                                                       const struct of_phandle_args *args);
  10. 10          unsigned int                        of_pwm_n_cells;
  11. 11          bool                            can_sleep;
  12. 12 };
复制代码


        第4行,pwm_ops结构体就是PWM外设的各种操作函数集合,编写PWM外设驱动的时候需要开发人员实现。pwm_ops结构体也定义在pwm.h头文件中,定义如下:
示例代码73.1.2.2 pwm_ops结构体
  1. 1  struct pwm_ops {
  2. 2           int                (*request)(struct pwm_chip *chip,   //请求PWM
  3. 3                                               struct pwm_device *pwm);
  4. 4           void          (*free)(struct pwm_chip *chip,          //释放PWM
  5. 5                                    struct pwm_device *pwm);
  6. 6           int           (*config)(struct pwm_chip *chip,    //配置PWM周期和占空比
  7. 7                     struct pwm_device *pwm,
  8. 8                     int duty_ns, int period_ns);
  9. 9           int           (*set_polarity)(struct pwm_chip *chip,  //设置PWM极性
  10. 10                    struct pwm_device *pwm,
  11. 11                    enum pwm_polarity polarity);  
  12. 12          int           (*enable)(struct pwm_chip *chip,    //使能PWM
  13. 13                                    struct pwm_device *pwm);
  14. 14          void           (*disable)(struct pwm_chip *chip,   //关闭PWM
  15. 15                     struct pwm_device *pwm);
  16. 16          struct module       *owner;
  17. 17 };
复制代码


        pwm_ops中的这些函数不一定全部实现,但是像config、enable和disable这些肯定是需要实现的,否则的话打开/关闭PWM,设置PWM的占空比这些就没操作了。
        PWM子系统驱动的核心初始化pwm_chip结构体,然后向内核注册初始化完成以后的pwm_chip。这里就要用到pwmchip_add函数,此函数定义在drivers/pwm/core.c文件中,函数原型如下:
  1. int pwmchip_add(struct pwm_chip         *chip)
复制代码


函数参数和返回值含义如下:
        chip:要向内核注册的pwm_chip。
        返回值:0 成功;负数 失败。
        卸载PWM驱动的时候需要将前面注册的pwm_chip从内核移除掉,这里要用到pwmchip_remove函数,函数原型如下:
  1. int pwmchip_remove(struct pwm_chip *chip)
复制代码


函数参数和返回值含义如下:
        chip:要移除的pwm_chip。
        返回值:0 成功;负数 失败。
73.1.3 PWM驱动源码分析
        我们简单分析一下Linux内核自带的I.MX6ULL PWM驱动,驱动文件前面都说了,是pwm-imx.c这个文件。打开这个文件,可以看到,这是一个标准的平台设备驱动文件,如下所示:
示例代码73.1.3.1 I.MX6ULL PWM平台驱动
  1. 1  static const struct of_device_id imx_pwm_dt_ids[] = {
  2. 2           { .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
  3. 3           { .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
  4. 4           { /* sentinel */ }
  5. 5  };
  6. 6  
  7. 7  ......
  8. 8  
  9. 9  static struct platform_driver imx_pwm_driver = {
  10. 10          .driver     = {
  11. 11              .name   = "imx-pwm",
  12. 12              .of_match_table = imx_pwm_dt_ids,
  13. 13          },
  14. 14          .probe      = imx_pwm_probe,
  15. 15                  .remove     = imx_pwm_remove,
  16. 16 };
  17. 17
复制代码


18 module_platform_driver(imx_pwm_driver);
        第3行,当设备树PWM节点的compatible属性值为“fsl,imx27-pwm”的话就会匹配此驱动,注意后面的.data为imx_pwm_data_v2,这是一个imx_pwm_data类型的结构体变量,内容如下:
示例代码73.1.3.2 imx_pwm_data_v2结构体变量
  1. 1 static struct imx_pwm_data imx_pwm_data_v2 = {
  2. 2           .config = imx_pwm_config_v2,
  3. 3           .set_enable = imx_pwm_set_enable_v2,
  4. 4 };
复制代码


        imx_pwm_config_v2函数就是最终操作I.MX6ULL的PWM外设寄存器,进行实际配置的函数。imx_pwm_set_enable_v2就是具体使能PWM的函数。
        第14行,当设备树节点和驱动匹配以后imx_pwm_probe函数就会执行。
        imx_pwm_probe函数如下(有缩减):
示例代码73.1.3.3 imx_pwm_probe函数
  1. 1  static int imx_pwm_probe(struct platform_device *pdev)
  2. 2  {
  3. 3           const struct of_device_id *of_id =
  4. 4                   of_match_device(imx_pwm_dt_ids, &pdev->dev);
  5. 5           const struct imx_pwm_data *data;
  6. 6           struct imx_chip *imx;
  7. 7           struct resource *r;
  8. 8           int ret = 0;
  9. 9  
  10. 10          if (!of_id)
  11. 11                     return -ENODEV;
  12. 12
  13. 13          imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
  14. 14          if (imx == NULL)
  15. 15              return -ENOMEM;
  16. ......
  17. 31          imx->chip.ops = &imx_pwm_ops;
  18. 32          imx->chip.dev = &pdev->dev;
  19. 33          imx->chip.base = -1;
  20. 34          imx->chip.npwm = 1;
  21. 35          imx->chip.can_sleep = true;
  22. 36
  23. 37          r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  24. 38          imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
  25. 39          if (IS_ERR(imx->mmio_base))
  26. 40              return PTR_ERR(imx->mmio_base);
  27. 41
  28. 42          data = of_id->data;
  29. 43          imx->config = data->config;
  30. 44          imx->set_enable = data->set_enable;
  31. 45
  32. 46          ret = pwmchip_add(&imx->chip);
  33. 47          if (ret < 0)
  34. 48              return ret;
  35. 49
  36. 50          platform_set_drvdata(pdev, imx);
  37. 51          return 0;
  38. 52 }
复制代码


        第13行,imx是一个imx_chip类型的结构体指针变量,这里为其申请内存。imx_chip结构体有个重要的成员变量chip,chip是pwm_chip类型的。所以这一行就引出了PWM子系统核心部件pwm_chip,稍后的重点就是初始化chip。
        第31~35行,初始化imx的chip成员变量,也就是初始化pwm_chip!第31行设置pwm_chip的ops操作集为imx_pwm_ops,imx_pwm_ops定义如下:
示例代码73.1.3.4 imx_pwm_ops操作集合
  1. 1 static struct pwm_ops imx_pwm_ops = {
  2. 2           .enable = imx_pwm_enable,
  3. 3           .disable = imx_pwm_disable,
  4. 4           .config = imx_pwm_config,
  5. 5           .owner = THIS_MODULE,
  6. 6 };
复制代码


        imx_pwm_enable、imx_pwm_disable和imx_pwm_config这三个函数就是使能、关闭和配置PWM的函数。
继续回到示例代码73.1.3.3中的37和38行,从设备树中获取PWM节点中关于PWM控制器的地址信息,然后在进行内存映射,这样我们就得到了PWM控制器的基地址。
        第43和44行,这两行设置imx的config和set_enable这两个成员变量为data->config和data->set_enable,也就是示例代码73.1.3.2中的imx_pwm_config_v2和imx_pwm_set_enable_v2这两个函数。imx_pwm_enable、imx_pwm_disable和imx_pwm_config这三个函数最终调用就是imx_pwm_config_v2和imx_pwm_set_enable_v2。
        整个pwm-imx.c文件里面,最终和I.MX6ULL的PWM寄存器打交道的就是imx_pwm_config_v2和imx_pwm_set_enable_v2这两个函数,我们先来看一下imx_pwm_set_enable_v2函数,此函数用于打开或关闭对应的PWM,函数内容如下:
示例代码73.1.3.5 imx_pwm_set_enable_v2函数
  1. 1  static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable)
  2. 2  {
  3. 3           struct imx_chip *imx = to_imx_chip(chip);
  4. 4           u32 val;
  5. 5  
  6. 6           val = readl(imx->mmio_base + MX3_PWMCR);
  7. 7  
  8. 8           if (enable)
  9. 9                       val |= MX3_PWMCR_EN;
  10. 10          else
  11. 11              val &= ~MX3_PWMCR_EN;
  12. 12
  13. 13          writel(val, imx->mmio_base + MX3_PWMCR);
  14. 14 }
复制代码


        第6行,读取PWMCR寄存器的值。
        第9行,如果enable为真,表示使能PWM,将PWMCR寄存器的bit0置1即可,宏MX3_PWMCR_EN为(1<<0)。
        第11行,如果enable不为真,表示关闭PWM,将PWMCR寄存器的bit0清0即可。
        第13行,将新的val值写入到PWMCR寄存器中。
        imx_pwm_config_v2函数用于设置PWM的频率和占空比,相关操作如下:
示例代码73.1.3.6 imx_pwm_config_v2函数
  1. 1  static int imx_pwm_config_v2(struct pwm_chip *chip,
  2. 2       struct pwm_device *pwm, int duty_ns, int period_ns)
  3. 3  {
  4. 4           struct imx_chip *imx = to_imx_chip(chip);
  5. 5           struct device *dev = chip->dev;
  6. 6           unsigned long long c;
  7. 7           unsigned long period_cycles, duty_cycles, prescale;
  8. 8           unsigned int period_ms;
  9. 9                   bool enable = test_bit(PWMF_ENABLED, &pwm->flags);
  10. 10          int wait_count = 0, fifoav;
  11. 11          u32 cr, sr;
  12. 12
  13. ......
  14. 42
  15. 43          c = clk_get_rate(imx->clk_per);
  16. 44          c = c * period_ns;
  17. 45          do_div(c, 1000000000);
  18. 46          period_cycles = c;
  19. 47
  20. 48          prescale = period_cycles / 0x10000 + 1;
  21. 49
  22. 50          period_cycles /= prescale;
  23. 51          c = (unsigned long long)period_cycles * duty_ns;
  24. 52          do_div(c, period_ns);
  25. 53          duty_cycles = c;
  26. 54
  27. 55  /*
  28. 56   * according to imx pwm RM, the real period value should be
  29. 57   * PERIOD value in PWMPR plus 2.
  30. 58   */
  31. 59          if (period_cycles > 2)
  32. 60              period_cycles -= 2;
  33. 61          else
  34. 62              period_cycles = 0;
  35. 63
  36. 64          writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
  37. 65          writel(period_cycles, imx->mmio_base + MX3_PWMPR);
  38. 66
  39. 67          cr = MX3_PWMCR_PRESCALER(prescale) |
  40. 68                      MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
  41. 69              MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH;
  42. 70
  43. 71          if (enable)
  44. 72              cr |= MX3_PWMCR_EN;
  45. 73
  46. 74          writel(cr, imx->mmio_base + MX3_PWMCR);
  47. 75
  48. 76          return 0;
  49. 77 }
复制代码


        第43~62行,根据参数duty_ns和period_ns来计算出应该写入到寄存器里面的值duty_cycles和period_cycles。
        第64行,将计算得到的duty_cycles写入到PWMSAR寄存器中,设置PWM的占空比
        第65行,将计算得到的period_cycles写入到PWMPR寄存器中,设置PWM的频率。
        至此,I.MX6ULL的PWM驱动我们就分析完了。
73.2 PWM驱动编写
73.2.1 修改设备树
        PWM驱动就不需要我们再编写了,NXP已经写好了,前面我们也已经详细的分析过这个驱动源码了。我们在实际使用的时候只需要修改设备树即可,ALPHA开发板上的JP2排针引出了GPIO1_IO04这个引脚,如图73.2.1.1所示:
图片1.png
图73.2.1.1 GPIO1_IO4引脚
        GPIO1_IO04可以作为PWM3的输出引脚,所以我们需要在设备树里面添加GPIO1_IO04的引脚信息以及PWM3控制器对应的节点信息。
        1、添加GPIO1_IO04引脚信息
        打开imx6ull-alientek-emmc.dts文件,在iomuxc节点下添加GPIO1_IO04的引脚信息,如下所示:
示例代码73.2.1.1 GPIO1_IO04引脚信息
  1. 1 pinctrl_pwm3: pwm3grp {
  2. 2           fsl,pins = <
  3. 3               MX6UL_PAD_GPIO1_IO04__PWM3_OUT   0x110b0
  4. 4           >;
  5. 5 };
复制代码


        2、向pwm3节点追加信息
        前面已经讲过了,imx6ull.dtsi文件中已经有了“pwm3”节点,但是还不能直接使用,需要在imx6ull-alientek-emmc.dts文件中向pwm3节点追加一些内容,在imx6ull-alientek-emmc.dts文件中加入如下所示内容:
示例代码73.2.1.2 向pwm3添加的内容
  1. 1 &pwm3 {
  2. 2           pinctrl-names = "default";
  3. 3           pinctrl-0 = <&pinctrl_pwm3>;
  4. 4           clocks = <&clks IMX6UL_CLK_PWM3>,
  5. 5                      <&clks IMX6UL_CLK_PWM3>;
  6. 6           status = "okay";
  7. 7 };
复制代码


        第3行,pinctrl-0属性指定PWM3所使用的输出引脚对应的pinctrl节点,这里设置为示例代码73.2.1中的pinctrl_pwm3。
        第4和5行,设置时钟,第4行设置ipg时钟,第5行设置per时钟。有些pwm节点默认时钟源是IMX6UL_CLK_DUMMY,这里我们需要将其改为对应的时钟,比如这里设置为IMX6UL_CLK_PWM3。PWM1~PWM8分别对应IMX6UL_CLK_PWM1~ IMX6UL_CLK_PWM8。
        3、屏蔽掉其他复用的IO
        检查一下设备树中有没有其他外设用到GPIO1_IO04,如果有的话需要屏蔽掉!注意,不能只屏蔽掉GPIO1_IO04的pinctrl配置信息,也要搜索一下“gpio1 4”,看看有没有哪里用到,用到的话也要屏蔽掉。
设备树修改完成以后重新编译设备树,然后使用新的设备树启动系统。
73.2.2 使能PWM驱动
        NXP官方的Linux内核已经默认使能了PWM驱动,所以不需要我们修改,但是为了学习,我们还是需要知道怎么使能。打开Linux内核配置界面,按照如下路径找到配置项:
  1. -> Device Drivers                                                      
  2.         -> Pulse-Width Modulation (PWM) Support
  3.                 -> <*> i.MX PWM support
复制代码


        配置如图73.2.2.1所示:
图片2.png
图73.2.2.1 PWM配置项
73.3 PWM驱动测试
        使用新的设备树启动系统,然后将开发板JP2排针上的GPIO_4(GPIO1_IO04)引脚连接到示波器上,通过示波器来查看PWM波形图。我们可以直接在用户层来配置PWM,进入目录/sys/class/pwm中,如图73.3.1所示:
图片5.png
图73.3.1各路PWM
        图73.3.1中pwmchip0~pwmchip7对应I.MX6ULL的PWM1~PWM8,所以我们需要用到pwmchip2。
1、调出pwmchip2的pwm0子目录
首先需要调出pwmchip2下的pwm0目录,否则后续就没法操作了,输入如下命令:
echo 0 > /sys/class/pwm/pwmchip2/export
        执行完成会在pwmchip2目录下生成一个名为“pwm0”的子目录,如图73.3.2所示:
图片3.png
图73.3.2 新生成的pwm0子目录
        2、使能PWM3
        输入如下命令使能PWM3:
  1. echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable
复制代码


        3、设置PWM3的频率
        注意,这里设置的是周期值,单位为ns,比如20KHz频率的周期就是50000ns,输入如下命令:
  1. echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period
复制代码


        4、设置PWM3的占空比
        这里不能直接设置占空比,而是设置的一个周期的ON时间,也就是高电平时间,比如20KHz频率下20%占空比的ON时间就是10000,输入如下命令:
echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
        设置完成使用示波器查看波形是否正确,正确的话如图73.3.3所示:
图片4.png
图73.3.3 PWM波形图
        从图73.3.3可以看出,此时PWM频率为20KHz,占空比为20%,与我们设置的一致。如果要修改频率或者占空比的话一定要注意这两者时间值,比如20KHz频率的周期值为50000ns,那么你在调整占空比的时候ON时间就不能设置大于50000,否则就会提示你参数无效。
73.4 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:支持的电压,此属性可以不需要。
        以正点原子ALPHA开发板为例,看一下PWM背光节点是如何设置的,打开imx6ull-alientek-emmc.dts,找到如下所示节点内容:
示例代码73.4.1.1 pwm背光节点
  1. 1 backlight {
  2. 2           compatible = "pwm-backlight";
  3. 3           pwms = <&pwm1 0 5000000>;
  4. 4           brightness-levels = <0 4 8 16 32 64 128 255>;
  5. 5           default-brightness-level = <7>;
  6. 6           status = "okay";
  7. 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做其他外设某个功能的时候要具体问题具体分析。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 15:12

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表