36、PWM驱动与调节器框架详解

AI助手已提取文章相关产品:

PWM驱动与调节器框架详解

1. PWM驱动

PWM(脉冲宽度调制)驱动在嵌入式系统中有着广泛的应用,下面将详细介绍PWM驱动的相关内容。

1.1 PWM控制器绑定

在设备树(DT)中绑定PWM控制器时, #pwm-cells 是最重要的属性,它表示用于表示该控制器的PWM设备的单元数量。如果 struct pwm_chip 结构中的 of_xlate 钩子未设置, pwm-cells 必须设置为2;否则,应设置为与 of_pwm_n_cells 相同的值。

以下是一个i.MX6 SoC的PWM控制器节点示例:

pwm3: pwm@02088000 {
    #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", "per";
    status = "disabled";
};

对应的假PWM驱动节点如下:

fake_pwm: pwm@0 {
    #pwm-cells = <2>;
    compatible = "packt,fake-pwm";
    /*
     * Our driver does not use resource
     * neither mem, IRQ, nor Clock)
     */
};
1.2 PWM消费者接口

消费者是实际使用PWM通道的设备,PWM通道在内核中由 struct pwm_device 结构表示。在kernel v4.7之前,该结构如下:

struct pwm_device {
   const char *label;
   unsigned long flags;
   unsigned int hwpwm;
   unsigned int pwm;
   struct pwm_chip *chip;
   void *chip_data;
  unsigned int period;     /* in nanoseconds */
  unsigned int duty_cycle; /* in nanoseconds */
  enum pwm_polarity polarity;
};

各字段含义如下:
- label :PWM设备的名称。
- flags :与PWM设备关联的标志。
- hwpwm :PWM设备相对于芯片的相对索引。
- pwm :PWM设备的系统全局索引。
- chip :提供此PWM设备的PWM芯片。
- chip_data :与此PWM设备关联的芯片私有数据。

自kernel v4.7起,结构变为:

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;
};

其中:
- args :表示与板相关的PWM参数,通常从PWM查找表或设备树中获取,代表用户希望在此PWM设备上使用的初始配置。
- state :表示当前PWM通道的状态。

struct pwm_args struct pwm_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 */
}

PWM框架在Linux的发展过程中经历了一些变化,消费者接口可分为两个版本:
- 旧版本 :使用 pwm_request() pwm_free() 来请求和释放PWM设备。
- 新版本(推荐) :使用 pwm_get() pwm_put() 函数。 pwm_get() 以消费者设备和通道名称作为参数请求PWM设备, pwm_put() 以要释放的PWM设备作为参数。还有管理变体 devm_pwm_get() devm_pwm_put()

struct pwm_device *pwm_get(struct device *dev, const char *con_id)
void pwm_put(struct pwm_device *pwm)

需要注意的是, pwm_request()/pwm_get() pwm_free()/pwm_put() 不能在原子上下文中调用,因为PWM核心使用了可能会休眠的互斥锁。

请求PWM设备后,需要使用 pwm_config() 进行配置:

int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);

使用 pwm_enable() pwm_disable() 来启动和停止PWM输出:

int pwm_enable(struct pwm_device *pwm)
void pwm_disable(struct pwm_device *pwm)

pwm_enable() 成功时返回0,失败时返回负错误码。

以下是一个驱动PWM LED的消费者代码示例:

static void pwm_led_drive(struct pwm_device *pwm,
                      struct private_data *priv)
{
    /* Configure the PWM, applying a period and duty cycle */
    pwm_config(pwm, priv->duty, priv->pwm_period);
    /* Start toggling */
    pwm_enable(pchip->pwmd);
    [...] /* Do some work */
    /* And then stop toggling*/
    pwm_disable(pchip->pwmd);
}
1.3 PWM客户端绑定

PWM设备可以从以下方式分配给消费者:
- 设备树
- ACPI
- 板初始化文件中的静态查找表

这里推荐使用设备树绑定。绑定PWM消费者(客户端)到其驱动时,需要提供与之关联的控制器的句柄。建议将PWM属性命名为 pwms ,还可以提供可选属性 pwm-names 来命名 pwms 属性中列出的每个PWM设备。如果未提供 pwm-names 属性,则将使用用户节点的名称作为备用。

以下是一个基于PWM的背光设备示例:

