# LPWM 驱动调试指南

## 概述

LPWM (Lite-PWM) 用于生成具有可变脉冲宽度和频率的方波与 pps 触发信号同步，一般用于 camsys 系统中触发 sensor 曝光， LPWM 本身也需要外界触发，在收到 trigger 信号后，按照所配置的 lpwm 参数输出方波。

参数规格：

- LPWM 默认支持频率范围为 1Hz - 1MHz，输出脉冲宽度范围为 1us～ 4ms。

## 特点

芯片中 LPWM 具有以下特点：

**1. 包含两个独立的 LPWM CORE，具有可编程的周期和采样周期。**

**2. 每个 LPWM CORE 包含 4 个 channel 输出。**

**3. LPWM 支持外界硬件触发与内部软件触发两种不同的模式。**

**4. 每个通道的 LPWM 可启用/禁用。**

**5. 每个通道的 LPWM 脉冲极性可以通过软件选择。**


## 功能描述

### 典型应用

当需要使用多路 Camera Sensor 时，需要使用 LPWM 控制 FSIN/VSYNC 引脚来实现多路 Camera Sensor 的同步采集，其中主要有两种控制方式：

**方式一：**

![sensor_control_1](_static/_images/29-LPWM_Driver_Debug_Guide/sensor_control_1.png)

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

**方式二：**

![sensor_control_2](_static/_images/29-LPWM_Driver_Debug_Guide/sensor_control_2.png)

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

**方式三：**

![sensor_control_3](_static/_images/29-LPWM_Driver_Debug_Guide/sensor_control_3.png)

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

### 功能原理

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

![diff_lpwm_core](_static/_images/29-LPWM_Driver_Debug_Guide/diff_lpwm_core.png)

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

![lpwm_sensor_cim](_static/_images/29-LPWM_Driver_Debug_Guide/lpwm_sensor_cim.png)

## 驱动代码

### LPWM 代码路径

```shell
kernel/drivers/media/platform/horizon/camsys/lpwm/
```

### 内核配置

```shell
/* 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`。

<font color=red> 备注：</font>
x5.dtsi 中的节点主要声明 SoC 共有特性，和具体电路板无关，一般情况下不用修改。

```shell
/* 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 信号在物理引脚上的行为，例如引脚号、复用功能、电源域配置等。

## 功能使用

### kernel space

#### lpwm preobe 驱动源码

hobot_lpwm_probe 函数是一个设备驱动的初始化函数，属于 platform_driver 的 probe 操作。它在设备被检测到并且驱动被加载时执行，用于初始化 lpwm 模块硬件资源和配置平台设备。

```c
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 是一个结构体，定义了一组与视频输入设备相关的操作函数，每个操作函数对应一个特定的视频输入控制操作：

```c
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 路径下查看 .

```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](_static/_images/29-LPWM_Driver_Debug_Guide/lpwm_channel.png)

公式计算如下 :

- 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 ，设备树关联信息配置如下：

```shell
&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。

  ```shell
  	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 控制器节点 如下：

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

```shell
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 。

```shell
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 通道：

```shell
root@buildroot:/sys/class/pwm/pwmchip2# echo 0 > export
root@buildroot:/sys/class/pwm/pwmchip2# cd pwm0
```

- 设置 周期 为 30hz：

```shell
root@buildroot:/sys/class/pwm/pwmchip2/pwm0# echo 33333000 > period
```

- 设置 占空比 为 0.3%：

```shell
root@buildroot:/sys/class/pwm/pwmchip2/pwm0# echo 100000 > duty_cycle
```

- 使能 lpwm 输出或关闭

```shell
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](_static/_images/29-LPWM_Driver_Debug_Guide/lpwm_pin.png)

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

![lpwm_pin](_static/_images/29-LPWM_Driver_Debug_Guide/lpwm_pin_position.png)

运行 sample 时，使用示波器探测如图：

![lpwm_pin](_static/_images/29-LPWM_Driver_Debug_Guide/lpwm_trigger.png)

示波器中频率与周期和配置文件中 lpwm 参数一致。

## 常见问题

### Q: 如果源 trigger 一段时间后，源停止 trigger， LPWM 还会输出方波吗？

**A** : 会继续按照配置的 period 输出方波，但是 offset 功能失效，因为没有 trigger，无法保证 后面输出的 LPWM 方波准确性，即方波的 period 可能会有误差。

### Q:lpwm trigger 与中断机制

**A** :lpwm 发送给 cim 的信号是每次出波高电平时都会发送 ,lpwm 的中断则是根据外部 trigger source 给的 sync 信号产生的。