介绍

通用定时器可以在三个 Linux 子系统中工作。

  • 时钟源 —— 通用定时器可以作为时间子系统的时钟源。

  • MFD 定时器 —— 仅用于在 MFD 子系统中实现通用定时器的基本定时功能。

  • PWM – timer8 是 PWM 定时器,并在 PWM 子系统中实现。

这三个部分将分别进行描述。

时钟源

介绍

架构

通用定时器可以用作时钟源或时钟事件。典型用法是在时间子系统中用作广播定时器。下图显示了 Linux 时间子系统的架构。

../../rst_linux/8_timer/figures/time_subsystem_software_arch.png

与时间相关的模块(如节拍设备、hrtimer、时间保持等)依赖于时钟源模块和时钟事件模块,这些模块依赖于硬件定时器。通用定时器是硬件定时器,因此可以用作时钟源模块和时钟事件模块。

通用定时器的频率不足以让其成为系统的节拍设备。实际上,CPU 本地定时器是用于为系统生成节拍的设备。

然而,通用定时器在时间子系统中仍然发挥作用,即作为广播定时器。广播定时器的存在对时间子系统很有用,因为它使 CPU 本地定时器可以使用单次模式。此外,系统可以在空闲状态下停止 CPU 本地定时器以节省功耗。

所有通用定时器都可以用作时钟源。

备注

Timer0 可以用作 LP 和 NP 系统的系统定时器,因此不建议将 Timer0 用作时钟源。

实现

通用定时器的时钟源驱动实现如下文件:

驱动程序位置

介绍

<linux>/drivers/rtkdrivers/clocksource/Kconfig

时钟源驱动程序 Kconfig

<linux>/drivers/rtkdrivers/clocksource/Makefile

时钟源驱动程序 Makefile

<linux>/drivers/rtkdrivers/clocksource/timer-rtk.c

通用定时器的时钟源驱动程序

配置

设备树配置

设备树的配置如下所示。以 TIMER1 为例:

timer1: timer@4200B200 {
   compatible = "realtek,ameba-timer-clk";
   reg = <0x4200B200 0x200>;
   interrupts = <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>;
   clocks = <&rcc RTK_CKE_TIM1>;
};

其设备树属性的描述如下:

属性

描述

可配置?

compatible

用于匹配驱动程序和设备的 ID

reg

寄存器资源

interrupts

SPI 中断

clocks

通用定时器时钟节点

编译配置

选择 Device Drivers > Drivers for Realtek > Clocksource driver > General timer

../../rst_linux/8_timer/figures/clock_source_build_configuration1.png
../../rst_linux/8_timer/figures/clock_source_build_configuration2.png

APIs

用户空间 API

无。

内核空间 API

参考 timers_information v5.4timers_information v6.6 获取更多信息。

MFD 定时器

介绍

软件架构

通用定时器的基本计时功能被放置在 MFD(多功能设备)子系统中。由于出现了一种具有多种功能的外设设备,Linux 提供了一个 MFD 子系统。

实际上,MFD 定时器驱动并未使用 MFD 子系统的任何内容,因此此处未展示 MFD 架构。

MFD 定时器驱动只是为内核提供 API,以便内核可以简单地使用定时器中断功能。

所有通用定时器都可以是 MFD 定时器。

备注

Timer0 是 LP 和 NP 的系统定时器,因此不建议将 Timer0 用作 MFD 定时器。

实现

MFD 定时器驱动实现为以下文件:

驱动程序位置

介绍

<linux>/drivers/rtkdrivers/mfd_timer/Kconfig

MFD 定时器驱动程序 Kconfig

<linux>/drivers/rtkdrivers/mfd_timer/Makefile

MFD 定时器驱动程序 Makefile

<linux>/drivers/rtkdrivers/mfd_timer/rtk-timer.c

通用定时器的 MFD 定时器驱动程序

配置

设备树配置

其设备树配置如下所示。以 timer2 为例:

timer2: timer@4200B400 {
   compatible = "realtek,ameba-timer";
   reg = <0x4200B400 0x200>;
   interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
   clocks = <&rcc RTK_CKE_TIM2>;
};

下面列出其属性的描述:

属性

描述

可配置?

compatible

用于匹配驱动程序和设备的 ID

reg

寄存器资源

interrupts

SPI 中断

clocks

通用时钟节点

编译配置

选择 Device Drivers > Drivers for Realtek > Timer driver

../../rst_linux/8_timer/figures/mfd_build_configuration.png

APIs

用户空间 API

无。

内核空间 API

API

描述

rtk_gtimer_init

用特定的通道号初始化一个 MFD 定时器

rtk_gtimer_dynamic_init

初始化一个 MFD 定时器,并使用任意空闲通道

rtk_gtimer_deinit

重置定时器

rtk_gtimer_int_config

启用或禁用定时器更新中断

rtk_gtimer_int_clear

清除中断标志

rtk_gtimer_change_period

改变定时器周期

rtk_gtimer_start

启动/停止定时器(计数器不会被同时置为0)

以下是一个示例,展示如何使用内核 API。

  1. 初始化 MFD 定时器设备。

    rtk_gtimer_init(Tim_Idx, 10000000, timer_callback, cbdata);
    
  2. 配置 MFD 定时器中断。

    rtk_gtimer_int_config(Tim_Idx, 1);
    
  3. 启动定时器。

    rtk_gtimer_start(Tim_Idx, 1);
    

定时器中断将在 10000000 纳秒后触发,然后执行使用参数 cbdata 的处理函数 time_callback

PWM

介绍

架构

PWM 定时器可以用作 PMW 设备。Linux PWM 子系统的架构如下所示。

../../rst_linux/8_timer/figures/pwm_subsystem_arch.png

应用程序通过 sysfs 层控制 PWM 设备,而内核通过 API 控制它。PWM 核心实现 PWM 子系统的核心逻辑。

备注

只有 timer8 可以用作 PWM 设备。

实现

PWM 驱动程序的实现如下所列:

驱动程序位置

介绍

<linux>/drivers/rtkdrivers/pwm/Kconfig

PWM 定时器驱动程序 Kconfig

<linux>/drivers/rtkdrivers/pwm/Makefile

PWM 定时器驱动程序 Makefile

<linux>/drivers/rtkdrivers/pwm/rtk-pwm.c

PWM 定时器的 PWM 驱动程序

配置

设备树配置

由于 PWM 定时器也可以作为通用定时器使用,因此 PWM 节点位于定时器节点中。设备树配置如下。

timer8: timer@4100A000 {
   compatible = "realtek,ameba-timer";
   #address-cells = <1>;
   #size-cells = <1>;
   reg = <0x4100A000 0x200>;
   interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
   clocks = <&rcc RTK_CKE_TIM_PWM>;
   pwm: pwm@0 {
      compatible = "realtek,ameba-pwm";
      status = "disabled";
   };
};

其属性如下表所述:

属性

描述

可配置?

compatible

用于匹配驱动程序和设备的 ID

reg

寄存器资源

status

是否启用设备

  • “disable”

  • “okay”

pinctrl 的设备树节点在 <dts>/rtl8730e-pinctrl.dtsi 中描述。

pwm_pins: pwm@0 {
   pins1 {
      pinmux = <REALTEK_PINMUX('B', 14, PWM)>, // HS_PWM0
               <REALTEK_PINMUX('B', 15, PWM)>,     // HS_PWM1
               <REALTEK_PINMUX('B', 16, PWM)>,     // HS_PWM2
               <REALTEK_PINMUX('A', 11, PWM)>,     // HS_PWM3
               <REALTEK_PINMUX('A', 12, PWM)>;     // HS_PWM4
      bias-pull-up;
      slew-rate = <0>;
      drive-strength = <0>;
   };
   pins2 {
      pinmux = <REALTEK_PINMUX('A', 13, PWM)>;  // HS_PWM5
      bias-pull-up;
      swd-disable;
      slew-rate = <0>;
      drive-strength = <0>;
   };
};

下表列出其属性的描述:

属性

描述

可配置?

pinmux

PWM 的引脚定义

bias-pull-up

引脚上拉/下拉状态

slew-rate

引脚电压转换速率

drive-strength

引脚驱动强度

备注

请参考引脚复用(pinmux)规范来选择引脚分配。

编译配置

选择 Device Drivers > Drivers for Realtek > PWM driver

../../rst_linux/8_timer/figures/pwm_build_configuration.png

APIs

用户空间 API

PWM 子系统提供用户空间接口: /sys/class/pwm

用户可以通过 sysfs 层控制 PWM 设备。PWM 节点是 /sys/class/pwm/pwmchip0

下面是一个示例:

  1. 用户应首先选择一个通道进行导出。导出通道 2 的命令是:

    echo 2 > /sys/class/pwm/pwmchip0/export
    
  2. 配置周期为 2000000ns 的命令是:

    echo 2000000 > /sys/class/pwm/pwmchip0/pwm2/period
    
  3. 配置占空比为 25%的命令是:

    echo 500000 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle
    
  4. 启用 PWM 通道的命令是:

    echo 1 > /sys/class/pwm/pwmchip0/pwm2/enable
    

然后,通道 2 将输出频率为 500Hz、占空比为 25%的方波。

用户空间的 PWM 演示位于 <test>/pwm。请参考 PWM 演示以获得更多信息。

内核空间 API

PWM 子系统为内核空间提供 API。

API

描述

pwm_request

请求PWM设备

pwm_free

是否PWM设备

pwm_apply_state

原子性地将新状态应用于PWM设备

pwm_adjust_config

将当前PWM配置调整为PWM参数

pwm_config

改变PWM设备的配置

pwm_enable

启动PWM输出切换

pwm_disable

停止PWM输出切换

参考 PWM_information v5.4PWM_information v6.6 获取更多细节。

以下是一个展示如何使用内核 API 的例子

  1. 请求 PWM 设备。

    pwm_dev= pwm_request(pwm_channel_id, NULL);
    
  2. 配置和应用 PWM 设置。

    ret = pwm_config(pwm_dev, 500000, 1000000);
    
  3. 启动 PWM 输出。

    ret = pwm_enable(pwm_dev);