4.3.10. LPWM 驱动调试指南
4.3.10.1. 概述
LPWM (Lite-PWM) 用于生成具有可变脉冲宽度和频率的方波与 pps 触发信号同步,一般用于 camsys 系统中触发 sensor 曝光, LPWM 本身也需要外界触发,在收到 trigger 信号后,按照所配置的 lpwm 参数输出方波。
参数规格:
LPWM 默认支持频率范围为 1Hz - 1MHz,输出脉冲宽度范围为 1us~ 4ms。
4.3.10.2. 特点
芯片中 LPWM 具有以下特点:
1. 包含两个独立的 LPWM CORE,具有可编程的周期和采样周期。
2. 每个 LPWM CORE 包含 4 个 channel 输出。
3. LPWM 支持外界硬件触发与内部软件触发两种不同的模式。
4. 每个通道的 LPWM 可启用/禁用。
5. 每个通道的 LPWM 脉冲极性可以通过软件选择。
4.3.10.3. 功能描述
典型应用
当需要使用多路 Camera Sensor 时,需要使用 LPWM 控制 FSIN/VSYNC 引脚来实现多路 Camera Sensor 的同步采集,其中主要有两种控制方式:
方式一:

Sensor0 配置为主模式( FSIN/VSYNC 为输出),而 Sensor1 配置为从模式( FSIN/VSYNC 为输入)。
Sensor0 作为主设备输出 FSYNC 信号,控制其他传感器的时间同步,当 HOST 启动图像采集时,主设备 Sensor0 在曝光过程中输出 FSYNC 信号,触发其他传感器进行同步采集。
Sensor1 的 FSIN/VSYNC 引脚作为输入,受到来自 Sensor0 的控制,从而实现了两路图像传感器的同步采集。
方式二:

将两路 Sensor 都配置为从模式,使得相机的每一帧采集都由外部 FSYNC 信号触发。
HOST 统一向 Slave Sensor 发送同步信号,触发其曝光过程,通过配置的 LPWM 参数,在触发引脚上输出方波信号,每当两个传感器接收到触发信号时,它们便同步采集一帧图像。
方式三:

一路 Sensor 配置为 Slave 模式, HOST 启动图像采集时,触发引脚上会输出 LPWM 配置的方波,当 Sensor 接收到 trigger 信号时就采集一帧图像。
功能原理
LPWM_CORE 模块用于根据用户配置生成输出波形,可以自定义周期,占空比和偏移量, LPWM 内核对参考时钟进行分频( clk_lpwm)转换为 1MHZ 工作时钟,内部时间计数器用于计算时间刻度和生成具有用户定义的偏移、周期、宽度的脉冲,每个 LPWM CORE 都可以根据配置独立工作,如下时序图:

当 Sensor 在收到 LPWM 的 trigger 信号后(红色箭头),开始曝光出图, 由于 LPWM 上存在一个固定的 delay 会在整个运行期间存在,所以 trigger 信号与 SIF 曝光出图间并未完整对齐, LPWM 与 Sensor, SIF 交互图如下:

4.3.10.4. 驱动代码
LPWM 代码路径
kernel/drivers/media/platform/horizon/camsys/lpwm/
内核配置
/* arch/arm64/configs/hobot_x5_soc_defconfig */
..
CONFIG_HOBOT_LPWM=m
...
DTS 节点配置
X5 PWM 及 LPWM 控制器设备树定义位于 kernel 文件夹下的 arch/arm64/boot/dts/hobot/x5.dtsi 文件内 , 当需要使能特定 PWM 端口输出的时候,可以到对应的板级文件修改,这里以 x5-evb.dts 为例,使能 lpwm0 ch0-4、lpwm1 ch1。
备注: x5.dtsi 中的节点主要声明 SoC 共有特性,和具体电路板无关,一般情况下不用修改。
/* arch/arm64/boot/dts/hobot/x5-evb.dts */
...
&lpwm0 {
status = "okay";
/* conflict with camera pwd gpio */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lpwm0_0 &pinctrl_lpwm0_1
&pinctrl_lpwm0_2 &pinctrl_lpwm0_3>;
};
&lpwm1 {
status = "okay";
pinctrl-names = "default";
/** for display backlight **/
pinctrl-0 = <&pinctrl_lpwm1_1>;
};
...
lpwm 设备树解析如下:
&lpwm0:这是一个设备节点,表示一个名为 lpwm0 的 PWM 控制器。status = "okay":表示启用此设备节点,即 lpwm0 PWM 控制器处于启用状态,如果状态设置为 “disabled”,则该设备会被禁用。pinctrl-names = "default":定义了引脚控制的名称,这里使用了默认引脚控制配置。pinctrl-0 = <&pinctrl_lpwm0_0 &pinctrl_lpwm0_1 &pinctrl_lpwm0_2 &pinctrl_lpwm0_3>: 这里打开了 LPWM0 四个 channel。指定了 lpwm0 控制器的引脚配置。这是一个指针数组,指向一系列引脚控制配置(通常是由 pinctrl 子系统管理的),每个 pinctrl_lpwm0_X 表示一个预定义的引脚配置,用于控制 LPWM 信号在物理引脚上的行为,例如引脚号、复用功能、电源域配置等。
4.3.10.5. 功能使用
kernel space
lpwm preobe 驱动源码
hobot_lpwm_probe 函数是一个设备驱动的初始化函数,属于 platform_driver 的 probe 操作。它在设备被检测到并且驱动被加载时执行,用于初始化 lpwm 模块硬件资源和配置平台设备。
static int32_t hobot_lpwm_probe(struct platform_device *pdev)
{
int32_t ret = LPWM_RET_OK;
struct hobot_lpwm_ins *lpwm;
lpwm = (struct hobot_lpwm_ins *)osal_kmalloc(sizeof(struct hobot_lpwm_ins),0);
if (IS_ERR(lpwm)) {
lpwm_err(NULL, "devm kzalloc failed!\n");
return -ENOMEM;
}
memset(lpwm, 0, sizeof(struct hobot_lpwm_ins));
ret = lpwm_preinit(pdev, lpwm);
if (ret != LPWM_RET_OK) {
return ret;
}
ret = lpwm_platform_init(pdev, lpwm);
if (ret != LPWM_RET_OK) {
goto err_disable_clk;
}
ret = pwmchip_add(&lpwm->chip);
if (ret < 0) {
lpwm_err(lpwm, "Add to Linux pwm chip failed!\n");
goto err_free_irq;
}
ret = lpwm_sysfs_create(pdev);
if (ret < 0) {
lpwm_err(lpwm, "Sysfs create failed!\n");
goto err_free_pwm_chip;
}
platform_set_drvdata(pdev, lpwm);
ret = lpwm_chip_cdev_create(lpwm);
if (ret != LPWM_RET_OK) {
goto err_free_pwm_chip;
}
lpwm_div_ratio_config(lpwm->base, 24);
glpwm_chip[lpwm->dev_idx] = lpwm;
lpwm->cim_cops = vio_get_callback_ops(&cim_ops, VIN_MODULE, COPS_9);
vin_register_device_node(VIN_LPWM, &vin_lpwm_ops);
lpwm_info(lpwm, "Probe success\n");
return ret;
err_free_pwm_chip:
pwmchip_remove(&lpwm->chip);
err_free_irq:
devm_free_irq(&pdev->dev, lpwm->irq, lpwm);
err_disable_clk:
clk_disable_unprepare(lpwm->sclk);
clk_disable_unprepare(lpwm->pclk);
osal_kfree(lpwm);
return ret;
}
驱动源码接口
1.osal_kmalloc
作用:在 hobot_lpwm_probe 中用来为 struct hobot_lpwm_ins 类型的实例分配内存。
参数:第一个参数为分配内存的大小,第二个参数通常是内存分配的标志( 0 表示默认)。
返回值:返回分配的内存地址,若分配失败返回 NULL。
2.lpwm_preinit
作用:对 LPWM 设备进行预初始化。
参数:平台设备对象 (pdev) 和 LPWM 设备对象 (lpwm)。
返回值:返回一个状态码,若为 LPWM_RET_OK 则表示初始化成功。
3.lpwm_platform_init
作用:初始化 LPWM 驱动需要的硬件资源,例如时钟、 GPIO、 IRQ 等。
参数:平台设备对象 (pdev) 和 LPWM 设备对象 (lpwm)。
返回值:返回负值表示添加失败,返回 0 表示成功。
4.pwmchip_add
作用:为设备创建一个 pwmchip 对象,将 LPWM 设备添加到 Linux 内核的 PWM 子系统。
参数:&lpwm->chip 为 LPWM 设备的 PWM 配置结构体。
返回值:返回负值表示添加失败,返回 0 表示成功。
4.lpwm_sysfs_create
作用:在 sysfs 中创建与 LPWM 相关的文件,允许用户空间通过文件系统与驱动交互
参数: LPWM 设备对象 (lpwm)。
返回值:返回 LPWM_RET_OK 表示成功,其他表示失败。
5.lpwm_chip_cdev_create
作用:为 LPWM 设备创建一个字符设备,允许应用程序通过设备文件进行访问。
参数:时钟句柄。
返回值:返回回调操作结构体。
6.vio_get_callback_ops:
作用:获取与 VIN(视频输入)相关的回调操作。
参数:回调操作结构体 (&cim_ops),模块标识( VIN_MODULE),操作类型标识( COPS_9 )。
返回值:返回复位控制器的句柄,若获取失败返回 ERR_PTR。
7.vin_register_device_node
作用:将 LPWM 设备注册到 VIN 子系统中,作为视频输入设备。
参数: 设备类型标识( VIN_LPWM),回调操作结构体 (&vin_lpwm_ops)。
返回值:无。
hobot_lpwm_probe 函数通过设置 vin_register_device_node(VIN_LPWM, &vin_lpwm_ops) 用于将一个与视频输入相关的设备(在这个例子中是 LPWM)注册到 VIN 框架中, vin_common_ops 是一个结构体,定义了一组与视频输入设备相关的操作函数,每个操作函数对应一个特定的视频输入控制操作:
static struct vin_common_ops vin_lpwm_ops = {
.open = lpwm_open,
.close = lpwm_close,
.video_set_attr = lpwm_init,
.video_get_attr = lpwm_get_attr,
.video_set_attr_ex = lpwm_change_attr,
.video_start = lpwm_start,
.video_stop = lpwm_stop,
.video_reset = lpwm_reset,
};
user space
lpwm 配置使用说明
以 sc230ai 软件触发为例, lpwm 的配置文件可在 app/samples/platform_samples/vp_sensors/sc230ai/linear_1920x1080_raw10_10fps_1lane.c 路径下查看 .
.lpwm_attr = {
.enable = 0,
.lpwm_chn_attr = {
{ .trigger_source = 0,
.trigger_mode = 0,
.period = 100*1000,
.offset = 10,
.duty_time = 100,
.threshold = 0,
.adjust_step = 0,
},
},
};
lpwm_attr 结构体参数解析如下:
| 参数 | 说明 | 范围 | 输出 30hz,高电平 10us 参数 |
|---|---|---|---|
| offset | 与 trigger 点之间的偏移时间 | 11 | |
| duty_time | 有效高电平时间,实际生效的时间为 (duty_time + 1)us | [0, 4000 | period) us | 9 |
| period | 周期,实际生效的周期时间为 (period + 1)us | [1, 1000000) us | 33332 |
| threshold | 缓慢同步功能阈值 | [0, 65535] us | 0 |
| adjust_step | 每次的调整时间 adjust_time = 2 ^ adjust_step | [0, 15] | 0 |
| trigger_source | 触发源选择, 1 个 lpwm chip 只能选择 1 个触发源。 比如: lpwm0 ,不能 channel_0 设置为 trigger_source 为 7 , channel_1 设置 trigger_source 为 0 | [0, 10] | |
| trigger_mode | 触发机制 (1- 外界硬件触发, 0- 内部软件触发 ) | [0, 1] |
常见场景如接入 30fps sensor,period 应设置为 1s/fps=33,333us, sensor 在跑完 30 帧后经过 999,990us, 与下次 PPS triggger 会有 10us 的间隙 ,因此 offset 应设为 10us( 至少 10us, 至多 period-duty_timeus, 否则 lpvvm 会在 1s 内发出 31 个方波 )

公式计算如下 :
Period = 1000000 / fps
Offset = 100000 - Period*fps
lpwm 触发方式
软件触发: trigger_mode = 0 ,代表内部软件触发,配置软件触发后, LPWM 驱动将自动提供基于 hrtimer 的周期为 1s 软件 trigger。本质就是 1s 去写一个 LPWM 寄存器 (LPWM_SW_TRIG),让 LPWM 重新按照配置输出方波。
硬件触发: trigger_mode = 1 ,代表代表外部触发,需要外接 一个 PPS (pulse per second) 信号, LPWM 收到 PPS 信号后,硬件会按照软件的配置,输出方波。每收到一个 PPS 信号, LPWM 都会按照配置,重新出波。
外部触发需要配置 trigger_source,外部触发源可以按照数值配置以下触发源:
| trigger_source 的值 | 对应的触发源 | 说明 |
|---|---|---|
| 0-3 | pad_trigger_in | 硬件 IO 输入 PPS,软件暂不支持。 |
| 4 | enet_ptp_pps | 预留 |
| 5 | sw_trigger_in | 软件触发 |
| 6 | gps_trigger_in (TIME_SYNC2) | 硬件 IO 输入 PPS |
| 7 | mcu_trigger_in (TIME_SYNC1) | 硬件 IO 输入 PPS |
| 8 | time_sync2_in (TIME_SYNC3) | 硬件 IO 输入 PPS |
| 9 | time_sync3_in (TIME_SYNC4) | 硬件 IO 输入 PPS |
| 10 | camera_1sec_pulse_out | SIF 模块输出 PPS,内部硬件直接连接到 LPWM (推荐) |
lpwm 映射关系
lpwm pin name 与 pad_trigger_in_x 映射关系如下:
| LPWM0 | LSIO_SPI5_SCLK | pad_trigger_in_0 |
|---|---|---|
| LSIO_SPI5_SSN | pad_trigger_in_1 | |
| LSIO_SPI5_MISO | pad_trigger_in_2 | |
| LSIO_SPI5_MOSI | pad_trigger_in_3 | |
| LPWM1 | LSIO_SPI3_SCLK | pad_trigger_in_0 |
| LSIO_SPI3_SSN | pad_trigger_in_1 | |
| LSIO_SPI3_MISO | pad_trigger_in_2 | |
| LSIO_SPI3_MOSI | pad_trigger_in_3 |
查看 evb_x5 原理图, MIPI0 接口的 camera sensor 的 lpwm trigger 引脚为 LSIO_SPI5_SCLK,如上表对应关系为 LPWM0 ,设备树关联信息配置如下:
&lpwm0 {
status = "okay";
/* conflict with camera pwd gpio */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lpwm0_0 &pinctrl_lpwm0_1
&pinctrl_lpwm0_2 &pinctrl_lpwm0_3>;
};
&vin_vcon0 {
status = "okay";
/* camera sensor
* reset gpio: AON_GPIO0_00: 498
* pwd gpio: LSIO_GPIO1_00: 347
*/
pinctrl-names = "default";
pinctrl-0 = <&aon_gpio_0>;
gpio_oth = <498>; //reset gpio
bus = <4>; //i2c4
lpwm_chn = <0>; //open lpwm0 - channel0
};
重点参数解析如下:
lpwm0 :这是一个引用节点,指的是名为 lpwm0 的硬件模块。
pinctrl-0 (lpwm): 这个属性指定了与 lpwm0 设备相关联的具体引脚控制配置( pinmux 配置)
pinctrl_lpwm0_0: 配置节点属性,配置引脚为 LSIO_SPI5_SCLK,引脚复用属性为 Lite PWM0 output。
pinctrl_lpwm0_0: pinctrl_lpwm0_0 { horizon,pins = < LSIO_SPI5_SCLK LSIO_PINMUX_2 BIT_OFFSET8 MUX_ALT2 &pconf_pwm_1v8 >; };
vin_con0 :名为 vin_vcon0 的视频输入控制节点。
lpwm_chn:选择启用当前 lpwm 的通道,通道 0 则对应 lpwm 的映射引脚为 LSIO_SPI5_SCLK。
当设备树 vin_vcon0 控制器被引用时, lpwm 通道 0 对应的 trigger 引脚将根据 lpwm_attr 结构体中的参数配置,输出方波信号。
sysfs 节点调试
在板端操作 LPWM 时,需要使用 cat 命令,读取 pwmchip 下的 device/uevent 文件并用来区分 PWM 与 LPWM 节点。
查看 x5.dtsi 中 关于 LPWM 控制器节点 如下:
lpwm0: lpwm@34100000 {
compatible = "hobot,hobot-lpwm";
status = "disabled";
reg = <0x34100000 0x10000>;
lpwm-int-status = <&lsio_sys_con 0x0>;
interrupt-parent = <&gic>;
interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&hpsclks X5_LSIO_LPWM0_CLK>, <&hpsclks X5_LSIO_LPWM0_PCLK>;
clock-names = "lpwm_sclk", "lpwm_pclk";
#pwm-cells = <2>;
resets = <&socrst LSIO_LPWM0_RESET>;
};
root@buildroot:~# cat /sys/class/pwm/pwmchip2/device/uevent
DRIVER=hobot-lpwm
OF_NAME=lpwm
OF_FULLNAME=/soc/a55_apb0/lpwm@34100000
OF_COMPATIBLE_0=hobot,hobot-lpwm
OF_COMPATIBLE_N=1
OF_ALIAS_0=lpwm0
MODALIAS=of:NlpwmT(null)Chobot,hobot-lpwm
可以看出 pwmchip2 的地址为 0x34100000 且对应驱动为 hobot-lpwm, DTS 中 lpwm0 的地址为 0x34100000 ,因此 lpwm0 对应 pwmchip2 。
root@buildroot:/sys/class/pwm# cd pwmchip2
root@buildroot:/sys/class/pwm/pwmchip2# ls
device export npwm power subsystem uevent unexport
device: 这是一个指向设备的符号链接,它通常链接到实际的硬件设备节点。通过访问该文件,系统可以获取或修改与该设备相关的属性。
npwm: 指当前 lpwm 所包含的 channel 数量,一般为 4 。
echo:户可以通过向 export 文件写入通道的编号来将其导出到 /sys/class/pwm/pwmchip2/pwmX 目录( X 是 LPWM 通道的编号)。例如, echo “0~3” > unexport 注销某个 channel,仅允许每个 channel 申请一次,再次申请会返回 BUSY。
power:该文件提供了与设备电源管理相关的信息。它可以包含关于设备的电源状态的详细信息,比如是否启用节能模式、是否开启了电源等。
subsystem:该目录是指向该 LPWM 设备所属的子系统的符号链接。子系统是 Linux 中的设备管理层次结构的一部分,表示该设备所属的类别。
uevent:该文件用于管理 udev(设备管理器)事件,它通常用于向系统通知设备的变化。
unexport:该文件允许用户通过写入来取消导出某个 LPWM 通道,如果某个 LPWM 通道已经被导出(通过 export 文件),则用户可以通过向 unexport 文件写入该通道的编号来取消导出,从而使该 LPWM 通道不再可用。
可以通过 echo 的方式,设置 period, duty_cycle 参数。注意, linux lpwm 框架参数精度为 1ns,输入参数会经过四舍五入计算出精度为 1us 的参数设置到寄存器,所以 period/duty_cycle 输入值需要 x1000 。
申请注册 lpwm0 通道:
root@buildroot:/sys/class/pwm/pwmchip2# echo 0 > export
root@buildroot:/sys/class/pwm/pwmchip2# cd pwm0
设置 周期 为 30hz:
root@buildroot:/sys/class/pwm/pwmchip2/pwm0# echo 33333000 > period
设置 占空比 为 0.3%:
root@buildroot:/sys/class/pwm/pwmchip2/pwm0# echo 100000 > duty_cycle
使能 lpwm 输出或关闭
root@buildroot:/sys/class/pwm/pwmchip2/pwm0# echo 1 > enable
root@buildroot:/sys/class/pwm/pwmchip2/pwm0# echo 0 > enable
硬件调试
查看 evb x5 原理图,使用 lpwm0 通道 0 ,对应引脚为 LSIO_SPI5_SCLK 如图:

对应开发板的点位图如下:

运行 sample 时,使用示波器探测如图:

示波器中频率与周期和配置文件中 lpwm 参数一致。
4.3.10.6. 常见问题
Q: 如果源 trigger 一段时间后,源停止 trigger, LPWM 还会输出方波吗?
A : 会继续按照配置的 period 输出方波,但是 offset 功能失效,因为没有 trigger,无法保证 后面输出的 LPWM 方波准确性,即方波的 period 可能会有误差。
Q:lpwm trigger 与中断机制
A :lpwm 发送给 cim 的信号是每次出波高电平时都会发送 ,lpwm 的中断则是根据外部 trigger source 给的 sync 信号产生的。