Linux PWM framework(1)_简介和API描述

作者:wowo 发布于:2015-10-11 15:45 分类:通信类协议

1. 前言

PWM是Pulse Width Modulation(脉冲宽度调制)的缩写,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,其本质是一种对模拟信号电平进行数字编码的方法。在嵌入式设备中,PWM多用于控制马达、LED、振动器等模拟器件。

PWM framework是kernel为了方便PWM driver开发、PWM使用而抽象出来的一套通用API,之所以要分析该framework,原因如下:

1)PWM接口,本质上一种通信协议,和I2C、SPI、USB、WIFI等没有任何差别。因此,本文将会是kernel通信协议有关framework的分析文章的第一篇。

2)它太简单了!但是,虽然简单,思路却大同小异,因而非常适合做第一篇。

3)我计划整理显示子系统的分析文章,而PWM,是显示子系统中最基础的那一个。

闲话少说,言归正传!

2. 软件框架及API汇整

PWM framework非常简单,但它同样具备framework的基本特性:对上,为内核其它driver(Consumer)提供使用PWM功能的统一接口;对下,为PWM driver(Provider)提供driver开发的通用方法和API;内部,抽象并实现公共逻辑,屏蔽技术细节。下面我们通过它所提供的API,进一步认识PWM framework。

2.1 向PWM consumer提供的APIs

对consumer而言,关注PWM的如下参数:

1)频率

PWM的频率决定了所模拟出来的模拟电平的平滑度,通俗的讲,就是逼真度。不同的模拟器件,对期待的频率是有要求的,因此需要具体情况具体对待。

另外,人耳能感知的频率范围是20Hz~16KHz,因此要注意PWM的频率不要落在这个范围,否则可能会产生莫名其妙的噪声。

2)占空比

占空比,决定了一个周期内PWM信号高低的比率,进而决定了一个周期内的平均电压,也即所模拟的模拟电平的电平值。

3)极性

简单的说,一个PWM信号的极性,决定了是高占空比的信号输出电平高,还是低占空比信号输出电平高。假设一个信号的占空比为100%,如果为正常极性,则输出电平最大,如果为翻转的极性,则输出电平为0。

4)开关

控制PWM信号是否输出。

基于上述需求,linux pwm framework向consumer提供了如下API:

  1: /* include/linux/pwm.h */
  2: 
  3: /*
  4:  * pwm_config - change a PWM device configuration
  5:  */
  6: int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
  7: 
  8: /*
  9:  * pwm_enable - start a PWM output toggling
 10:  */
 11: int pwm_enable(struct pwm_device *pwm);
 12: 
 13: /*
 14:  * pwm_disable - stop a PWM output toggling
 15:  */
 16: void pwm_disable(struct pwm_device *pwm);
 17: 
 18: /*
 19:  * pwm_set_polarity - configure the polarity of a PWM signal
 20:  */
 21: int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity);

pwm_config,用于控制PWM输出信号的频率和占空比,其中频率是以周期(period_ns)的形式配置的,占空比是以有效时间(duty_ns)的形式配置的。

pwm_enable/pwm_disable,用于控制PWM信号输出与否。

pwm_set_polarity,可以更改pwm信号的极性,可选参数包括normal(PWM_POLARITY_NORMAL)和inversed(极性翻转,PWM_POLARITY_INVERSED)两种。

上面的API都以struct pwm_device类型的指针为操作句柄,该指针抽象了一个PWM设备(consumer不需要关心其内部构成),那么怎么获得PWM句柄呢?使用如下的API:

注1:本文只介绍基于DTS的、新的pwm request系列接口,对于那些旧接口,让它随风而去吧。

  1: /* include/linux/pwm.h */
  2: 
  3: struct pwm_device *pwm_get(struct device *dev, const char *con_id);
  4: struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id);
  5: void pwm_put(struct pwm_device *pwm);
  6: 
  7: struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id);
  8: struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
  9:                                    const char *con_id);
 10: void devm_pwm_put(struct device *dev, struct pwm_device *pwm);

pwm_get/devm_pwm_get,从指定设备(dev)的DTS节点中,获得对应的PWM句柄。可以通过con_id指定一个名称,或者会获取和该设备绑定的第一个PWM句柄。设备的DTS文件需要用这样的格式指定所使用的PWM device(具体的形式,还依赖pwm driver的具体实现,后面会再介绍):
bl: backlight {
        pwms = <&pwm 0 5000000 PWM_POLARITY_INVERTED>;
        pwm-names = "backlight";
};
如果“con_id”为NULL,则返回DTS中“pwms”字段所指定的第一个PWM device;如果“con_id”不为空,如是“backlight”,则返回和“pwm-names ”字段所指定的name对应的PWM device。
上面“pwms”字段各个域的含义如下:
1)&pwm,对DTS中pwm节点的引用;
2)0,pwm device的设备号,具体需要参考SOC以及pwm driver的实际情况;
3)5000000,PWM信号默认的周期,单位是纳秒(ns);
4)PWM_POLARITY_INVERTED,可选字段,是否提供由pwm driver决定,表示pwm信号的极性,若为0,则正常极性,若为PWM_POLARITY_INVERTED,则反转极性。

of_pwm_get/devm_of_pwm_get,和pwm_get/devm_pwm_get类似,区别是可以指定需要从中解析PWM信息的device node,而不是直接指定device指针。

2.2 向PWM provider提供的APIs

接着从PWM provider的角度,看一下PWM framework为provider编写PWM驱动提供了哪些API。

2.2.1 pwm chip

PWM framework使用struct pwm_chip抽象PWM控制器。通常情况下,在一个SOC中,可以同时支持多路PWM输出(如6路),以便同时控制多个PWM设备。这样每一路PWM输出,可以看做一个PWM设备(由上面struct pwm_device抽象),没有意外的话,这些PWM设备的控制方式应该类似。PWM framework会统一管理这些PWM设备,将它们归类为一个PWM chip。

struct pwm_chip的定义如下:

  1: /* include/linux/pwm.h */
  2: 
  3: /**
  4:  * struct pwm_chip - abstract a PWM controller
  5:  * @dev: device providing the PWMs
  6:  * @list: list node for internal use
  7:  * @ops: callbacks for this PWM controller
  8:  * @base: number of first PWM controlled by this chip
  9:  * @npwm: number of PWMs controlled by this chip
 10:  * @pwms: array of PWM devices allocated by the framework
 11:  * @can_sleep: must be true if the .config(), .enable() or .disable()
 12:  *             operations may sleep
 13:  */
 14: struct pwm_chip {
 15:         struct device           *dev;
 16:         struct list_head        list;
 17:         const struct pwm_ops    *ops;
 18:         int                     base;
 19:         unsigned int            npwm;
 20: 
 21:         struct pwm_device       *pwms;
 22: 
 23:         struct pwm_device *     (*of_xlate)(struct pwm_chip *pc,
 24:                                             const struct of_phandle_args *args);
 25:         unsigned int            of_pwm_n_cells;
 26:         bool                    can_sleep;
 27: };

dev,该pwm chip对应的设备,一般由pwm driver对应的platform驱动指定。必须提供!

ops,操作PWM设备的回调函数,后面会详细介绍。必须提供!

npwm,该pwm chip可以支持的pwm channel(也可以称作pwm device由struct pwm_device表示)个数,kernel会根据该number,分配相应个数的struct pwm_device结构,保存在pwms指针中。必须提供!

pwms,保存所有pwm device的数组,kernel会自行分配,不需要driver关心。

base,在将该chip下所有pwm device组成radix tree时使用,只有旧的pwm_request接口会使用,因此忽略它吧,编写pwm driver不需要关心。

of_pwm_n_cells,该PWM chip所提供的DTS node的cell,一般是2或者3,例如:为3时,consumer需要在DTS指定pwm number、pwm period和pwm flag三种信息(如2.1中的介绍);为2时,没有flag信息。

of_xlate,用于解析consumer中指定的、pwm信息的DTS node的回调函数(如2.1中介绍的,pwms = <&pwm 0 5000000 PWM_POLARITY_INVERTED>)。

注2:一般情况下,of_pwm_n_cells取值为3,或者2(不关心极性),of_xlate则可以使用kernel提供的of_pwm_xlate_with_flags(解析of_pwm_n_cells为3的chip)或者of_pwm_simple_xlate(解析of_pwm_n_cells为2的情况)。具体的driver可以根据实际情况修改上述规则,但不到万不得已的时候,不要做这种非标准的、掏力不讨好的事情!(有关of_xlate的流程,会在下一篇流程分析的文章中介绍。)

can_sleep,如果ops回调函数中,.config(),.enable()或者.disable()操作会sleep,则要设置该变量。

2.2.2 pwm ops

