Camera 开发指南
本篇主要介绍 Spacemit K1 平台 Camera 模块的快速上手开发。
K1 仅支 持 MIPI 类型接口,使用 Spacemit camera 驱动框架。
Camera 快速点亮导览
点亮一款新的摄像头,通常仅需要调整 cam-test 应用层的代码即可快速支持上。
点亮 sensor 所依赖的上下电 GPIO,MCLK 时钟,MIPI lane 配置等硬件功能配置,在方案对外发布前就已经由内部工程师提前开发验证完毕,极少数情况下需要修改配置 dts 和驱动。比如:由于外部不可抗拒原因,必须要对主板的 MIPI CSI 接口电路进行更改,上电 GPIOA 更改为 GPIOB,MCLKA 更改为 MCLKB 等,此情况下需要重新定制内核 dts 配置,详情参阅camera驱动框架下的bring up总结章节。
如果不考虑特殊情况,点亮一款新摄像头,建议按照以下步骤展开:
- 根据当前摄像头型号,复用列表中已支持的相近型号的应用代码(主要是复用摄像头应用的代码结构排布,减少开发工作量),修改函数名称,结构体名称等为当前摄像头型号,详情参阅user层cam_sensors库的bring up总结章节。
- 阅读摄像头的数据手册,确定摄像头的寄存器位数,I2C 地址,上电流程,ID 寄存器以及 ID 值,并修改 sensor 应用代码,其中,上电流程可以参阅sensor驱动章节.
- 配置摄像头的 setting tab 寄存器数组,并根据原厂提供的信息,或计算出来的数值,确定配置所使用的 lane 数/HTS/VTS/MCLK/FPS/PCLK/分辨率/data Lane 等信息,并完善函数内容(主要关注 xxx_spm_get_sensor_capbility 和 xxx_spm_get_sensor_work_info 函数)。
- 调整 xxx_sensor.c 源文件中使用到的曝光增益等寄存器地址。
- 尝试上电读 ID 测试,如果读 ID 失败,请重 新检查步骤 1。
- 尝试出图测试,出图测试可以选用 single online test,详情参阅user层demo示例的场景介绍章节。如果出图失败,可以使用 only viisp case 再测试。如果仍旧失败,请认真检查步骤 3,步骤 4,或寻求工程师协助分析。
- Single online test 正常出图的 log,可以参阅实操log章节。
备注:关于测试应用以及各个 test 的介绍,可以参阅user层demo示例章节内容。
另外: 关于camera模块的规格特性,请参考进迭时空开发者社区的 芯片规格说明书:https://developer.spacemit.com/documentation?token=BWbGwbx7liGW21kq9lucSA6Vnpb#part779
关于camera模块的ISP效果调试,请参考进迭时空开发者社区的 ISP PQ工具用户指南:https://bianbu-linux.spacemit.com/development_guide/isp_pq_tools_user_guide
关于camera模块的ISP API开发,请参考进迭时空开发者社区的 ISP API开发指导手册:https://bianbu-linux.spacemit.com/development_guide/isp_api_development_guide
Camera 子系统硬件框图
Figure - camera 硬件子系统示意图
各个模块的功 能如下:
- SENSOR: 将从镜头传导过来的光线转换为电信号,再通过内部的 AD 转换为数字信号,最终输出 mipi raw 数据。
- CCIC: cmos camera image controller 的简称,解析接收 sensor 发送的 mipi 数据。
- IDI: ISP 硬件内部模块,接收 ccic 发送的数据或者从 ddr 读取数据,发送到 ISP Pipeline;同时可以将 ccic 发送的数据 dump 到 DDR。
- ISP-PIPE: ISP 硬件内部 pipeline,进行一系列图像相关算法的处理。
- ISP-FORMAT: ISP 硬件内部模块,控制输出的图像格式。
- ISP-DMA: ISP 硬件内部模块,将图像输出到 DDR 中。
- CPP: 图像降噪处理和边缘增强。
Camera 驱动框架
本章节内容仅学习了解即可,除了 sensor 驱动部分内容,其余在 bring up senosr 基本不会使用到。
框架简介
Spacemit camera 驱动框架是基于 linux 内核 v4l2 框架实现的,主要提供如下功能:
- 访问 camera 各个模块的硬件寄存器
- 响应中断
- buffer 和 event 的管理
- 给 user space sdk 提供调用接口
在 camera 子系统中各个模块之间的关系如下图所示:
从软件调用关系来看,从上到下依次分为以下三层:
- userspace:运行在用户空间。主要包括 ISP,CPP,VI 和 tuningtools 软件库,ISP, CPP firmware 库及 sensor 模块源码。用户主要通过调用 ISP,CPP,VI 和 sensor 模块的接口来实现 camera 应用场景。firmware 库中的接口会由 ISP 和 CPP 软件库进行内部调用。另外,如果用户需要使用 ASR 的 tuning tool 来调试 ISP/CPP 的输出图像效果,需要在应用程序中调用 libtuningtools.so 的接口创建 tuning server。
- kernel space:运行在内核空间。主要提供 ISP,CPP,VI,CCIC 及 sensor 的驱动。
- hardware layer:驱动实际调用的硬件模块。
对于用户 bring up sensor 需求而言,主要关注 userspace 的代码,重点是 APP demo 部分,即如何根据场景需求调用 SDK 动态库的接口。
源码结构
~/k1x/linux-6.1/drivers/media/platform/spacemit/camera$ tree
.
|-- built-in.a
|-- cam_ccic
| |-- ccic_drv.c
| |-- ccic_drv.h
| |-- ccic_hwreg.c
| |-- ccic_hwreg.h
| |-- csiphy.c
| |-- csiphy.h
| |-- dptc_drv.c
| |-- dptc_drv.h
| `-- dptc_pll_setting.h
|-- cam_cpp
| |-- cpp_compat_ioctl32.c
| |-- cpp_compat_ioctl32.h
| |-- cpp_dmabuf.c
| |-- cpp_dmabuf.h
| |-- cpp_iommu.c
| |-- cpp_iommu.h
| |-- cpp-v2p0.c
| |-- k1x_cpp.c
| |-- k1x_cpp.h
| |-- regs-cpp-iommu.h
| |-- regs-cpp-v2p0.h
| `-- regs-fbc-v2p0.h
|-- cam_isp
| |-- k1x_isp_drv.c
| |-- k1x_isp_drv.h
| |-- k1x_isp_pipe.c
| |-- k1x_isp_pipe.h
| |-- k1x_isp_reg.c
| |-- k1x_isp_reg.h
| |-- k1x_isp_statistic.c
| `-- k1x_isp_statistic.h
|-- cam_plat
| |-- cam_plat.c
| `-- cam_plat.h
|-- cam_sensor
| |-- cam_sensor.c
| `-- cam_sensor.h
|-- cam_util
| |-- cam_dbg.c
| `-- cam_dbg.h
|-- Kconfig
|-- Makefile
|-- modules.order
`-- vi
|-- cam_block.c
|-- cam_block.h
|-- k1xvi
| |-- fe_isp.c
| |-- fe_isp.h
| |-- hw-seq
| | |-- hw_ccic.c
| | |-- hw_ccic.h
| | |-- hw_dma.c
| | |-- hw_dma.h
| | |-- hw_iommu.c
| | |-- hw_iommu.h
| | |-- hw_isp.c
| | |-- hw_isp.h
| | |-- hw_postpipe.c
| | |-- hw_postpipe.h
| | |-- hw_reg.h
| | `-- hw_reg_iommu.h
| |-- k1xvi.c
| `-- k1xvi.h
|-- mlink.c
|-- mlink.h
|-- spacemit_videobuf2.h
|-- subdev.c
|-- subdev.h
|-- vdev.c
|-- vdev.h
|-- vsensor.c
`-- vsensor.h
驱动配置
执行 make linux-menuconfig 命令进入 bianbu-linux 的内核配置,找到对应的宏配置,依次打开即可。
Symbol: SPACEMIT_K1X_CAMERA_V2 [=y] │
│ Type : tristate │
│ Defined at drivers/media/platform/spacemit/camera/Kconfig:8 │
│ Prompt: SPACEMIT K1X camera and video capture V2 support │
│ Depends on: MEDIA_SUPPORT [=y] && MEDIA_PLATFORM_SUPPORT [=y] && MEDIA_PLATFORM_DRIVERS [=y] │
│ Location: │
│ -> Device Drivers │
│ -> Multimedia support (MEDIA_SUPPORT [=y]) │
│ -> Media drivers │
│ -> Media platform devices (MEDIA_PLATFORM_DRIVERS [=y]) │
│ (1) -> SPACEMIT K1X camera and video capture V2 support (SPACEMIT_K1X_CAMERA_V2 [=y]) │
│ Selects: MEDIA_CONTROLLER [=y] && VIDEO_V4L2_SUBDEV_API [=y]
上面是 CONFIG_SPACEMIT_K1X_CAMERA_V2 配置的路径,选择打开之后,在把剩余的 camera 相关配置打开即可。打开并保存完毕后,可以通过输出保存的 ./output/k1/build/linux-custom/.config 文件确认,如下所示。
#
# SPACEMIT K1X Camera And Video V2
#
CONFIG_SPACEMIT_K1X_CAMERA_V2=y
CONFIG_SPACEMIT_K1X_CCIC_V2=y
CONFIG_SPACEMIT_K1X_VI_V2=y
CONFIG_SPACEMIT_K1X_VI_IOMMU=y
CONFIG_SPACEMIT_K1X_ISP_V2=y
CONFIG_SPACEMIT_K1X_CPP_V2=y
CONFIG_SPACEMIT_K1X_SENSOR_V2=y
Sensor 驱动
Sensor 相关的驱动代码位于 linux/drivers/media/platform/spacemit/camera/cam_sensor 目录,驱动加载后生成设备节点/dev/cam_sensorX(’X’为 sensor device ID,即下文提到的 camera ID)。
Sensor 驱动是个轻量级的字符串驱动,驱动内主要是来控制 power 以及封装 I2C 的 read/write 操作。在 bring up 时要特别注意:
-
camsnr_of_parse() 函数表明了当前支持解析的 sensor dts 节点中的属性内容。
-
cam_sensor_power_set()函数内定义的 power on/off 流程是否和新 sensor 操作流程一致。
- 如果操作一致,像 imx135 sensor。直接调用 ioctl CAM_SENSOR_UNRESET 即可,在 imx135_init 函数调用 sensor_hw_unreset 函数完成了上电操作,在 imx135_deinit 函数调用 sensor_hw_reset 函数完成了下电操作。
- 如果操作不一致,像 gc2375h sensor。需要结合 sensor 字符串驱动透出的 ioctl 接口,自定义 gc2375h_power_on 函数组合出满足 gc2375h sensor 上电要求的操作。下电同理。
(sensor 的上下电要求在 sensor datasheet 中有篇章说明)
DTS 介绍
和 camera 相关的 dts 配置主要分布在以下几个文件(方案间可能会有细微差别):
路径:archriscvbootdtsspacemitk1-x-camera-sensor.dtsi
作用:各类sensor的配置信息
路径:archriscvbootdtsspacemitk1-x-camera-sdk.dtsi
作用:ccic、csiphy、isp、vi、cpp的配置信息
路径:archriscvbootdtsspacemitk1-x_pinctrl.dtsi
作用:camera所依赖的pinctr1配置信息
路径:archriscvbootdtsspacemitk1-xxxx.dts
作用:不同方案的board相关配置
Pinctrl
目前仅有 camera mclk 引脚的配置是通过 pinctrl 进行定义的。
路径:archriscvbootdtsspacemitk1-x_pinctrl.dtsi
pinctrl_camera0: camera0_grp {
pinctrl-single,pins =<
K1X_PADCONF(GPIO_53, MUX_MODE1, (EDGE_NONE | PULL_DOWN | PAD_1V8_DS2)) /* cam_mclk0 */
>;
};
pinctrl_camera1: camera1_grp {
pinctrl-single,pins =<
K1X_PADCONF(GPIO_58, MUX_MODE1, (EDGE_NONE | PULL_DOWN | PAD_1V8_DS2)) /* cam_mclk1 */
>;
};
pinctrl_camera2: camera2_grp {
pinctrl-single,pins =<
K1X_PADCONF(GPIO_120, MUX_MODE1, (EDGE_NONE | PULL_DOWN | PAD_1V8_DS2)) /* cam_mclk2 */
>;
};
路径:archriscvbootdtsspacemitk1-x-camera-sensor.dtsi
/* imx315 */
backsensor: cam_sensor@0 {
......
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_camera0>;
......
status = "okay";
};
GPIO
查看开发板原理图,找到 mipi csi(0/1/2)硬件接口的复位信号 gpio 和上下电信号 gpio,通常至少会有一组 GPIO。假设 mipi csi0 硬件接口复位 gpio 为 gpio 111,上下电信号 gpio 为 gpio 113,且接入为 camera0:imx135 mipi。(建议 camera ID 和 mipi csi 的编号对应)
方案 dts 中 backsensor 配置如下。
路径:archriscvbootdtsspacemitk1-xxxx.dts
//为了提高效率,需要如下使用GPIO
//GPIO_111 后边的2表示连续要配置的GPIO,示例中表示GPIO_111,GPIO_112。
&pinctrl {
pinctrl-single,gpio-range = <
&range GPIO_111 2 (MUX_MODE0 | EDGE_NONE | PULL_DOWN | PAD_1V8_DS2)
>;
};
&gpio {
gpio-ranges = <
&pinctrl 111 GPIO_111 2
>;
};
/* imx315 */
&backsensor {
pwdn-gpios = <&gpio 113 0>;
reset-gpios = <&gpio 111 0>;
......
status = "okay";
};
路径:archriscvbootdtsspacemitk1-x-camera-sensor.dtsi
//camera ID0对应imx135
&soc {
/*imx315 */
backsensor: cam sensor@0 {
cell-index =<0>;
status ="okay";
};
};
pwdn-gpios,reset-gpios 跟 sensor 模组的供电配置有关,sensor 驱动中使用这组配置完成 sensor 的上、下电和 reset 操作。不同的 sensor 模组配置可能不一样,bring up 时需要仔细修改。
Sesnor dts 配置
k1-x-camera-sensor.dtsi 内定义的 sensor 配置如下
/* imx315 */
backsensor: cam_sensor@0 {
cell-index = <0>;
twsi-index = <0>;
dphy-index = <0>;
compatible = "spacemit,cam-sensor";
clocks = <&ccu CLK_CAMM0>;
clock-names = "cam_mclk0";
/* 该部分内容已经被移动到顶层的dts
afvdd28-supply = <&ldo_12>;
avdd28-supply = <&ldo_10>;
dvdd12-supply = <&ldo_20>;
iovdd18-supply = <&ldo_11>;
cam-supply-names = "afvdd28", "avdd28", "dvdd12", "iovdd18";
*/
......
status = "okay";
};
- cell-index 表示整个 sensor 所在的 device ID,这个 device ID 和上层使用的 sensor device ID 完全匹配
- twsi-index 表示 sensor 使用的 I2C core 的 ID,使用前要确保对应的 i2c bus dts 配置已经开启,具体请参阅 i2c 章节。
- dphy-index 表示 sensor 使用的 PHY ID。
- clocks/clock-names 表示 sensor 使用的 mclk 的时钟源。
Bring up 总结
在 bring up 过程中,对驱动的修改主要有以下几步:
- 如果是硬件平台当前位置的 sensor 模组第一次 bring up,则需要检查 k1-x--camera-sdk.dtsi
内定义的 csiphy 和 ccic 配置是否正确;如果不是,则本步骤可忽略。
- 修改 k1-x-camera-sensor.dtsi 或顶层的 dts 内定义的 sensor 配置适配新的 sensor。
- 检查 sensor 驱动中 power on/off 流程是否适配新的 sensor,如果不适配,可能需要修改 sensor 驱动。
User 层 cam_sensors 库
在 User 层,对于 sensor 模组的操作经过编译后,会封装在 libcam_sensors 库中,该模块源码目录位于 xxxx/package-src/k1x-cam/sensors。
公共操作
Sensor 的公共操作代码位于 sensor 子目录内,cam_sensor.c 文件定义的是所有 sensor 的 common 操作,该文件不用修改。xxx_sensor.c 对应的是某个具体型号 sensor 的 common 操作,如果 sensor 子目录下有待 bring up sensor 的 xxx_sensor.c 文件,则当前子目录不需要修改;如果没有的话,需要为新的 sensor 添加 xxx_sensor.c 文件。
我们对 xxx_sensor.c 文件定义的功能做简单说明。
操作函数集
文件定义 struct SENSOR_OBJ 结构体作为 sensor 对外的操作函数集。结构体包含以下成员变量:
- const char* name;
sensor 名字。
- int (pfnInit)(void* pHandle, int sns_id, uint8_t sns_addr);
sensor 初始化。
- int (pfnDeinit)(void handle);
sensor 清除初始化。
- int (pfnGloablConfig)(void handle, SENSOR_WORK_INFO_S* work_info);
根据输入的 work info 全局配置 sensor。
- int (pfnSetParam)(void handle, const SENSOR_INIT_ATTR_S* init_attr);
在初始化前设置 sensor 的 3A 参数。
- int (pfnStreamOn)(void handle);
sensor streamOn。
- int (pfnStreamOff)(void handle);
sensor streamOff。
- int (pfnGetSensorOps)(void handle, ISP_SENSOR_REGISTER_S* pSensorFuncOps);
获取 sensor 注册给 ISP 的回调操作函数集。
- int (pfnDetectSns)(void handle, SENSOR_VENDOR_ID_S* vendor_id);
Detect sensor。
- int (pfnWriteReg)(void handle, uint16_t regAddr, uint16_t value);
写寄存器。
- int (pfnReadReg)(void handle, uint16_t regAddr, uint16_t* value);
读寄存器。