Linux pwm子系统分析1

Linux pwm子系统分析1(基于Linux6.6)---系统框架介绍

 


一、pwm子系统框架

如下即为pwm子系统的系统框架,大致可以分为如下几个方面:

  1. pwm子系统接口层,提供pwm的request、free(类似于gpio_request、gpio_free);pwm的使能与去使能;pwm 配置(配置pwm的占空比等)等接口
  2. 上面pwm子系统接口层通过pwm号在pwm_tree基数树中找到对应的pwm_device(所有注册的pwm device均会添加至pwm_tree中,另外一个pwm_chip可包含多个pwm_device,因此pwm_chip与pwm_device之间也存在关联),并借助pwm_chip提供的方法配置pwm控制器,实现pwm配置等操作;
  3. pwm_chip层主要对应一个pwm控制器,一个pwm控制器可包含多个pwm_device,针对一个pwm_chip,提供了访问pwm 控制器的方法,通过pwm_chip提供的方法,实现对pwm 控制器的配置;
  4. pwm器件即是对应的pwm控制器。

以上即为pwm子系统的框架,另外pwm子系统也借鉴了gpio子系统的实现流程(export、unexport)。针对pwm_chip,也借助device子系统以及sysfs子系统接口,为注册的pwm_chip对应struct device类型成员实现export、unexport属性(即sys下文件,如/sys/class/pwm/pwmchipX/export文件),而应用程序通过将该pwm chip对应的pwm号设置到export中,则export对应的store函数即会为该pwm device创建对应struct device类型变量,并为该device变量创建duty、period_ns、enable等属性参数,从而应用程序即可实现pwm的控制(这个和操作gpio是一样的)。

Linux 中的 PWM(脉宽调制)子系统为控制硬件(如 LED、马达、蜂鸣器等)提供了一个标准化的接口。PWM 是一种通过调节信号的占空比来控制设备输出的方式,因此它通常用于精确控制设备的功率或亮度。

Linux PWM 子系统的框架设计使得开发者能够统一访问 PWM 控制器,并允许通过标准接口管理多个 PWM 输出。下面是 Linux PWM 子系统的框架和工作原理的详细解释。

1. PWM 子系统的结构

Linux 的 PWM 子系统的核心是 pwm 子系统,它通过提供一个抽象层,允许用户和驱动程序通过简单的 API 来控制硬件 PWM 控制器。整个系统框架大致如下:

+-----------------------------------+
|        User Space (Applications)  |
|-----------------------------------|
| sysfs interfaces (e.g., pwm chip) |
+-----------------------------------+
                |
                |
                v
+-----------------------------------+
|        PWM Subsystem (Kernel)     |
|-----------------------------------|
| 1. pwm_device                     |
| 2. pwm_chip                       |
| 3. PWM API (pwm_request, pwm_config)|
|-----------------------------------|
                |
                |
                v
+-----------------------------------+
|         Hardware Layer            |
|-----------------------------------|
|   PWM Hardware Controller         |
+-----------------------------------+

2. PWM 子系统的主要组件

PWM 控制器(PWM Chip)

每个硬件平台通常包含一个或多个 PWM 控制器,每个控制器被视为一个 pwm_chip 结构。在 Linux 中,PWM 控制器是作为设备驱动程序(Device Driver)进行管理的,每个 pwm_chip 结构包含了所有与硬件控制器相关的信息。

关键字段:

  • base: PWM 控制器的基础地址。
  • npwm: PWM 控制器支持的最大通道数。
  • of_pwm_n_cells: 用于解析设备树中的 PWM 信息的单元数。
  • request: 请求 PWM 控制的函数。
  • free: 释放 PWM 控制的函数。
  • apply: 应用 PWM 配置的函数。

通常,每个硬件平台的驱动程序都会提供一个 pwm_chip 结构,来表示该平台的 PWM 控制器。

PWM 设备(pwm_device)

每个通过 pwm_chip 管理的 PWM 控制器可以提供多个 PWM 通道(即每个通道可以控制一个独立的设备,如 LED、风扇等)。每个控制通道通过 pwm_device 结构表示。用户空间或驱动程序通过 pwm_device 与特定的 PWM 通道进行交互。

pwm_device 结构包含以下信息:

  • chip: 指向对应 pwm_chip 的指针。
  • channel: PWM 通道编号。
  • enabled: 标识该 PWM 是否已启用。
  • duty_cycle: 设置 PWM 信号的占空比。

PWM 驱动(Driver)

PWM 驱动程序负责硬件平台的初始化、PWM 控制器的配置、启用和禁用 PWM 输出,以及与硬件交互的细节。驱动程序将会向内核注册 PWM 控制器,并暴露 API 给上层使用。

PWM API(用户接口)

Linux 内核为开发者提供了一组 PWM API,允许驱动程序或用户空间程序控制 PWM。常用的 API 包括:

  • pwm_request(int pwm_id, const char *label): 请求一个 PWM 控制通道。通过通道 ID 和标签来标识。

  • pwm_free(struct pwm_device *pwm): 释放 PWM 通道。

  • pwm_config(struct pwm_device *pwm, unsigned long duty_ns, unsigned long period_ns): 配置 PWM 信号的占空比和周期。

  • pwm_enable(struct pwm_device *pwm): 启用 PWM 输出。

  • pwm_disable(struct pwm_device *pwm): 禁用 PWM 输出。 

二、pwm相关数据结构说明

上面说明了pwm 子系统的框架,从数据结构、接口说明pwm子系统是如何实现上述一中所说的框架的。如下图,pwm子系统对外提供系统接口,供内核其他子系统调用。

  1. pwm子系统接口提供了pwm的使能、去使能、pwm配置(占空比、频率等属性);
  2. 上述1中的接口一般传递pwm device或者pwm号,获取到pwm device,而pwm device中则包含指向其pwm_chip的指针,从而找到pwm chip,并通过pwm chip的ops接口,实现与pwm控制器的通信;
  3. pwm chip中主要包括该pwm chip的pwm base index、pwm num、pwm 操作接口(enable、disable、pwm config、pwm request、pwm free),这基本上和gpio子系统中gpio_chip的成员类似;
  4. pwm_ops主要包括enable、disable、pwm config、pwm request、pwm free这几个接口。

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.
 * @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 (*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);
	int (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
			 struct pwm_state *state);
	struct module *owner;
};

/**
 * struct pwm_chip - abstract a PWM controller
 * @dev: device providing the PWMs
 * @ops: callbacks for this PWM controller
 * @base: number of first PWM controlled by this chip
 * @npwm: number of PWMs controlled by this chip
 * @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
 * @list: list node for internal use
 * @pwms: array of PWM devices allocated by the framework
 */
struct pwm_chip {
	struct device *dev;
	const struct pwm_ops *ops;
	int base;
	unsigned int npwm;

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

	/* only used internally by the PWM framework */
	struct list_head list;
	struct pwm_device *pwms;
};

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

/**
 * 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: last applied state
 * @last: last implemented state (for PWM_DEBUG)
 */
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_state last;
};

 

如下即为这几个数据结构的关联,此处不对数据结构中每一个成员做详细说明,该子系统相对来说还是和gpio 子系统很像的。

三、pwm chip 驱动开发流程说明

Pwm chip驱动的开发流程相对也比较简单,下面简要说明:

  1. 创建platform device,用于存储该pwm chip相关的配置参数,如pwm base index、pwm num、pwm操作相关的寄存器参数等等;
  2. 创建platform driver,在该driver的probe接口中完成pwm chip的注册,主要包括:
    1. 申请struct pwm_chip类型的内存空间;
    2. 实现struct pwm_ops中各成员接口,主要实现与pwm 控制器的通信;
    3. 调用pwmchip_add,完成pwm chip的添加。

实现以上几步,即可完成pwm chip的注册。

在 Linux 中,开发一个 PWM 控制器驱动通常涉及几个步骤。这些步骤包括硬件初始化、PWM 控制器的注册、设备树支持(如果需要)、驱动程序的编写、以及与上层应用或内核子系统的交互。以下是开发 PWM 控制器驱动的详细流程。

1. 理解硬件平台和 PWM 控制器

在开始开发 PWM 驱动程序之前,首先需要对目标硬件平台的 PWM 控制器有充分的理解。PWM 控制器通常包含以下几个部分:

  • PWM 信号的输出引脚。
  • 配置周期和占空比的寄存器。
  • 使能和禁用 PWM 输出的控制寄存器。
  • 中断处理(如果适用)。

此外,要了解如何在硬件中配置 PWM 通道,硬件文档或 datasheet 是非常重要的参考资料。

2. 准备设备树支持

大部分嵌入式系统使用设备树(Device Tree)来描述硬件信息,包括 PWM 控制器。设备树提供了硬件的描述,允许内核自动识别并初始化相关硬件。

通常,PWM 控制器的设备树节点会像下面这样进行描述:

dts

pwm1: pwm@40000000 {
    compatible = "my_pwm_controller";
    #pwm-cells = <2>;
    pwm-names = "pwm1";
    status = "okay";
};
  • compatible 字段描述了设备的兼容性字符串,用来指示该 PWM 控制器的驱动。
  • #pwm-cells 描述了如何通过设备树来传递 PWM 控制器的参数。通常,PWM 通道和占空比/周期信息需要两个单元。
  • pwm-names 用来为每个 PWM 通道命名。

3. 编写 PWM 控制器驱动

PWM 控制器驱动需要遵循一定的架构,以便与 Linux 内核的 PWM 子系统交互。主要步骤包括:

定义 pwm_chip 结构

pwm_chip 结构体是表示 PWM 控制器的核心,它包含了该控制器的初始化和控制方法。一个完整的 pwm_chip 结构会包括以下信息:

struct pwm_chip {
    struct device *dev;                  /* 控制器的设备结构 */
    unsigned int npwm;                   /* PWM 通道数量 */
    int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);  /* 请求 PWM 通道 */
    void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);     /* 释放 PWM 通道 */
    int (*config)(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns);  /* 配置 PWM */
    void (*enable)(struct pwm_chip *chip, struct pwm_device *pwm);   /* 启用 PWM 输出 */
    void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);  /* 禁用 PWM 输出 */
};

需要实现的关键方法:

  • request: 申请一个 PWM 通道。
  • free: 释放 PWM 通道。
  • config: 配置 PWM 通道的周期和占空比。
  • enable: 启用 PWM 输出。
  • disable: 禁用 PWM 输出。

实现 pwm_device 结构

pwm_device 是一个表示 PWM 输出通道的结构体。它通常由 pwm_chip 来管理。pwm_device 包含了具体 PWM 输出的属性,如占空比、周期等。

struct pwm_device {
    struct pwm_chip *chip;  /* 指向 PWM 控制器的指针 */
    unsigned int hwpwm;     /* 硬件 PWM 通道编号 */
    bool enabled;           /* 是否启用 */
    unsigned long duty_cycle; /* 占空比 */
    unsigned long period;     /* 周期 */
};

初始化 pwm_chippwm_device

在驱动的初始化过程中,通常需要初始化 pwm_chippwm_device 结构。pwm_chip 结构通过 PWM 控制器的硬件接口来管理 PWM 通道,而每个 pwm_device 代表一个通道。

例如:

static int my_pwm_chip_probe(struct platform_device *pdev) {
    struct pwm_chip *chip;
    struct pwm_device *pwm;
    
    chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
    if (!chip)
        return -ENOMEM;

    chip->dev = &pdev->dev;
    chip->npwm = 4;  /* 假设该 PWM 控制器有 4 个通道 */

    /* 初始化 PWM 控制器的方法 */
    chip->request = my_pwm_chip_request;
    chip->free = my_pwm_chip_free;
    chip->config = my_pwm_chip_config;
    chip->enable = my_pwm_chip_enable;
    chip->disable = my_pwm_chip_disable;

    /* 注册 PWM 控制器 */
    return pwmchip_add(chip);
}

设备注册与解绑

注册 PWM 控制器设备时,使用 pwmchip_add() 函数,将 pwm_chip 结构添加到内核中。解绑时,使用 pwmchip_remove() 函数注销设备。

static int my_pwm_chip_remove(struct platform_device *pdev) {
    struct pwm_chip *chip = platform_get_drvdata(pdev);
    
    pwmchip_remove(chip);
    return 0;
}

4. 实现 PWM 控制方法

驱动的核心是如何实现对硬件 PWM 控制器的控制。以下是一些常见的方法:

请求 PWM 通道

pwm_request 方法在设备启动时由内核调用,它应该验证并初始化 PWM 通道。

static int my_pwm_chip_request(struct pwm_chip *chip, struct pwm_device *pwm) {
    /* 初始化 PWM 通道 */
    return 0;
}

配置 PWM 信号

pwm_config 方法设置 PWM 的占空比和周期。例如,设置周期为 1000 毫秒,占空比为 500 毫秒:

static int my_pwm_chip_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) {
    /* 配置 PWM 控制器的硬件寄存器 */
    return 0;
}

启用和禁用 PWM 输出

pwm_enablepwm_disable 方法控制 PWM 输出的启用和禁用。

static void my_pwm_chip_enable(struct pwm_chip *chip, struct pwm_device *pwm) {
    /* 启用 PWM 输出 */
}

static void my_pwm_chip_disable(struct pwm_chip *chip, struct pwm_device *pwm) {
    /* 禁用 PWM 输出 */
}

5. 注册与解绑设备

proberemove 函数中,分别调用 pwmchip_add()pwmchip_remove() 来注册和注销 PWM 控制器。

static struct platform_driver my_pwm_driver = {
    .driver = {
        .name = "my_pwm_driver",
        .of_match_table = my_pwm_of_match,
    },
    .probe = my_pwm_chip_probe,
    .remove = my_pwm_chip_remove,
};

6. 使用 sysfs 接口

为了让用户空间可以访问和控制 PWM,通常会通过 sysfs 提供一个接口。这些接口通常会暴露给用户空间应用,用于设置占空比、周期、启用/禁用等。

static ssize_t pwm_duty_cycle_show(struct device *dev, struct device_attribute *attr, char *buf) {
    return sprintf(buf, "%lu\n", pwm->duty_cycle);
}

static ssize_t pwm_duty_cycle_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
    /* 解析并设置占空比 */
    return count;
}

static DEVICE_ATTR(duty_cycle, 0644, pwm_duty_cycle_show, pwm_duty_cycle_store);

7. 测试与调试

开发完 PWM 驱动后,需要进行全面的测试和调试,确保 PWM 控制器的功能正常。这可以通过:

  • 使用 dmesg 查看内核日志。
  • 使用 sysfs 接口手动配置 PWM。
  • 通过应用程序测试 PWM 信号的输出效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值