struct pwm_ops结构是pwm device有关的操作函数集,如下:

  1: /**
  2:  * struct pwm_ops - PWM controller operations
  3:  * @request: optional hook for requesting a PWM
  4:  * @free: optional hook for freeing a PWM
  5:  * @config: configure duty cycles and period length for this PWM
  6:  * @set_polarity: configure the polarity of this PWM
  7:  * @enable: enable PWM output toggling
  8:  * @disable: disable PWM output toggling
  9:  * @dbg_show: optional routine to show contents in debugfs
 10:  * @owner: helps prevent removal of modules exporting active PWMs
 11:  */
 12: struct pwm_ops {
 13:         int                     (*request)(struct pwm_chip *chip,
 14:                                            struct pwm_device *pwm);
 15:         void                    (*free)(struct pwm_chip *chip,
 16:                                         struct pwm_device *pwm);
 17:         int                     (*config)(struct pwm_chip *chip,
 18:                                           struct pwm_device *pwm,
 19:                                           int duty_ns, int period_ns);
 20:         int                     (*set_polarity)(struct pwm_chip *chip,
 21:                                           struct pwm_device *pwm,
 22:                                           enum pwm_polarity polarity);
 23:         int                     (*enable)(struct pwm_chip *chip,
 24:                                           struct pwm_device *pwm);
 25:         void                    (*disable)(struct pwm_chip *chip,
 26:                                            struct pwm_device *pwm);
 27: #ifdef CONFIG_DEBUG_FS
 28:         void                    (*dbg_show)(struct pwm_chip *chip,
 29:                                             struct seq_file *s);
 30: #endif
 31:         struct module           *owner;
 32: };

这些回调函数的操作对象是具体的pwm device(由struct pwm_device类型的指针表示),包括:

config,配置pwm device的频率、占空比。必须提供!

enable/disable,使能/禁止pwm信号输出。必须提供!

request/free,不再使用。

set_polarity,设置pwm信号的极性。可选,具体需要参考of_pwm_n_cells的定义。

2.2.3 pwm device

struct pwm_device是pwm device的操作句柄,consumer的API调用,会中转到provider的pwm ops回调函数上,provider(及pwm driver)根据pwm device的信息,进行相应的寄存器操作。如下:

  1: struct pwm_device {
  2:         const char              *label;
  3:         unsigned long           flags;
  4:         unsigned int            hwpwm;
  5:         unsigned int            pwm;
  6:         struct pwm_chip         *chip;
  7:         void                    *chip_data;
  8: 
  9:         unsigned int            period;         /* in nanoseconds */
 10:         unsigned int            duty_cycle;     /* in nanoseconds */
 11:         enum pwm_polarity       polarity;
 12: };

pwm driver比较关心的字段是:

hwpwm,该pwm device对应的hardware pwm number,可用于寄存器的寻址操作。

period、duty_cycle、polarity,pwm信号的周期、占空比、极性等信息。

2.2.4 pwmchip_add/pwmchip_remove

初始化完成后的pwm chip可以通过pwmchip_add接口注册到kernel中,之后的事情,pwm driver就不用操心了。该接口的原型如下:

  1: int pwmchip_add(struct pwm_chip *chip);
  2: int pwmchip_remove(struct pwm_chip *chip);

3. API使用指南

3.1 consumer使用PWM的步骤

基于2.1章节描述的API,可以得到pwm consumer(如pwm backlight driver)使用pwm framework的方法和步骤如下:

1)查看pwm provider所提供的pwm dts binding信息(一般会在“Documentation/devicetree/bindings/pwm”目录中),并以此在该device所在的dts node中添加“pwms ”以及“pwm-names ”相关的配置。例如:

/* arch\arm\boot\dts\imx23-evk.dts */
backlight {
        compatible = "pwm-backlight";
        pwms = <&pwm 2 5000000>;
        brightness-levels = <0 4 8 16 32 64 128 255>;
        default-brightness-level = <6>;
};

其中,&pwm,表示对pwm driver的DTS节点的引用,具体可参考下面3.2章节的介绍。

2)在driver的probe接口中,调用devm_pwm_get接口,获取pwm device句柄,并保存起来。

3)devm_pwm_get成功后,该pwm信号已经具备初始的周期和极性。后续根据需要,可以调用pwm_config和pwm_set_polarity更改该pwm信号的周期、占空比和极性。

4)driver可以根据需要,调用pwm_enable/pwm_disable接口,打开或者关闭pwm信号的输出。

3.2 provider编写PWM driver的步骤

基于2.2章节描述的API,可以得到pwm provider(即具体的PWM驱动)使用pwm framework的方法和步骤如下:

1)创建代表该pwm driver的DTS节点,并提供platform device有关的资源信息,例如:

/* arch\arm\boot\dts\imx23.dtsi */
pwm: pwm@80064000 {
        compatible = "fsl,imx23-pwm";
        reg = <0x80064000 0x2000>;
        clocks = <&clks 30>;
        #pwm-cells = <2>;
        fsl,pwm-number = <5>;
        status = "disabled";
};

