- 了解PWM基础知识。
1.Introduction
Pulse Wide Modulation (PWM) operates like a switch that constantly cycles on and off. It is a hardware feature used to control servomotors, for voltage regulation, and so on. The most wellknown applications of PWM are:
- Motor speed control
- Light dimming
- Voltage regulation
Now, let us introduce PWM with a simple following figure:
The preceding figure describes a complete PWM cycle, introducing some terms we need to clarify prior to getting deeper into the kernel PWM framework:
- Ton: This is the duration during which the signal is high.
- Toff: This is the duration during which the signal is low.
- Period: This is the duration of a complete PWM cycle. It represents the sum of Ton and - Toff of the PWM signal.
- Duty cycle: It is represented as a percentage of the time signal that remains on during the period of the PWM signal.
2.The Linux PWM framework has two interfaces:
- Controller interface: The one that exposes the PWM line. It is the PWM chip, that is, the producer.
- Consumer interface: The device consuming PWM lines exposed by the controller.
Drivers of such devices use helper functions exported by the controller by means of a generic PWM framework.
2.1.PWM controller driver
PWM framework使用struct pwm_chip抽象PWM控制器。通常情况下,在一个SOC中,可以同时支持多路PWM输出(如6路),以便同时控制多个PWM设备。这样每一路PWM输出,可以看做一个PWM设备(由上面struct pwm_device抽象)。PWM framework会统一管理这些PWM设备,将它们归类为一个PWM chip。
2.1.1.struct pwm_chip
1: /* include/linux/pwm.h */
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: This represents the device associated with this chip.
- ops: This is a data structure providing callback functions this chip exposes to consumer drivers.
- base: This is the number of the first PWM controlled by this chip. If chip->base < 0 then, the kernel will dynamically assign a base.
- can_sleep: This should be set to true by the chip driver if .config(), .enable(), or .disable() operations of the ops field may sleep.
- npwm: This is the number of PWM channels (devices) this chip provide.
- pwms: This is an array of PWM devices of this chip, allocated by the framework, to consumer drivers.
- of_pwm_n_cells: This is the number of cells expected in the DT for a PWM specifier.
- of_xlate: This is an optional callback to request a PWM device given a DT PWM specifier. If not defined, it will be set to of_pwm_simple_xlate by the PWM core, which will force of_pwm_n_cells to 2 as well.(of_pwmchip_add function)
Note:
一般情况下,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的情况)。
2.1.2. pwm ops
struct pwm_ops结构是pwm device有关的操作函数集,如下:
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: };
- request: This is an optional hook that, if provided, is executed during a PWM channel request.
- free: This is the same as request, ran during PWM freeing.
- config: This is the PMW configuration hook. It configures duty cycles and period length for this PWM.
- set_polarity: This hook configures the polarity of this PWM.
- enable: This enables the PWM line, starting output toggling.
- disable: This disables the PWM line, stopping output toggling.
- apply: This atomically applies a new PWM config. The state argument should be adjusted with the real hardware config.
- get_state: This returns the current PWM state. This function is only called once per PWM device when the PWM chip is registered.
- owner: This is the module that owns this chip, usually THIS_MODULE
In the probe function of the PWM controller driver, it is good practice to retrieve DT resources, initialize hardware, fill a struct pwm_chip and its struct pwm_ops, and then, add the PWM chip with the pwmchip_add function.
2.1.3. pwmchip_add/pwmchip_remove
初始化完成后的pwm chip可以通过pwmchip_add接口注册到kernel中,之后的事情,pwm driver就不用操心了。该接口的原型如下:
int pwmchip_add(struct pwm_chip *chip);
int pwmchip_remove(struct pwm_chip *chip);
3.PWM consumer interface
A consumer is the device that actually uses PWM channels. A PWM channel is represented in the kernel as an instance of struct pwm_device structure:
3.1.pwm device
struct pwm_device是pwm device的操作句柄,consumer的API调用,会中转到provider的pwm ops回调函数上,provider(及pwm driver)根据pwm device的信息,进行相应的寄存器操作。如下:
struct pwm_device {
const char *label;
unsigned long flags;
unsigned int hwpwm;
unsigned int pwm;
struct pwm_chip *chip;
void *chip_data;
struct pwm_args args;
struct pwm_state state;
}
struct pwm_args {
unsigned int period; /* Device's nitial period */
enum pwm_polarity polarity;
};
struct pwm_state {
unsigned int period; /* PWM period (in nanoseconds) */
unsigned int duty_cycle; /* PWM duty cycle (in nanoseconds) */
enum pwm_polarity polarity; /* PWM polarity */
bool enabled; /* PWM enabled status */
}
3.2.向PWM consumer提供APIs
对consumer而言,关注PWM的如下参数:
-
频率
PWM的频率决定了所模拟出来的模拟电平的平滑度,通俗的讲,就是逼真度。不同的模拟器件,对期待的频率是有要求的,因此需要具体情况具体对待。另外,人耳能感知的频率范围是20Hz~16KHz,因此要注意PWM的频率不要落在这个范围,否则可能会产生莫名其妙的噪声。
-
占空比
占空比,决定了一个周期内PWM信号高低的比率,进而决定了一个周期内的平均电压,也即所模拟的模拟电平的电平值。 -
极性
一个PWM信号的极性,决定了是高占空比的信号输出电平高,还是低占空比信号输出电平高。假设一个信号的占空比为100%,如果为正常极性,则输出电平最大,如果为翻转的极性,则输出电平为0。 -
开关
控制PWM信号是否输出。
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
int pwm_enable(struct pwm_device *pwm);
void pwm_disable(struct pwm_device *pwm);
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)两种。
Note: 上面的API都以struct pwm_device类型的指针为操作句柄,该指针抽象了一个PWM设备,那么怎么获得PWM句柄呢?使用如下的API:
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);
Legacy users can request a PWM device using pwm_request() and free it
after usage with pwm_free().
New users should use the pwm_get() function and pass to it the consumer
device or a consumer name. pwm_put() is used to free the PWM device. Managed
variants of these functions, devm_pwm_get() and devm_pwm_put(), also exist.
其中pwm_get/devm_pwm_get,从指定设备(dev)的DTS节点中,获得对应的PWM句柄。可以通过con_id指定一个名称,或者会获取和该设备绑定的第一个PWM句柄。设备的DTS文件需要用这样的格式指定所使用的PWM device(具体的形式,还依赖pwm driver的具体实现,后面会再介绍):
bl: backlight {
pwms = ;
pwm-names = "backlight";
};
如果“con_id”为NULL,则返回DTS中“pwms”字段所指定的第一个PWM device;如果“con_id”不为空,如是“backlight”,则返回和“pwm-names ”字段所指定的name对应的PWM device。
上面“pwms”字段各个域的含义如下:
- &pwm,对DTS中pwm节点的引用;
- 0,pwm device的设备号,具体需要参考SOC以及pwm driver的实际情况;
- 5000000,PWM信号默认的周期,单位是纳秒(ns);
- 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指针。
AAfter being requested, a PWM has to be configured using::
int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state);
This API controls both the PWM period/duty_cycle config and the enable/disable state.
The pwm_config(), pwm_enable() and pwm_disable() functions are just wrappers around pwm_apply_state() and should not be used if the user wants to change several parameter at once. For example, if you see pwm_config() and pwm_{enable,disable}() calls in the same function, this probably means you should switch to pwm_apply_state().
4.API使用指南
4.1 consumer使用PWM的步骤
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 = ;
brightness-levels = ;
default-brightness-level = ;
};
其中,&pwm,表示对pwm driver的DTS节点的引用。
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信号的输出。
4.2 provider编写PWM driver的步骤
1>.创建代表该pwm driver的DTS节点,并提供platform device有关的资源信息,例如:
/* arch\arm\boot\dts\imx23.dtsi */
pwm: pwm@80064000 {
compatible = "fsl,imx23-pwm";
reg = ;
clocks = ;
#pwm-cells = ;
fsl,pwm-number = ;
status = "disabled";
};
/* arch\arm\boot\dts\imx23-evk.dts */
pwm: pwm@80064000 {
pinctrl-names = "default";
pinctrl-0 = ;
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自行解释),并操作对应的寄存器即可。
5.Using PWMs with the sysfs interface
The PWM core sysfs root path is /sys/class/pwm/. It is the user space way to manage PWM device. Each PWM controller/chip added to the system creates a pwmchipN directory entry under the sysfs root path, where N is the base of the PWM chip. The directory contains the following files:
- npwm: This is a reads only file printing the number of PWM channels that this chip supports
- Export: This is a write-only file allowing to export a PWM channel for use with sysfs (this
functionality is equivalent to GPIO sysfs interface) - Unexport: Unexports a PWM channel from sysfs (write-only)
The PWM channels are numbered using an index from 0 to pwm. These numbers are local to the chip. Each PWM channel exportation creates a pwmX directory in the pwmchipN, which is the same directory as the one containing the export file used. X is the number of the channel that was exported. Each channel directory contains the following files:
- Period: This is a readable/writable file to get/set the total period of the PWM signal. Value is in nanoseconds.
- duty_cycle: This is a readable/writable file to get/set the duty cycle of the PWM signal. It represents the active time of the PWM signal. Value is in nanoseconds and must always be less than the period.
- Polarity: This is a readable/writable file to use only if the chip of this PWM device
supports polarity inversion. It is better to change the polarity only when this PWM is not enabled. Accepted values are string normal or inversed. - Enable: This is a readable/writable file, to enable (start toggling)/disable (stop toggling) the PWM signal. Accepted values are:
- 0: disabled
- 1: enabled
Documentation/ABI/testing/sysfs-class-pwm:
-
/sys/class/pwm/
The pwm/ class sub-directory belongs to the Generic PWM Framework and provides a sysfs interface for using PWM channels -
/sys/class/pwm/pwmchipN/
A /sys/class/pwm/pwmchipN directory is created for each probed PWM controller/chip where N is the base of the PWM chip. -
/sys/class/pwm/pwmchipN/npwm
The number of PWM channels supported by the PWM chip. -
/sys/class/pwm/pwmchipN/export
Exports a PWM channel from the PWM chip for sysfs control.Value is between 0 and /sys/class/pwm/pwmchipN/npwm - 1. -
/sys/class/pwm/pwmchipN/unexport
Unexports a PWM channel. -
/sys/class/pwm/pwmchipN/pwmX
A /sys/class/pwm/pwmchipN/pwmX directory is created for each exported PWM channel where X is the exported PWM channel number. -
/sys/class/pwm/pwmchipN/pwmX/period
Sets the PWM signal period in nanoseconds. -
/sys/class/pwm/pwmchipN/pwmX/duty_cycle
Sets the PWM signal duty cycle in nanoseconds. -
/sys/class/pwm/pwmchipN/pwmX/enable
Enable/disable the PWM signal.
Export PWM channel for user control:
~# echo 0 > /sys/class/pwm/pwmchip0/export
~# echo 1 > /sys/class/pwm/pwmchip0/export
~# echo 2 > /sys/class/pwm/pwmchip0/export
~# echo 3 > /sys/class/pwm/pwmchip0/export
For any exported channel a directory called pwmX wil be created with the following structure:
/sys/class/pwm/pwmchip0/pwmX/
|-- duty_cycle (r/w) duty cycle (in nanoseconds)
|-- enable (r/w) enable/disable PWM
|-- period (r/w) period (in nanoseconds)
|-- polarity (r/w) polarity of PWM
|-- power
`-- uevent
The follow example illustrate how enable a PWM signale with a period of 1mS with a 0.5mS of duty cycle:
~# echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
~# echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
~# echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
Test:pwm2 输出占空比为50%
insmod test/pwm-xxx.ko
cd /sys/class/pwm/pwmchip16/
echo 2 > export //执行之后,目录出现pwm2 (如果调试pwm3,则echo 3 > export)
cd pwm2
echo 0x3e8 > period
echo 0x1f5 >duty_cycle
echo 1 > enable (必须先设置 period和duty_cycle)
如果想卸载pwm-xxx.ko,首先echo 2 > unexport,然后
rmmod test/pwm-xxx.ko
- /sys/class/pwm/,pwmchip0对应pwm0,pwmchip8 对应pwm1,pwmchip16 对应pwm2,以此类推。
refer to
- https://en.wikipedia.org/wiki/Pulsewidth_modulation
- Documentation/pwm.txt
- drivers/pwm/core.c
- drivers/pwm/sysfs.c
- drivers/pwm/pwm-xxx.c