Linux PWM 驱动的实现

0. PWM 概念

PWM 指的是脉冲宽度调制技术。 PWM 频率,指 1 秒的时间里 PWM 运行的次数。 PWM 周期,指一次完整的 PWM 输出所使用的时间。 占空比,指一个周期里输出高的时间所占的比例。

Linux 里已经有了完整的 PWM 驱动接口、用户操作接口。对于一个 PWM 控制器,它的驱动需要在 Linux 定义好的接口下,实现操作。

1. 定义驱动入口

在驱动入口处,定义了驱动能匹配的名字,然后注册驱动,把名字和 probe() 函数关联起来。

/* 定义了驱动能匹配的名字 */
static const struct of_device_id pwm_xxx_of_match[] = {
        { .compatible = "xxx,pwm0" },
        {},
};
MODULE_DEVICE_TABLE(of, pwm_xxx_of_match);

/* 注册驱动,关联名字和 probe() 函数 */
static struct platform_driver pwm_xxx_driver = {
        .probe = pwm_xxx_probe,
        .remove = pwm_xxx_remove,
        .driver = {
                .name = "pwm-xxx",
                .of_match_table = pwm_xxx_of_match,
        },
};
module_platform_driver(pwm_xxx_driver);

2. probe() 函数

  1. 在 dts 里定义 PWM 驱动时,会配置 compatible, reg, clocks 等等参数,这些参数需要由驱动来解析后使用到实现代码里;
  2. 安装 pwm_ops 回调函数,这些函数是用来驱动控制器;
  3. 向系统注册控制器驱动;
static int pwm_xxx_probe(struct platform_device *pdev)
{
    ...
    chip->ops = &pwm_xxx_ops;
    ret = pwmchip_add(chip);
    return 0;
}

3. pwm_ops 接口

pwm_ops 定义了 PWM 控制器的操作,定义在文件 include/linux/pwm.h 里:

/**
 * struct pwm_ops - PWM controller operations
 * @request: optional hook for requesting a PWM
 * @free: optional hook for freeing a PWM
 * @capture: capture and report PWM signal
 * @apply: atomically apply a new PWM config
 * @get_state: get the current PWM state. This function is only
 *             called once per PWM device when the PWM chip is
 *             registered.
 */
struct pwm_ops {
        int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
        void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
        int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm,
                       struct pwm_capture *result, unsigned long timeout);
        int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,
                     const struct pwm_state *state);
        void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
                          struct pwm_state *state);
        ...
};

其中,最需要关心的是实现 apply() 函数,和 pwm_state 结构。

/*
 * struct pwm_state - state of a PWM channel
 * @period: PWM period (in nanoseconds)
 * @duty_cycle: PWM duty cycle (in nanoseconds)
 * @polarity: PWM polarity
 * @enabled: PWM enabled status
 * @usage_power: If set, the PWM driver is only required to maintain the power
 *               output but has more freedom regarding signal form.
 *               If supported, the signal can be optimized, for example to
 *               improve EMI by phase shifting individual channels.
 */
struct pwm_state {
        u64 period;
        u64 duty_cycle;
        enum pwm_polarity polarity;
        bool enabled;
        bool usage_power;
};

static int pwm_xxx_apply(struct pwm_chip *chip, struct pwm_device *pwm,
                         const struct pwm_state *state)
{
        ...
}

apply() 函数,就是把 pwm_state 里的内容,设置到控制器里去。大概是这样的实现过程:

  • 解析 pwm_state 里的参数:
    • 判断 pwm_state->enabled,如果 False,关闭 PWM 控制器;
    • 否则,开启 PWM:
      • 判断 pwm_state->period 和 pwm_state->duty_cycle,这两个单位都是纳秒,再结合初始化时的 clock 输入,可以计算控制器寄存器的值;
      • 判断 pwm_state->polarity,如果是 NORMAL,duty-cycle 是高有效,如果是 INVERSED,duty-cycle 是低有效。控制器寄存器的值要相应调整;
  • 把计算好的值写入控制器寄存器,实现 PWM 状态的更新。

4. devicetree 的配置

在 devicetree 里通过名字来匹配 pwm 驱动(各个字段按实际情况填写):

pwm0: pwm@12345678 {
    compatible = "xxx,pwm0";
    reg = <0x12345678 0x12>;
    clocks = <...>;
    status = "okay";
};

5. sysfs 用户操作接口

用户操作接口在 /sys/class/pwm/ 目录下,有 export、unexport 等文件,可以把 PWM 导出或者取消。

已经 export 过的,可以看到有以下一些文件:

  • period: 周期,纳秒,可读可写;
  • duty_cycle: 也是纳秒,可读可写;
  • polarity: 用来设置极性,可以写入 normal 或者 inversed;
  • enable: 使能控制,可读可写,0 或者 1。

比如:

echo 45600000 > period
echo 12300000 > duty_cycle
echo 1 > enable

参考资料

  • linux/drivers/pwm/*.c
  • Documentation/driver-api/pwm.rst

Read More: