Bootstrap

linux PWM驱动程序

        脉宽控制(PWM)操作像不断循环开关,这一功能常用于控制伺服电机、灯光控制、电压调节等。

  • Ton:信号高电平的持续时间;
  • Toff:信号低电平的持续时间;
  • Period:完整的PWM周期的持续时间,它是Ton+Toff
  • Duty cycle:Ton在Period的占空比;它的值为(Ton/Period)*100

        linux的PWM框架有两个接口:

  • 控制器接口:提供PWM线路。它是PWM芯片。
  • 消费者接口:消费有PWM控制器提供的PWM线的设备。此类设备的驱动程序使用控制器通过PWM框架提供的辅助函数。

一、PWM控制器驱动程序

1. PWM控制器实例 

PWM控制器在内核中表示为struct pwm_chip{}实例。

struct pwm_chip {
    struct device *dev;
    const struct pwm_ops *ops; // 公开给消费者程序的回调函数
    int base; // 该芯片控制的第一个pwm的编号
    uint npwm; // 该芯片控制的第一个pwm通道的数量

    struct pwm_device *pwms; // 该芯片的pwm设备数组
    struct pwm_device *(of_xlate)(struct pwm_chip *pc,
                        const struct of_phandle_args *args); 
    // 默认值为of_pwm_simple_xlate
    uint of_pwm_n_cells;  // PWM说明符的cell数
    bool can_sleep;
};

// 控制器芯片的添加/删除
int pwmchip_add(struct pwm_chip *chip);
int pwmchip_remove(struct pwm_chip *chip);

// 每个pwm芯片会提供一些callback,这些callback供pwm内核或用户使用
struct pwm_ops {
    int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
    int (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
    int (*config)(struct pwm_chip *chip, struct pwm_device *pwm
                    int duty_ns, int period); // 配置函数
    int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm,
                        enum pwm_polarity polarity);
    int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm);
    int (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);
    int (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm
                    struct pwm_state *state); // 返回pwm当前的状态
    struct module *owner;
};

2. PWM芯片驱动程序实例

        下面是一个具有3个通道的PWM控制器的驱动程序:

struct fake_chip {
    struct pwm_chip chip;
    int foo;
    int bar;
    /*将客户端结构放在这里,例如spi_device, i2c_client等*/
};

#define to_fake_chip(pwm_chip) container_of(pwm_chip, struct fake_chip, chip)

static int fake_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) {
    return 0;
}
static int fake_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
            int duty_ns, int period_ns) {
    return 0;
}
static int fake_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) {
    return 0;
}
static void fake_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) {}

static const struct pwm_ops fake_pwm_ops = {
    .request = fake_pwm_request,
    .config = fake_pwm_config,
    .enable = fake_pwm_enable,
    .disable = fake_pwm_disable,
    .owner = THIS_MODULE,
};

static int fake_pwm_probe(struct platform_device *pdev) {
    struct fake_chip *priv;
    priv = devm_kzalloc(sizeof(*Priv));
    struct pwm_chip *chip = &priv->chip;
    chip->ops = &fake_pwm_ops;
    chip->dev = &pdev->dev;
    chip->base = -1;
    chip->npwm = 3;
    platform_set_drvdat(pdev, priv);
    return pwmchip_add(chip);
}

static void fake_pwm_remove(struct platform_device *pdev) {
    struct fake_chip *priv = platform_get_drvdata(pdev);
    pwmchip_remove(&priv->chip);
}

static const struct of_device_id fake_pwm_dt_ids [] = {
    {.compatible="packt,fake-pwm"},
};
MODULE_DEVICE_TABLE(of, fake_pwm_dt_ids);

static struct platform_driver fake_pwm_driver = {
    .driver = {.name=KBUILD_MODULE, .owner=THIS_MODULE, 
                .of_match_table = fake_pwm_dt_ids,},
    .probe = fake_pwm_probe,
    .remove = fake_pwm_remove,
};

module_platform_driver(fake_pwm_driver);

3. PWM芯片与DT绑定

        从DT内绑定PWM控制器时,最重要的属性是#pwm-cells。

        下面是和fake_pwm驱动对应的节点:

fake_pwm: pwm@0 {
    #pwm-cells=<2>;
    compatible = "packt,fake-pwm"
};

下面是一个真实的示例,i.MX6 SoC中的PWM控制器示例如下:

pwm3: pwm@0208800 {
    #pwm-cells = <2>;
    compatible = "fsl,imx6q-pwm","fsl,imx27-pwm";
    reg = <0x02088000 0x4000>;
    interrupts = <0 85 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6QDL_CLK_IPG>,<&clks IMX6QDL_CLK_PWM3>;
    clock-names = "ipg", "pwm3";
    status = "disabled";
};

二、PWM框架消费者接口

        消费者是实际使用PWM通道的设备,PWM通道在内核中表示为struct pwm_device{}结构体。

struct pwm_device {
    const char *label;
    ulong flags;
    uint hwpwm; // pwm通道在硬件芯片内的相对索引
    uint pwm; // Pwm通道的全局索引

    struct pwm_chip *chip;
    void *chip_data;  // 与pwm芯片相关的专有数据

    uint period;
    uint duty_cycle;
    enum pwm_polarity polarity;
};

// PWM框架提供的pwm设备使用函数由两个版本:旧版pwm_request/pwm_free,新版pwm_get/pwm_free;
struct pwm_device* pwm_get(struct device *dev, const char *con_id); // 其中参数dev是客户端设备
void pwm_put(struct pwm_device *pwm);

// 获取struct pwm_device{}后配置接口:
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);

// 启动、停止切换pwm输出
int pwm_enable(struct pwm_device *pwm);
int pwm_disable(struct pwm_device *pwm);

使用DT方式进行pwm消费者绑定:

// pwm控制器芯片
pwm0: pwm {
    #pwm-cells = <2>;
};

// pwm消费者
bl: backligth {
    pwms = <&pwm0 0 5000000>; // 其中0为pwm索引,5000000ns为period
    pwm-names = "backligth"; // 名称对应函数pwm_get()中的con_id
};

三、通过sysfs接口使用pwm

pwm芯片对应的目录为/sys/class/pwm/pwmchip{chip-id}/,其中有三个文件:

  • npwm:只读文件,芯片中pwm通道的数量;
  • export:只写文件,echo {id} >export会导出对应对应的pwm通道,并生成对应通道目录/sys/class/pwm/pwmchip{chip-id}/pwm{pwmdevice-id}/
  • unexport:只写文件,与export文件相反

pwm通道对应的目录为/sys/class/pwm/pwmchip{chip-id}/pwm{pwmdevice-id}/,其中PWM通道的索引为芯片的相对索引,该目录下有如下文件:

  • period:用于设置/读取PWM通道的周期,ns单位
  • duty_cycle:用于设置/读取PWM的Duty cycle
  • polarity:用于设置PWM的极性
  • enable:用户启动/停止PWM设备
;