Linux内核4.14版本——PWM子系统(1)_框架分析

目录

1. 前言

2. 软件框架及API汇整

2.1 向PWM consumer提供的APIs

2.2 向PWM provider提供的APIs

2.2.1 pwm chip

2.2.2 pwm ops

2.2.3 pwm device

2.2.4 pwmchip_add/pwmchip_remove

3. 分析几个函数

3.1 pwmchip_add

3.2 pwm_request

4. sysfs-class-pwm的使用


1. 前言

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

      PWM framework是kernel为了方便PWM driver开发、PWM使用而抽象出来的一套通用API。

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:

/* include/linux/pwm.h */
/**
 * pwm_config() - change a PWM device configuration
 * @pwm: PWM device
 * @duty_ns: "on" time (in nanoseconds)
 * @period_ns: duration (in nanoseconds) of one cycle
 *
 * Returns: 0 on success or a negative error code on failure.
 */
static inline int pwm_config(struct pwm_device *pwm, int duty_ns,
			     int period_ns);

/**
 * pwm_enable() - start a PWM output toggling
 * @pwm: PWM device
 *
 * Returns: 0 on success or a negative error code on failure.
 */
static inline int pwm_enable(struct pwm_device *pwm);

/**
 * pwm_disable() - stop a PWM output toggling
 * @pwm: PWM device
 */
static inline void pwm_disable(struct pwm_device *pwm);

/**
 * pwm_set_polarity() - configure the polarity of a PWM signal
 * @pwm: PWM device
 * @polarity: new polarity of the PWM signal
 *
 * Note that the polarity cannot be configured while the PWM device is
 * enabled.
 *
 * Returns: 0 on success or a negative error code on failure.
 */
static inline 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:

/* include/linux/pwm.h */

struct pwm_device *pwm_get(struct device *dev, const char *con_id);
struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id);
void pwm_put(struct pwm_device *pwm);

struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id);
struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
                                   const char *con_id);
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的定义如下:

/**
 * struct pwm_chip - abstract a PWM controller
 * @dev: device providing the PWMs
 * @list: list node for internal use
 * @ops: callbacks for this PWM controller
 * @base: number of first PWM controlled by this chip
 * @npwm: number of PWMs controlled by this chip
 * @pwms: array of PWM devices allocated by the framework
 * @of_xlate: request a PWM device given a device tree PWM specifier
 * @of_pwm_n_cells: number of cells expected in the device tree PWM specifier
 */
struct pwm_chip {
	struct device *dev;
	struct list_head list;
	const struct pwm_ops *ops;
	int base;
	unsigned int npwm;

	struct pwm_device *pwms;

	struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
					const struct of_phandle_args *args);
	unsigned int of_pwm_n_cells;
};

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的流程,会在下一篇流程分析的文章中介绍。)

2.2.2 pwm ops

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

/**
 * struct pwm_ops - PWM controller operations
 * @request: optional hook for requesting a PWM
 * @free: optional hook for freeing a PWM
 * @config: configure duty cycles and period length for this PWM
 * @set_polarity: configure the polarity of this PWM
 * @capture: capture and report PWM signal
 * @enable: enable PWM output toggling
 * @disable: disable PWM output toggling
 * @apply: atomically apply a new PWM config. The state argument
 *	   should be adjusted with the real hardware config (if the
 *	   approximate the period or duty_cycle value, state should
 *	   reflect it)
 * @get_state: get the current PWM state. This function is only
 *	       called once per PWM device when the PWM chip is
 *	       registered.
 * @dbg_show: optional routine to show contents in debugfs
 * @owner: helps prevent removal of modules exporting active PWMs
 */
struct pwm_ops {
	int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
	void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
	int (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
		      int duty_ns, int period_ns);
	int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm,
			    enum pwm_polarity polarity);
	int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm,
		       struct pwm_capture *result, unsigned long timeout);
	int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm);
	void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);
	int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,
		     struct pwm_state *state);
	void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
			  struct pwm_state *state);
#ifdef CONFIG_DEBUG_FS
	void (*dbg_show)(struct pwm_chip *chip, struct seq_file *s);
#endif
	struct module *owner;
};

这些回调函数的操作对象是具体的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的信息,进行相应的寄存器操作。如下:

/**
 * struct pwm_device - PWM channel object
 * @label: name of the PWM device
 * @flags: flags associated with the PWM device
 * @hwpwm: per-chip relative index of the PWM device
 * @pwm: global index of the PWM device
 * @chip: PWM chip providing this PWM device
 * @chip_data: chip-private data associated with the PWM device
 * @args: PWM arguments
 * @state: curent PWM channel state
 */
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;
};

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就不用操心了。该接口的原型如下:

int pwmchip_add(struct pwm_chip *chip);
int pwmchip_remove(struct pwm_chip *chip);

3. 分析几个函数

3.1 pwmchip_add