pwm: pwm {
    #pwm-cells = <2>;
};
[...]
bl: backlight {
pwms = <&pwm 0 5000000>;
   pwm-names = "backlight";
};

pwms 中的 0 对应于相对于控制器的PWM索引, 5000000 表示周期(纳秒)。在这个例子中,指定 pwm-names 是多余的,因为 backlight 名称会作为备用。驱动需要调用:

static int my_consummer_probe(struct platform_device *pdev)
{
    struct pwm_device *pwm;
    pwm = pwm_get(&pdev->dev, "backlight");
    if (IS_ERR(pwm)) {
       pr_info("unable to request PWM, trying legacy API\n");
       /*
        * Some drivers use the legacy API as fallback, in order
        * to request a PWM ID, global to the system
        * pwm = pwm_request(global_pwm_id, "pwm beeper");
        */
    }
    [...]
    return 0;
}
1.4 使用sysfs接口操作PWM

PWM核心的sysfs根路径是 /sys/class/pwm/ ,这是用户空间管理PWM设备的方式。每个添加到系统的PWM控制器/芯片会在sysfs根路径下创建一个 pwmchipN 目录,其中 N 是PWM芯片的基址。该目录包含以下文件:
- npwm :只读文件,显示该芯片支持的PWM通道数量。
- Export :只写文件,用于导出PWM通道以供sysfs使用。
- Unexport :只写文件,用于从sysfs中取消导出PWM通道。

PWM通道编号从0到 pwm<n-1> ,这些编号是芯片本地的。每个PWM通道导出会在 pwmchipN 中创建一个 pwmX 目录,该目录包含以下文件:
- Period :可读写文件,用于获取/设置PWM信号的总周期(纳秒)。
- duty_cycle :可读写文件,用于获取/设置PWM信号的占空比(纳秒),值必须小于周期。
- Polarity :可读写文件,仅当PWM设备的芯片支持极性反转时使用,最好在PWM未启用时更改极性,接受的值为字符串 normal inversed
- Enable :可读写文件,用于启用(开始切换)/禁用(停止切换)PWM信号,接受的值为 0 (禁用)和 1 (启用)。

以下是通过sysfs接口从用户空间使用PWM的示例:
1. 启用PWM:

# echo 1 > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/enable
  1. 设置PWM周期:
# echo <value in nanoseconds> > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/period
  1. 设置PWM占空比(值必须小于PWM周期):
# echo <value in nanoseconds> > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/duty_cycle
  1. 禁用PWM:
# echo 0 > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/enable
2. 调节器框架

调节器是为其他设备供电的电子设备,由调节器供电的设备称为消费者。大多数调节器可以启用和禁用其输出,有些还可以控制输出电压或电流。Linux调节器框架旨在接口和控制电压和电流调节器,分为以下四个独立接口:
- 调节器驱动接口,用于调节器PMIC驱动,其结构可在 include/linux/regulator/driver.h 中找到。
- 消费者接口,用于设备驱动。
- 机器接口,用于板配置。
- sysfs接口,用于用户空间。

2.1 PMIC/生产者驱动接口

生产者是产生调节后的电压或电流的设备,即PMIC(电源管理集成电路),可用于电源排序、电池管理、DC-DC转换或简单的电源开关(开/关)。它在软件控制下调节输入电源的输出功率。

该接口需要包含以下头文件:

#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>
2.2 驱动数据结构

以下是调节器框架使用的数据结构介绍。

2.2.1 描述结构

内核通过 struct regulator_desc 结构描述PMIC提供的每个调节器,该结构包含调节器的固定属性。例如,Intersil的ISL6271A是一个具有三个独立调节输出的PMIC,其驱动中应该有三个 regulator_desc 实例。

struct regulator_desc {
   const char *name;
   const char *of_match;
   int id;
   unsigned n_voltages;
   const struct regulator_ops *ops;
   int irq;
   enum regulator_type type;
   struct module *owner;
   unsigned int min_uV;
   unsigned int uV_step;
};

各字段含义如下:
- name :调节器的名称。
- of_match :用于在DT中标识调节器的名称。
- id :调节器的数字标识符。
- owner :提供调节器的模块,应设置为 THIS_MODULE
- type :指示调节器是电压调节器还是电流调节器,值可以是 REGULATOR_VOLTAGE REGULATOR_CURRENT ,其他值将导致调节器注册失败。
- n_voltages :该调节器可用的选择器数量,表示调节器可以输出的数值,对于固定输出电压, n_voltages 应设置为1。
- min_uV :该调节器可以提供的最小电压值,即最低选择器给出的电压。
- uV_step :每个选择器的电压增量。
- ops :调节器操作表,指向调节器可以支持的一组操作回调。
- irq :调节器的中断号。

2.2.2 约束结构

当PMIC向消费者暴露调节器时,需要使用 struct regulation_constraints 结构为该调节器施加一些标称限制,这是调节器驱动和消费者驱动之间的一种契约。

struct regulation_constraints {
   const char *name;
   /* voltage output range (inclusive) - for voltage control */
   int min_uV;
   int max_uV;
   int uV_offset;
   /* current output range (inclusive) - for current control */
   int min_uA;
   int max_uA;
   /* valid regulator operating modes for this machine */
   unsigned int valid_modes_mask;
   /* valid operations for regulator on this machine */
   unsigned int valid_ops_mask;
   struct regulator_state state_disk;
   struct regulator_state state_mem;
   struct regulator_state state_standby;
   suspend_state_t initial_state; /* suspend state to set at init */
   /* mode to set on startup */
   unsigned int initial_mode;
   /* constraint flags */
   unsigned always_on:1;   /* regulator never off when system is on */
   unsigned boot_on:1;     /* bootloader/firmware enabled regulator */
   unsigned apply_uV:1;    /* apply uV constraint if min == max */
};

各字段含义如下:
- min_uV min_uA max_uA max_uV :消费者可以设置的最小电压/电流值。
- uV_offset :应用于消费者电压的偏移量,以补偿电压降。
- valid_modes_mask valid_ops_mask :分别是消费者可以配置/执行的模式/操作的掩码。
- always_on :如果调节器不应被禁用,则应设置该标志。
- boot_on :如果调节器在系统初始启动时启用,则应设置该标志。如果调节器未由硬件或引导加载程序启用,则在应用约束时将启用它。
- name :用于显示目的的约束描述性名称。
- apply_uV :初始化时应用电压约束。
- input_uV :当该调节器由另一个调节器供电时的输入电压。
- state_disk state_mem state_standby :定义系统在磁盘模式、内存模式或待机模式下挂起时调节器的状态。
- initial_state :默认设置的挂起状态。
- initial_mode :启动时设置的模式。

2.2.3 初始化数据结构

有两种方式将 regulator_init_data 传递给驱动:通过板初始化文件中的平台数据或使用 of_get_regulator_init_data 函数从设备树节点中获取。

struct regulator_init_data {
   struct regulation_constraints constraints;
   /* optional regulator machine specific init */
   int (*regulator_init)(void *driver_data);
   void *driver_data;      /* core does not touch this */
};

各字段含义如下:
- constraints :调节器约束。
- regulator_init :核心注册调节器时在给定时刻调用的可选回调。
- driver_data :传递给 regulator_init 的数据。

2.3 向板文件中填充初始化数据

这种方法是在驱动或板文件中填充约束数组,并将其作为平台数据的一部分。以下是基于Intersil的ISL6271A的示例:

static struct regulator_init_data isl_init_data[] = {
    [0] = {
                .constraints = {
                    .name           = "Core Buck",
                    .min_uV         = 850000,
                    .max_uV         = 1600000,
                    .valid_modes_mask   = REGULATOR_MODE_NORMAL
                                | REGULATOR_MODE_STANDBY,
                    .valid_ops_mask     = REGULATOR_CHANGE_MODE
                                | REGULATOR_CHANGE_STATUS,
                },
        },
    [1] = {
                .constraints = {
                    .name           = "LDO1",
                    .min_uV         = 1100000,
                    .max_uV         = 1100000,
                    .always_on      = true,
                    .valid_modes_mask   = REGULATOR_MODE_NORMAL
                                | REGULATOR_MODE_STANDBY,
                    .valid_ops_mask     = REGULATOR_CHANGE_MODE
                                | REGULATOR_CHANGE_STATUS,
                },
        },
    [2] = {
                .constraints = {
                    .name           = "LDO2",
                    .min_uV         = 1300000,
                    .max_uV         = 1300000,
                    .always_on      = true,
                    .valid_modes_mask   = REGULATOR_MODE_NORMAL
                                | REGULATOR_MODE_STANDBY,
                    .valid_ops_mask     = REGULATOR_CHANGE_MODE
                                | REGULATOR_CHANGE_STATUS,
                },
        },
};

