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 的同步采集,其中主要有两种控制方式:

方式一:

sensor_control_1

  • Sensor0 配置为主模式( FSIN/VSYNC 为输出),而 Sensor1 配置为从模式( FSIN/VSYNC 为输入)。

  • Sensor0 作为主设备输出 FSYNC 信号,控制其他传感器的时间同步,当 HOST 启动图像采集时,主设备 Sensor0 在曝光过程中输出 FSYNC 信号,触发其他传感器进行同步采集。

  • Sensor1 的 FSIN/VSYNC 引脚作为输入,受到来自 Sensor0 的控制,从而实现了两路图像传感器的同步采集。

方式二:

sensor_control_2

  • 将两路 Sensor 都配置为从模式,使得相机的每一帧采集都由外部 FSYNC 信号触发。

  • HOST 统一向 Slave Sensor 发送同步信号,触发其曝光过程,通过配置的 LPWM 参数,在触发引脚上输出方波信号,每当两个传感器接收到触发信号时,它们便同步采集一帧图像。

方式三:

sensor_control_3

  • 一路 Sensor 配置为 Slave 模式, HOST 启动图像采集时,触发引脚上会输出 LPWM 配置的方波,当 Sensor 接收到 trigger 信号时就采集一帧图像。

功能原理

LPWM_CORE 模块用于根据用户配置生成输出波形,可以自定义周期,占空比和偏移量, LPWM 内核对参考时钟进行分频( clk_lpwm)转换为 1MHZ 工作时钟,内部时间计数器用于计算时间刻度和生成具有用户定义的偏移、周期、宽度的脉冲,每个 LPWM CORE 都可以根据配置独立工作,如下时序图:

diff_lpwm_core

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

lpwm_sensor_cim

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-4lpwm1 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 个方波 )

lpwm_channel

公式计算如下 :

  • 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 如图:

lpwm_pin

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

lpwm_pin

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

lpwm_pin

示波器中频率与周期和配置文件中 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 信号产生的。