/**
 * pwmchip_add() - register a new PWM chip
 * @chip: the PWM chip to add
 *
 * Register a new PWM chip. If chip->base < 0 then a dynamically assigned base
 * will be used. The initial polarity for all channels is normal.
 *
 * Returns: 0 on success or a negative error code on failure.
 */
int pwmchip_add(struct pwm_chip *chip)
{
	return pwmchip_add_with_polarity(chip, PWM_POLARITY_NORMAL);
}

最终调用pwmchip_add_with_polarity。

/**
 * pwmchip_add_with_polarity() - register a new PWM chip
 * @chip: the PWM chip to add
 * @polarity: initial polarity of PWM channels
 *
 * Register a new PWM chip. If chip->base < 0 then a dynamically assigned base
 * will be used. The initial polarity for all channels is specified by the
 * @polarity parameter.
 *
 * Returns: 0 on success or a negative error code on failure.
 */
int pwmchip_add_with_polarity(struct pwm_chip *chip,
			      enum pwm_polarity polarity)
{
	struct pwm_device *pwm;
	unsigned int i;
	int ret;

	if (!chip || !chip->dev || !chip->ops || !chip->npwm)
		return -EINVAL;

	if (!pwm_ops_check(chip->ops))
		return -EINVAL;

	mutex_lock(&pwm_lock);

	ret = alloc_pwms(chip->base, chip->npwm);
	if (ret < 0)
		goto out;

	chip->pwms = kcalloc(chip->npwm, sizeof(*pwm), GFP_KERNEL);
	if (!chip->pwms) {
		ret = -ENOMEM;
		goto out;
	}

	chip->base = ret;

	for (i = 0; i < chip->npwm; i++) {
		pwm = &chip->pwms[i];

		pwm->chip = chip;
		pwm->pwm = chip->base + i;
		pwm->hwpwm = i;
		pwm->state.polarity = polarity;

		if (chip->ops->get_state)
			chip->ops->get_state(chip, pwm, &pwm->state);

		radix_tree_insert(&pwm_tree, pwm->pwm, pwm);
	}

	bitmap_set(allocated_pwms, chip->base, chip->npwm);

	INIT_LIST_HEAD(&chip->list);
	list_add(&chip->list, &pwm_chips);

	ret = 0;

	if (IS_ENABLED(CONFIG_OF))
		of_pwmchip_add(chip);

out:
	mutex_unlock(&pwm_lock);

	if (!ret)
		pwmchip_sysfs_export(chip);

	return ret;
}

      这个也比较简单,根据这个pwm控制器的channel个数,创建相应的struct pwm_device,最终加到pwm_tree链表树中。这样所有的可以使用的pwm channel都被抽象成了struct pwm_device结构体,也是我们最终的操作结构体。 

static RADIX_TREE(pwm_tree, GFP_KERNEL);

     pwm_tree是静态的,在本文件中搜索,找到pwm_to_device函数,这个函数是被pwm_request调用。。

static struct pwm_device *pwm_to_device(unsigned int pwm)
{
	return radix_tree_lookup(&pwm_tree, pwm);
}

3.2 pwm_request

/**
 * pwm_request() - request a PWM device
 * @pwm: global PWM device index
 * @label: PWM device label
 *
 * This function is deprecated, use pwm_get() instead.
 *
 * Returns: A pointer to a PWM device or an ERR_PTR()-encoded error code on
 * failure.
 */
struct pwm_device *pwm_request(int pwm, const char *label)
{
	struct pwm_device *dev;
	int err;

	if (pwm < 0 || pwm >= MAX_PWMS)
		return ERR_PTR(-EINVAL);

	mutex_lock(&pwm_lock);

	dev = pwm_to_device(pwm);
	if (!dev) {
		dev = ERR_PTR(-EPROBE_DEFER);
		goto out;
	}

	err = pwm_device_request(dev, label);
	if (err < 0)
		dev = ERR_PTR(err);

out:
	mutex_unlock(&pwm_lock);

	return dev;
}

    pwm_to_device函数调用以后,实际上已经找到我们需要的PWM channel了。pwm_device_request也不需要了。

static int pwm_device_request(struct pwm_device *pwm, const char *label)
{
	int err;

	if (test_bit(PWMF_REQUESTED, &pwm->flags))
		return -EBUSY;

	if (!try_module_get(pwm->chip->ops->owner))
		return -ENODEV;

	if (pwm->chip->ops->request) {
		err = pwm->chip->ops->request(pwm->chip, pwm);
		if (err) {
			module_put(pwm->chip->ops->owner);
			return err;
		}
	}

	set_bit(PWMF_REQUESTED, &pwm->flags);
	pwm->label = label;

	return 0;
}

     这个也就是我们前面说的ops中,request被遗弃的原因。

4. 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,以此类推。

你可能感兴趣的:(嵌入式一些知识,linux,PWM子系统)