这种方法现已弃用,推荐使用设备树方法。

2.4 向设备树中填充初始化数据

为了从设备树中提取初始化数据,需要引入 struct of_regulator_match 结构:

struct of_regulator_match {
   const char *name;
   void *driver_data;
   struct regulator_init_data *init_data;
   struct device_node *of_node;
   const struct regulator_desc *desc;
};

在设备树中,每个PMIC节点应该有一个名为 regulators 的子节点,其中需要将该PMIC提供的每个调节器声明为一个专用子节点。调节器节点可以定义以下标准化属性:
- regulator-name :用于调节器输出的描述性名称。
- regulator-min-microvolt :消费者可以设置的最小电压。
- regulator-max-microvolt :消费者可以设置的最大电压。
- regulator-microvolt-offset :应用于电压的偏移量,以补偿电压降。
- regulator-min-microamp :消费者可以设置的最小电流。
- regulator-max-microamp :消费者可以设置的最大电流。
- regulator-always-on :布尔值,指示调节器是否不应被禁用。
- regulator-boot-on :引导加载程序/固件启用的调节器。
- <name>-supply :指向父电源/调节器节点的句柄。
- regulator-ramp-delay :调节器的斜坡延迟(uV/uS)。

以下是ISL6271A驱动的设备树条目示例:

isl6271a@3c {
   compatible = "isl6271a";
   reg = <0x3c>;
   interrupts = <0 86 0x4>;
    /* supposing our regulator is powered by another regulator */
   in-v1-supply = <&some_reg>;
   [...]
   regulators {
         reg1: core_buck {
               regulator-name = "Core Buck";
               regulator-min-microvolt = <850000>;
               regulator-max-microvolt = <1600000>;
         };
         reg2: ldo1 {
               regulator-name = "LDO1";
               regulator-min-microvolt = <1100000>;
               regulator-max-microvolt = <1100000>;
               regulator-always-on;
         };
         reg3: ldo2 {
               regulator-name = "LDO2";
               regulator-min-microvolt = <1300000>;
               regulator-max-microvolt = <1300000>;
               regulator-always-on;
         };
   };
};

通过以上内容,我们详细介绍了PWM驱动和调节器框架的相关知识,包括数据结构、接口和使用方法等。掌握这些知识可以帮助我们更好地开发和优化相关驱动程序。

PWM驱动与调节器框架详解

3. 总结与回顾

PWM驱动和调节器框架在嵌入式系统中起着至关重要的作用。对于PWM驱动,我们了解到它涵盖了控制器绑定、消费者接口、客户端绑定以及sysfs接口操作等多个方面:
- PWM控制器绑定 :关键在于 #pwm-cells 属性,其值的设定取决于 of_xlate 钩子是否设置。
- PWM消费者接口 :经历了版本的演变,新版本推荐使用 pwm_get() pwm_put() 函数,请求设备后需进行配置和启动/停止操作。
- PWM客户端绑定 :推荐使用设备树绑定,通过 pwms pwm-names 属性进行配置。
- sysfs接口操作 :提供了用户空间管理PWM设备的方式,通过读写不同文件实现对PWM通道的控制。

而调节器框架则主要分为四个接口,其中PMIC/生产者驱动接口涉及多个数据结构:
- 描述结构 struct regulator_desc 用于描述调节器的固定属性。
- 约束结构 struct regulation_constraints 为调节器施加标称限制。
- 初始化数据结构 struct regulator_init_data 可通过板文件或设备树传递初始化数据。

下面通过一个表格来对比PWM驱动和调节器框架的关键信息:
| 类别 | 关键元素 | 说明 |
| ---- | ---- | ---- |
| PWM驱动 | #pwm-cells | 控制器绑定中重要属性,决定PWM设备单元数量 |
| | pwm_get() / pwm_put() | 消费者接口新版本函数,用于请求和释放PWM设备 |
| | pwms / pwm-names | 客户端绑定中用于指定PWM设备和名称 |
| | /sys/class/pwm/ | sysfs接口根路径,用于用户空间管理PWM设备 |
| 调节器框架 | struct regulator_desc | 描述调节器固定属性的数据结构 |
| | struct regulation_constraints | 为调节器施加限制的约束结构 |
| | struct regulator_init_data | 传递初始化数据的数据结构 |
| | regulators 子节点 | 设备树中PMIC节点下用于声明调节器的子节点 |