/* arch\arm\boot\dts\imx23-evk.dts */
pwm: pwm@80064000 {
        pinctrl-names = "default";
        pinctrl-0 = <&pwm2_pins_a>;
        status = "okay";
};

2)定义一个pwm chip变量

3)注册相应的platform driver,并在driver的.probe()接口中,初始化pwm chip变量,至少要包括如下字段:

dev,使用platform device中的dev指针即可;npwm;ops,至少包括config、enable、disable三个回调函数。

如果该pwm chip支持额外的flag(如PWM极性,或者自定义的flag),将PWM cell指定为3(of_pwm_n_cells),of_xlate指定为of_pwm_xlate_with_flags。

初始化完成调用pwmchip_add接口,将chip添加到kernel中。

4)每当consumer有API调用时,kernel会以pwm device为参数,调用pwm driver提供的pwm ops,相应的回调函数可以从pwm device中取出pwm number(该number的意义driver自行解释),并操作对应的寄存器即可。

 

原创文章,转发请注明出处。蜗窝科技www.wowotech.net

标签: Linux driver pwm

评论:

小蚂蚁
2019-04-02 16:55
老板,你好,请问pwm_add之后,用户空间如果想配置PWM,怎么才能调用到相应的pwm_config函数呢?按正常的字符设备应该是open,然后ioctl,那么现在应该怎么调呢?
alisem
2019-03-29 16:45
pwm控制几个脉冲波形,如何传值进来
leon
2017-12-20 16:04
我用的是rk3288的板子,用PWM3 输入接入sharp的红外传感器,如何设置PWM是输入的呢?
houlic
2017-12-08 09:51
costumer和provider的区别是什么啊???
wowo
2017-12-08 10:07
@houlic:你可以试试这个链接:http://fanyi.baidu.com,查一下这两个单词的区别。
houlic
2017-12-08 10:25
@wowo:provider是驱动的提供者,它在内核空间,提供一些config、enable之类的功能。costumer是驱动的使用者,它在用户空间,可以调用config、enable这些功能来设置pwm。这样理解对么?
wowo
2017-12-08 14:07
@houlic:基本上是这个样子。不过呢,consumer不一定是在用户空间,也可能是另一个driver。
houlic
2017-12-08 14:36
@wowo:好的,谢谢!!!
linux_rock
2017-02-24 18:52
楼主,你好。我在dts中加了一个pwm控制风扇
fan: fan {
                compatible = "pwm-fan";
        pwms = <&pwm1 0 25000>;
        //pwm-names = "pwmfan";    
    };
但驱动中用 devm_pwm_get 每次都获取不到pwm结构体指针,但背光那个却可以
        pb->pwm = devm_pwm_get(pb->dev, NULL);
    if (IS_ERR(pb->pwm)) {
        dev_err(pb->dev, "unable to request PWM, trying legacy API\n");
        ret = PTR_ERR(pb->pwm);
        goto err_gpio;
    }
wowo
2017-02-24 22:36
@linux_rock:跟一下代码吧,总能找到原因的:-)
MODULE_LICENSE
2017-06-05 22:52
@linux_rock:请问您找到问题的原因了么,我虽然没有碰到,但很好奇。
linux_rock
2017-06-06 10:12
@MODULE_LICENSE:&pwm1 {
        status = "okay";
};
应该把pwm1打开的,一开始没有打开,所以获取不到pwm结构体指针
MODULE_LICENSE
2017-06-06 16:00
@linux_rock:非常感谢您的共享!
myway
2017-02-08 08:52
博主你好,我想问一下,PWM驱动是从哪里获取到GPIO信息的?
wowo
2017-02-08 09:59
@myway:如果是标准的实现的话,当然是从pinctrl subsystem了,例如:
/* arch\arm\boot\dts\imx23-evk.dts */
pwm: pwm@80064000 {
        pinctrl-names = "default";
        pinctrl-0 = <&pwm2_pins_a>;
        status = "okay";
};
只要把pinctrl配好,哪个pwm从哪个gpio出,就固定了。
ll
2019-07-12 14:54
@wowo:那请问如果使用电源芯片上的mpp去输出pwm控制,该怎么配置dts呢,驱动中又是怎么获取的呢?
linux_emb
2015-12-24 17:30
麻雀虽小,五脏俱全。
ushineme
2015-10-31 11:15
啊呀呀呀,要整理显示子系统了吗,我就是刚毕业在一个手机公司学习lcd的驱动,好期待!!
wowo
2015-10-31 12:13
@ushineme:是在准备,不过还没有开始呢,最近事情比较多,就耽搁了。
ushineme
2015-10-31 16:18
@wowo:嗯嗯,知道啦,我会慢慢等的

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog