# K230 Linux 摄像头路数与 Sensor 配置指南

本文介绍当前 K230 Linux SDK 各板级摄像头的默认配置，以及如何通过设备树
配置单路、双路或三路摄像头。新增一种 Sensor 驱动的方法请参考
[K230 Linux SENSOR 移植指南](sensor_adaptation_doc.md)。

## 当前 Linux 板级 Sensor 配置

### 基本概念

K230 最多提供 CSI0、CSI1、CSI2 三个物理 MIPI CSI 控制器。设备树中的
`mipi.N` 表示逻辑摄像头编号 N，不一定连接同编号的物理 CSI。

下面三个编号容易混淆，阅读配置前应先区分：

| 名称 | 示例 | 含义 |
| --- | --- | --- |
| 逻辑摄像头编号 | slot0、slot1、slot2 | 决定 ISP port 和每路输出分组 |
| VVCAM 输出序号 | `video_index=0`～`8` | 驱动内部序号，也是 entity 名称最后一段 |
| Linux video 设备号 | `/dev/video1`～`/dev/video9` | 内核动态分配的设备节点，不能由 DTS 固定 |

设备树中两类配置的职责不同：

- `mipi.N` 的节点序号 N 决定 `/dev/vvcam-mipi.N` 和 ISP port N；节点中的
  `id`、`reg`、`interrupts`、`resets` 决定该逻辑端口连接哪个物理 CSI。
- DTS 标签 `&v4l2isp` 指向运行时的 `v4l2-isp` 节点。其中
  `devN-sensor-name`、`devN-sensor-mode` 和 `devN-sensor-i2c-bus` 描述
  ISP port N 使用的 Sensor 驱动名称、该驱动的 mode 下标和 Linux I2C
  adapter 编号。它们不决定物理 CSI 映射。
- 只有 `mipi.N` 已启用并且对应 `devN-sensor-*` 完整时，该路才计入摄像头
  路数。

可以将数据通路理解为：

```text
物理 CSI（由 id 等资源决定）
    → 逻辑 mipi.N / ISP port N
    → 第 N 组 MP、SP1、SP2 entity
    → 内核动态分配的 /dev/videoX

Sensor 控制信息 devN-sensor-*
    → Sensor 驱动、mode 下标和 /dev/i2c-X
```

在当前 MCM 配置中，每路 Sensor 注册 MP、SP1、SP2 三个 VVCAM 输出：

| 逻辑摄像头 | MIPI 节点 | VVCAM `video_index` | entity 名称 | 当前板端设备节点 |
| --- | --- | --- | --- | --- |
| 第 1 路（slot0） | `mipi.0` | 0、1、2 | `vvcam-video.0.0`～`vvcam-video.0.2` | `/dev/video1`～`/dev/video3` |
| 第 2 路（slot1） | `mipi.1` | 3、4、5 | `vvcam-video.0.3`～`vvcam-video.0.5` | `/dev/video4`～`/dev/video6` |
| 第 3 路（slot2） | `mipi.2` | 6、7、8 | `vvcam-video.0.6`～`vvcam-video.0.8` | `/dev/video7`～`/dev/video9` |

以上“每路 3 个输出”以加载 `vvcam_video mcm_mask=1` 为前提。SDK 启动脚本
`S31canaan_isp` 已按此方式加载模块。若手工加载时不传该参数，会进入旧版
pipeline 布局，单路可能注册包含 RAW 在内的 4 个输出，不适用本文的多路
映射表。

表中的 `/dev/video1`～`/dev/video9` 是当前板端实测结果，不是固定规则。
代码调用 `video_register_device(..., -1)` 让内核自动分配设备号；当前系统中
`/dev/video0` 已被 `mvx` 占用，所以 VVCAM 从 `/dev/video1` 开始注册。
如果其它驱动的注册顺序变化，设备号也可能变化。

应用应通过 entity 名称查找对应设备，而不是直接假定某一路永远是
`/dev/video1`、`/dev/video4` 或 `/dev/video7`：

```shell
for d in /sys/class/video4linux/video*; do
    echo "$(basename "$d"): $(cat "$d/name")"
done

# 查看完整 media graph；当前板端 VVCAM media 设备为 media0
media-ctl -d /dev/media0 -p
```

三路 MP 的稳定标识分别是 `vvcam-video.0.0`、`vvcam-video.0.3` 和
`vvcam-video.0.6`。

### 芯片侧 CSI 资源

| 物理 CSI | id | reg | IRQ |
| --- | ---: | --- | ---: |
| CSI0 | 0 | `0x90009800` | 115 |
| CSI1 | 1 | `0x9000a000` | 118 |
| CSI2 | 2 | `0x9000a800` | 121 |

板级设备树可以把任一物理 CSI 映射到逻辑 `mipi.0`。例如部分板型将
物理 CSI2 映射到 `mipi.0`，因此它仍属于第 1 路，MP entity 仍是
`vvcam-video.0.0`。

SoC 基线 `k230.dtsi` 默认是 `mipi.0`→CSI0 且启用，`mipi.1`、
`mipi.2` 禁用。板级 DTS 将 CSI2 映射到 slot0 时，会覆盖 `mipi0` 的
`id`、寄存器、中断和复位资源，而不是改为启用原生 `mipi2`。

### 当前板端实测配置

在当前连接的板子上读取运行时设备树、`/proc/vsi/isp_subdev0` 和 media
拓扑，得到以下配置：

| 逻辑摄像头 | 物理 CSI | Sensor | mode | Linux I2C adapter | MP entity | 当前设备节点 |
| --- | --- | --- | ---: | ---: | --- | --- |
| 第 1 路（slot0） | CSI2 | GC2093 | 0 | 4 | `vvcam-video.0.0` | `/dev/video1` |
| 第 2 路（slot1） | CSI0 | GC2093 | 0 | 0 | `vvcam-video.0.3` | `/dev/video4` |
| 第 3 路（slot2） | CSI1 | GC2093 | 0 | 1 | `vvcam-video.0.6` | `/dev/video7` |

运行时还确认：

- `mipi.0`、`mipi.1`、`mipi.2` 均为 `"okay"`，物理 `id` 分别为 2、0、1。
- `/dev/vvcam-mipi.0`～`.2` 均存在。
- 9 个 VVCAM 输出的 entity 为 `vvcam-video.0.0`～
  `vvcam-video.0.8`，本次分别映射到 `/dev/video1`～`/dev/video9`。
- `/dev/video0` 是 `mvx`，`/dev/video10` 是 `canaan-non-ai-2d`，说明
  `/dev/videoN` 的 N 属于系统动态设备号，不等于 VVCAM `video_index`。

可以使用以下命令在目标板上重新核对：

```shell
cat /proc/vsi/isp_subdev0
media-ctl -d /dev/media0 -p
ls /dev/vvcam-mipi.*
```

### 各板型 SDK 默认配置

当前 SDK 默认 Sensor 主要为 GC2093，默认 mode 0 对应
1920×1080@30fps。这里描述的是 SDK DTS 默认值，不代表刷入后又被修改过的
板端当前状态：

| defconfig / 板型 | 默认路数 | 逻辑槽位到物理 CSI | DTS bus → 实际 I2C | 说明 |
| --- | ---: | --- | --- | --- |
| `k230_canmv_01studio_defconfig` | 1 | slot0→CSI2 | 4→i2c4 | 已预置 slot1→CSI0、slot2→CSI1，默认 disabled；CSI2 reset=gpio0_9 |
| `k230_canmv_01studio_emmc_defconfig` | 1 | slot0→CSI2 | 0→i2c4（alias） | 未预置三路；CSI2 reset=gpio1_30，与 SD 版不同 |
| `k230_canmv_lckfb_defconfig` | 1 | slot0→CSI2 | 4→i2c4 | 已预置 slot1→CSI0、slot2→CSI1，默认 disabled；CSI2 reset=gpio0_13 |
| `k230_canmv_defconfig` | 1 | slot0→CSI0 | 0→i2c0 | CanMV 1.0/1.1 |
| `k230_canmv_v3_defconfig` | 1 | slot0→CSI2 | 0→i2c4（alias） | |
| `k230_canmv_dongshanpi_defconfig` | 1 | slot0→CSI2 | 0→i2c4（alias） | |
| `k230_canmv_gt6700_defconfig` | 1 | slot0→CSI2 | 0→i2c4（alias） | |
| `k230d_canmv_defconfig` | 1 | slot0→CSI2 | 0→i2c4（alias） | |
| `k230d_canmv_ilp32_defconfig` | DTS 1，VVCAM 未启用 | slot0→CSI2 | 0→i2c4（alias） | 默认未启用 `BR2_PACKAGE_VVCAM` |
| `BPI-CanMV-K230D-Zero_defconfig` | DTS 1，但配置不一致 | slot0→CSI2 | 0→i2c0（无 alias） | defconfig 为 IMX335，DTS 仍为 GC2093，I2C 连接也需核对 |
| `BPI-CanMV-K230D-Zero_ilp32_defconfig` | DTS 1，VVCAM 未启用 | slot0→CSI2 | 0→i2c0（无 alias） | Sensor 配置不一致，且默认未启用 VVCAM |
| `k230_evb_defconfig` | 1 | slot0→CSI0 | 0→i2c0 | 继承 SoC 基线设备树 |
| `k230d_canmv_junroc_ai_cam_defconfig` | 1 | slot0→CSI1 | 0→i2c4（alias） | 特殊映射 |

需要特别注意：

1. `k230_canmv_01studio_defconfig` 和 `k230_canmv_lckfb_defconfig` 已写好
   三路 GC2093 配置，但默认仅 `mipi.0` 为 `"okay"`，所以只注册一路。
2. `BPI-CanMV-K230D-Zero` 的 defconfig 默认 Sensor 为 IMX335，而基线
   DTS 仍可能是 GC2093，使用前应统一 DTS 和 Buildroot 配置。
3. 部分 ILP32 配置没有启用 `BR2_PACKAGE_VVCAM`，仅修改 DTS 不能使摄像头
   工作。

### Sensor mode 与 I2C bus

`devN-sensor-mode` 是对应 Sensor 驱动中 `modes[]` 数组的下标，不是通用
分辨率枚举。GC2093 当前常用 mode 如下：

| mode | 分辨率和帧率 | MIPI lane |
| ---: | --- | ---: |
| 0 | 1920×1080@30fps | 2 |
| 1 | 1920×1080@60fps | 2 |
| 2 | 1280×960@90fps | 2 |
| 3 | 1280×720@90fps | 2 |

更换 Sensor 后必须查看
`buildroot-overlay/package/vvcam/src/<sensor>.c` 中该 Sensor 的 `modes[]`，
不能沿用 GC2093 的 mode 含义。

`devN-sensor-i2c-bus` 表示 Linux adapter 编号，即 `/dev/i2c-N` 中的 N。
该编号会受到板级 `aliases` 影响，SDK 中存在两种配置方式。

旧式单摄板常把 Linux adapter 0 映射到硬件 i2c4：

```dts
aliases {
    i2c0 = &i2c4;
};
```

此时 `devN-sensor-i2c-bus = <0>` 实际访问硬件 i2c4。

01studio SD 和 lckfb 的多摄配置使用真实编号：

```dts
aliases {
    i2c0 = &i2c0;
    i2c1 = &i2c1;
    i2c4 = &i2c4;
};
```

此时预置三路的 bus 4、0、1 分别表示硬件 i2c4、i2c0、i2c1。修改配置前
必须先检查本板 `aliases`，再填写 bus；同时确保实际 I2C 控制器节点为
`"okay"`。

## 配置单路、双路或三路摄像头

### 配置规则

驱动从 slot0 开始连续统计摄像头路数。每个启用槽位必须同时满足：

1. `mipi.N` 的 `status = "okay"`。
2. DTS 的 `&v4l2isp` 节点中存在 `devN-sensor-name`。
3. 同时配置匹配的 `devN-sensor-mode` 和
   `devN-sensor-i2c-bus`。
4. 对应 I2C 控制器已启用。

槽位必须从 0 连续配置，不能只启用 slot0 和 slot2。遇到第一个缺失或
disabled 的槽位后，后面的槽位不会计入摄像头路数。可以提前写好
`dev1/dev2-sensor-*`，只要 `mipi.1/mipi.2` 保持 disabled，系统仍按单路
注册。

在 `mcm_mask=1` 下，逻辑 slot N 的三个输出序号固定为 `3N`、`3N+1`、
`3N+2`，依次对应 MP、SP1、SP2。

DTS 源文件中的 `&mipi0`、`&mipi1`、`&mipi2` 是节点标签引用；系统启动后
对应的节点名称是 `mipi.0`、`mipi.1`、`mipi.2`。同样，DTS 标签
`&v4l2isp` 对应运行时的 `v4l2-isp` 节点。

映射物理 CSI 时，必须成套修改以下字段：

- `id`
- `reg`
- `interrupts`
- `resets`
- `reset-gpios`
- 对应的 `devN-sensor-i2c-bus`

不同 `mipi.N` 不能使用相同的物理 CSI `id`。其中 `reset-gpios` 和 I2C
连接是板级属性，必须以原理图和对应板级 DTS 为准，不能直接照搬其它板型。

### 修改位置和编译

编译一次 SDK 后，板级 DTS 位于：

```text
output/<defconfig>/build/linux-*/arch/riscv/boot/dts/canaan/
```

例如：

```text
output/k230_canmv_01studio_defconfig/build/linux-*/arch/riscv/boot/dts/canaan/k230-canmv-01studio.dts
output/k230_canmv_lckfb_defconfig/build/linux-*/arch/riscv/boot/dts/canaan/k230-canmv-lckfb-lcd.dts
```

直接修改 output 目录适合调试。正式修改还应同步到 SDK 的板级 DTS
补丁或 overlay 中，避免清理 output 后丢失。

修改后重新编译：

```shell
make linux-dirclean
make linux-rebuild
make
```

然后更新板上的 DTB 或完整镜像。若同时修改了 VVCAM 驱动或
`libvvcam.so`，还需要更新 `vvcam_mipi`、`vvcam_isp_subdev`、
`vvcam_video` 等对应内核模块和用户态库。

### 单路摄像头

单路必须使用逻辑 slot0。下面以 01studio / lckfb 已预置的
“slot0→物理 CSI2”为例：

```dts
&i2c4 {
    status = "okay";
};

&mipi0 {
    /* id/reg/irq/resets/reset-gpios 使用板级 CSI2 配置 */
    status = "okay";
};

&mipi1 {
    status = "disabled";
};

&mipi2 {
    status = "disabled";
};

&v4l2isp {
    dev0-sensor-name = "gc2093";
    dev0-sensor-mode = <0>;
    dev0-sensor-i2c-bus = <4>;
};
```

如果板级 alias 为 `i2c0 = &i2c4`，则上例的 bus 应写 `<0>`。若希望第一路
使用 CSI0，应将 `mipi0` 的整套物理资源改为 CSI0，同时修改 reset GPIO 和
I2C bus，而不是启用 `mipi1`。

单路配置会注册 3 个 VVCAM 输出：

- 内部 `video_index`：0～2。
- entity：`vvcam-video.0.0`～`vvcam-video.0.2`。
- 当前实测板端：`/dev/video1`～`/dev/video3`；其它系统可能不同。

### 双路摄像头

下面以 01studio / lckfb 的预置顺序为例：

- slot0 → 物理 CSI2
- slot1 → 物理 CSI0

```dts
&i2c0 {
    status = "okay";
};

&i2c4 {
    status = "okay";
};

&mipi0 {
    /* 板级 CSI2 资源 */
    status = "okay";
};

&mipi1 {
    /* 板级 CSI0 资源 */
    status = "okay";
};

&mipi2 {
    status = "disabled";
};

&v4l2isp {
    dev0-sensor-name = "gc2093";
    dev0-sensor-mode = <0>;
    dev0-sensor-i2c-bus = <4>; /* CSI2 */

    dev1-sensor-name = "gc2093";
    dev1-sensor-mode = <0>;
    dev1-sensor-i2c-bus = <0>; /* CSI0 */
};
```

对于已经预置三路配置的 01studio 和 lckfb，需要确认 I2C 已启用，并将
`mipi1` 的 `status` 从 `"disabled"` 改为 `"okay"`。其它板型还需要补充
`mipi1` 的 CSI0 物理资源、reset GPIO 和 `dev1-sensor-*`。

只修改 `mipi1` 而没有启用实际对应的 `i2c0`，会出现第二路节点已经注册、
但 Sensor 无法访问和出图的情况。

双路配置会注册 6 个 VVCAM 输出：

- 内部 `video_index`：0～5。
- entity：`vvcam-video.0.0`～`vvcam-video.0.5`。
- 两路 MP entity：`vvcam-video.0.0` 和 `vvcam-video.0.3`。
- 按当前板端设备注册顺序，通常对应 `/dev/video1`～`/dev/video6`，
  但应用仍应按 entity 名称查找。

### 三路摄像头

01studio / lckfb 的预置顺序是：

- slot0 → 物理 CSI2
- slot1 → 物理 CSI0
- slot2 → 物理 CSI1

这是这两类板型的预置 remap 顺序，不是芯片强制顺序。若使用
slot0→CSI0、slot1→CSI1、slot2→CSI2 的原生顺序，需要同时替换每个
`mipi.N` 的物理 CSI 资源、reset GPIO 和对应 `devN-sensor-i2c-bus`。

```dts
&i2c0 {
    status = "okay";
};

&i2c1 {
    status = "okay";
};

&i2c4 {
    status = "okay";
};

&mipi0 {
    /* 板级 CSI2 资源 */
    status = "okay";
};

&mipi1 {
    /* 板级 CSI0 资源 */
    status = "okay";
};

&mipi2 {
    /* 板级 CSI1 资源 */
    status = "okay";
};

&v4l2isp {
    dev0-sensor-name = "gc2093";
    dev0-sensor-mode = <0>;
    dev0-sensor-i2c-bus = <4>; /* CSI2 */

    dev1-sensor-name = "gc2093";
    dev1-sensor-mode = <0>;
    dev1-sensor-i2c-bus = <0>; /* CSI0 */

    dev2-sensor-name = "gc2093";
    dev2-sensor-mode = <0>;
    dev2-sensor-i2c-bus = <1>; /* CSI1 */
};
```

对于已预置三路配置的板型，确认 I2C 后将 `mipi1`、`mipi2` 都改为
`"okay"` 即可。未预置的板型必须补齐两个 MIPI 节点的全部物理资源和
`dev1/dev2` 配置。

三路预置方案需要硬件 i2c4、i2c0、i2c1 全部可用；`&i2c0` 或 `&i2c1`
未设置 `status = "okay"` 时，对应摄像头不能正常工作。

三路配置会注册 9 个 VVCAM 输出：

- 内部 `video_index`：0～8。
- entity：`vvcam-video.0.0`～`vvcam-video.0.8`。
- 三路 MP entity：`vvcam-video.0.0`、`vvcam-video.0.3` 和
  `vvcam-video.0.6`。
- 当前板端实际设备节点为 `/dev/video1`～`/dev/video9`。

### 更换 Sensor

将某一路由 GC2093 更换为其它 Sensor 时，需要同步完成：

1. 将 `devN-sensor-name` 改为 VVCAM 支持的名称。
2. 根据该 Sensor 驱动的 `modes[]` 设置 `devN-sensor-mode`。
3. 根据实际连接修改 I2C bus、reset GPIO、lane 数和 MIPI 频率。
4. 确认 Buildroot 的 `BR2_PACKAGE_VVCAM_DEF_SENSOR` 和 `libvvcam.so` 已包含
   该 Sensor，并准备 `/etc/vvcam/` 下对应的 ISP 标定文件。
5. 多路同型号且 I2C 地址相同时，优先使用不同 I2C 控制器，避免地址冲突。

### 板上验证

启动后依次检查：

```shell
# MIPI 逻辑设备数量
ls /dev/vvcam-mipi.*

# I2C adapter 是否存在
ls /dev/i2c-*

# DTS 中的 sensor、mode、I2C bus 是否被正确解析
cat /proc/vsi/isp_subdev0

# 物理 CSI 与逻辑 DVP/ISP port 的绑定
dmesg | grep -E 'bind SOURCE_CSI|misc_id|sensor_count'

# 根据 entity 名称查找动态分配的 /dev/video*
for d in /sys/class/video4linux/video*; do
    echo "$(basename "$d"): $(cat "$d/name")"
done
```

预期结果如下。“当前板端设备节点”一列用于说明本次实测映射，不应作为
应用程序的固定设备号：

| 路数 | MIPI 设备 | VVCAM entity | MP entity | 当前板端设备节点 |
| ---: | --- | --- | --- | --- |
| 1 | `/dev/vvcam-mipi.0` | `vvcam-video.0.0`～`vvcam-video.0.2` | `vvcam-video.0.0` | `/dev/video1`～`/dev/video3` |
| 2 | `/dev/vvcam-mipi.0`、`/dev/vvcam-mipi.1` | `vvcam-video.0.0`～`vvcam-video.0.5` | `vvcam-video.0.0`、`vvcam-video.0.3` | `/dev/video1`～`/dev/video6` |
| 3 | `/dev/vvcam-mipi.0`、`/dev/vvcam-mipi.1`、`/dev/vvcam-mipi.2` | `vvcam-video.0.0`～`vvcam-video.0.8` | `vvcam-video.0.0`、`vvcam-video.0.3`、`vvcam-video.0.6` | `/dev/video1`～`/dev/video9` |

如果某一路没有图像，优先检查该路的 I2C adapter、`reset-gpios`、
`devN-sensor-mode` 和 MIPI lane 配置。如果 `/dev/vvcam-mipi.*` 数量或
`vvcam-video.*` entity 数量不符合预期，检查 `mipi.N` 是否从 0 连续启用，
以及 `devN-sensor-*` 是否完整；不要根据 `/dev/videoN` 的最大编号判断路数。

### 配置与代码的对应关系

文档中的配置规则来自以下实现（路径相对于 `buildroot-overlay/`）：

| 文件 | 实际作用 |
| --- | --- |
| `package/vvcam/v4l2/common/vvcam_sensor_dt.h` | 从 slot0 开始，按“`devN-sensor-name` 存在且 `mipi.N` 已启用”连续统计路数 |
| `package/vvcam/mipi/vvcam_mipi_driver.c` | `mipi.N` 节点序号映射到 DVP/ISP port N，`id` 映射到物理 CSI |
| `package/vvcam/v4l2/isp/vvcam_isp_driver.c` | 解析 `devN-sensor-name`、mode 和 I2C bus |
| `package/vvcam/v4l2/video/vvcam_pipeline_link.h` | 定义每路 MP、SP1、SP2 对应的 `video_index` 和 ISP pad |
| `package/vvcam/v4l2/video/vvcam_video_driver.c` | MCM 模式按 `sensor_count × 3` 注册 VVCAM 输出 |
| `package/vvcam/v4l2/video/vvcam_video_register.c` | 使用 `video_register_device(..., -1)` 动态申请 `/dev/videoN` |
| `package/vvcam/src/gc2093.c` | 定义 GC2093 的 `modes[]`、分辨率、帧率、lane 和 MIPI 频率 |
| `board/canaan/k230-soc/rootfs_overlay/etc/init.d/S31canaan_isp` | 使用 `mcm_mask=1` 加载 `vvcam_video`，启用本文描述的多路布局 |

因此配置时不要用 `/dev/videoN` 的 N 推导逻辑端口或物理 CSI。推荐顺序是：

1. 先用 `mipi.N` 确定第几路逻辑摄像头。
2. 再用 `id` 和整套 CSI 资源确定物理接口。
3. 用同编号的 `devN-sensor-*` 配置 Sensor 控制信息。
4. 启动后根据 `vvcam-video.0.K` entity 名称查找实际 `/dev/videoN`。