4. 实际应用与流程示例

为了更好地理解PWM驱动和调节器框架的实际应用,下面分别给出一个PWM驱动控制LED和调节器框架配置电源的流程示例。

4.1 PWM驱动控制LED流程
graph LR
    A[初始化PWM设备] --> B[请求PWM设备]
    B --> C[配置PWM参数]
    C --> D[启动PWM输出]
    D --> E[执行其他操作]
    E --> F[停止PWM输出]
    F --> G[释放PWM设备]

具体操作步骤如下:
1. 初始化PWM设备 :在代码中包含必要的头文件,定义相关数据结构。
2. 请求PWM设备 :使用 pwm_get() 函数请求PWM设备。

struct pwm_device *pwm;
pwm = pwm_get(&pdev->dev, "backlight");
if (IS_ERR(pwm)) {
    pr_info("unable to request PWM, trying legacy API\n");
    // 尝试旧版本API
}
  1. 配置PWM参数 :使用 pwm_config() 函数配置PWM的占空比和周期。
pwm_config(pwm, priv->duty, priv->pwm_period);
  1. 启动PWM输出 :使用 pwm_enable() 函数启动PWM输出。
pwm_enable(pwm);
  1. 执行其他操作 :在PWM输出启动后,可以执行其他相关操作。
  2. 停止PWM输出 :使用 pwm_disable() 函数停止PWM输出。
pwm_disable(pwm);
  1. 释放PWM设备 :使用 pwm_put() 函数释放PWM设备。
pwm_put(pwm);
4.2 调节器框架配置电源流程
graph LR
    A[初始化调节器数据] --> B[查找调节器节点]
    B --> C[提取初始化数据]
    C --> D[注册调节器]
    D --> E[设备请求电源]
    E --> F[调节器提供电源]
    F --> G[设备使用电源]
    G --> H[设备释放电源]
    H --> I[调节器停止供电]

具体操作步骤如下:
1. 初始化调节器数据 :定义 struct regulator_desc struct regulation_constraints struct regulator_init_data 等数据结构,并进行初始化。

struct regulator_desc my_regulator_desc = {
    .name = "my_regulator",
    .of_match = "my_regulator_match",
    .id = 0,
    .n_voltages = 1,
    .min_uV = 1000000,
    .uV_step = 100000,
    // 其他字段初始化
};

struct regulation_constraints my_constraints = {
    .name = "my_constraints",
    .min_uV = 1000000,
    .max_uV = 2000000,
    // 其他字段初始化
};

struct regulator_init_data my_init_data = {
    .constraints = my_constraints,
    // 其他字段初始化
};
  1. 查找调节器节点 :在设备树中查找PMIC节点及其 regulators 子节点。
  2. 提取初始化数据 :使用 of_get_regulator_init_data 函数从设备树中提取初始化数据。
struct of_regulator_match my_match;
// 初始化my_match
of_get_regulator_init_data(of_node, my_match.desc, &my_match.init_data);
  1. 注册调节器 :将调节器注册到系统中。
// 注册调节器的代码
  1. 设备请求电源 :设备驱动使用相关函数请求调节器提供电源。
struct regulator *my_regulator;
my_regulator = regulator_get(&pdev->dev, "my_regulator");
if (IS_ERR(my_regulator)) {
    pr_err("Failed to get regulator\n");
}
  1. 调节器提供电源 :调节器根据请求提供电源。
  2. 设备使用电源 :设备使用调节器提供的电源进行工作。
  3. 设备释放电源 :设备使用完毕后,释放电源。
regulator_put(my_regulator);
  1. 调节器停止供电 :调节器停止向设备供电。

通过以上的总结、对比和实际应用流程示例,我们对PWM驱动和调节器框架有了更深入的理解。在实际开发中,可以根据具体需求灵活运用这些知识,开发出高效、稳定的嵌入式系统。

您可能感兴趣的与本文相关内容

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值