1、先梳理下camera的驱动架构,如下是驱动展开后的代码目录
简单介绍下几个比较重要的文件:
inc->kd_imgsensor.h -----定义sensor id 和sensor name
src->mt6580->camera_hw->kd_camera_hw.c -----配置camera的上电时序
src->mt6580->kd_sensorlist.h------结合对应的sensor id和名字绑定对应的初始化函数
src->mt6580->kd_sensorlist.c -----camera驱动模块的加载,platform总线的注册
src->mt6580->xxx摄像头-----具体camera的驱动程序放置在这里
2、具体分析下kd_sensorlist.c文件和一个典型的camera驱动加载流程
//kd_sensorlist.c -----camera驱动模块的加载,platform总线的注册
//从后往前分析:
module_init(CAMERA_HW_i2C_init);
module_exit(CAMERA_HW_i2C_exit);
/*=======================================================================
* CAMERA_HW_i2C_init()
*=======================================================================*/
static int __init CAMERA_HW_i2C_init(void)
{
...
platform_device_register(&camerahw2_platform_device);
platform_driver_register(&g_stCAMERA_HW_Driver2)
proc_create("driver/camsensor", 0, NULL, &fcamera_proc_fops);//创建proc节点
...
}
static struct platform_driver g_stCAMERA_HW_Driver2 = {
.probe = CAMERA_HW_probe2,//device与driver的名字匹配到之后就会跑prob函数
.remove = CAMERA_HW_remove2,
.suspend = CAMERA_HW_suspend2,
.resume = CAMERA_HW_resume2,
.driver = {
.name = "image_sensor_bus2",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = CAMERA_HW2_of_ids,
#endif
}
};
static int CAMERA_HW_probe2(struct platform_device *pdev)
{
return i2c_add_driver(&CAMERA_HW_i2c_driver2);
}
struct i2c_driver CAMERA_HW_i2c_driver2 = {
.probe = CAMERA_HW_i2c_probe2,
.remove = CAMERA_HW_i2c_remove2,
.driver = {
.name = CAMERA_HW_DRVNAME2,
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = CAMERA_HW2_i2c_driver_of_ids,
#endif
},
.id_table = CAMERA_HW_i2c_id2,
};
/*******************************************************************************
* CAMERA_HW_i2c_probe
********************************************************************************/
static int CAMERA_HW_i2c_probe2(struct i2c_client *client, const struct i2c_device_id *id)
{
...
/* Register char driver */
i4RetValue = RegisterCAMERA_HWCharDrv2();
...
}
/*******************************************************************************
* RegisterCAMERA_HWCharDrv
********************************************************************************/
static inline int RegisterCAMERA_HWCharDrv2(void)
{
...
alloc_chrdev_region(&g_CAMERA_HWdevno2, 0, 1, "kd_camera_hw_bus2"))
major = MAJOR(g_CAMERA_HWdevno2);//得到主设备号
g_CAMERA_HWdevno2 = MKDEV(major, 0);//生成dev_t
/* Allocate driver */
g_pCAMERA_HW_CharDrv2 = cdev_alloc();//用于动态申请一个cdev内存
/* Attatch file operation. */
cdev_init(g_pCAMERA_HW_CharDrv2, &g_stCAMERA_HW_fops0);//初始化cdev的成员,并建立cdev和file_operation之间的联系
...
}
//对设备进行初始化,并将g_stCAMERA_HW_fops 这个文件操作函数作为上层对 Camera 设备操作的接口留给上层进 行调用:
static const struct file_operations g_stCAMERA_HW_fops0 = {
.owner = THIS_MODULE,
.open = CAMERA_HW_Open2,
.release = CAMERA_HW_Release2,
.unlocked_ioctl = CAMERA_HW_Ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = CAMERA_HW_Ioctl_Compat,
#endif
};
其中成员函数 open()只是初始化一个原子变量留给系统调用。ioctl()才是整个 Camera驱动的入口
/*******************************************************************************
* CAMERA_HW_Ioctl
********************************************************************************/
static long CAMERA_HW_Ioctl(struct file *a_pstFile,
unsigned int a_u4Command, unsigned long a_u4Param)
{
...
switch (a_u4Command) {
case KDIMGSENSORIOC_X_SET_DRIVER:
i4RetValue = kdSetDriver((unsigned int *)pBuff);
break;
case KDIMGSENSORIOC_T_OPEN:
i4RetValue = adopt_CAMERA_HW_Open();
break;
...
}
通过判断 Sensor 状态的逻辑值来进行具体的操作,对于这个值的定义在:
Mediatek\custom\common\kernel\imgsensor\inc\Kd_imgsensor.h 中
/* set sensor driver */
#define KDIMGSENSORIOC_X_SET_DRIVER _IOWR(IMGSENSORMAGIC, 35, SENSOR_DRIVER_INDEX_STRUCT)
// _IOWR()的用法参考其他博客(https://blog.csdn.net/u013256622/article/details/50922385)
...
分析各个函数的作用
/*******************************************************************************
* kdSetDriver
********************************************************************************/
int kdSetDriver(unsigned int *pDrvIndex)
{
ACDK_KD_SENSOR_INIT_FUNCTION_STRUCT *pSensorList = NULL;//定义init结构体
kdGetSensorInitFuncList(&pSensorList);// -->*ppSensorList = &kdSensorList[0];
pSensorList[drvIdx[i]].SensorInit(&g_pInvokeSensorFunc[i]);//执行init函数
/* get sensor name */
memcpy((char *)g_invokeSensorNameStr[i],
(char *)pSensorList[drvIdx[i]].drvname,
sizeof(pSensorList[drvIdx[i]].drvname));
/* return sensor ID */
/* pDrvIndex[0] = (unsigned int)pSensorList[drvIdx].SensorId; */
}
到这里,整个 Camera 驱动从总线注册到完成具体 sensor 的初始化的流程就完成了,
CAMERA_HW_Ioctl()中其他的 ioctl 操作函数最后都会在$sensor$_sensor.c 中实现。
3、举例一个具体驱动的实现,以ov8858为例子:
//以下这些函数就和之前的platform总线绑定起来了,整套流程就清晰起来了
static SENSOR_FUNCTION_STRUCT sensor_func = {
open, //打开之前会读sensor id如果正确识别就会继续跑下面的代码
get_info,
get_resolution,
feature_control,
control,
close
};
static kal_uint32 open(void)
{
...
/* sensor have two i2c address 0x6c 0x6d & 0x21 0x20, we should detect the module used i2c address */
while (imgsensor_info.i2c_addr_table[i] != 0xff) {
spin_lock(&imgsensor_drv_lock);
imgsensor.i2c_write_id = imgsensor_info.i2c_addr_table[i];
spin_unlock(&imgsensor_drv_lock);
do {
sensor_id = ((read_cmos_sensor(0x300B) << 8) | read_cmos_sensor(0x300C));
if (sensor_id == imgsensor_info.sensor_id) {
printk("i2c write id: 0x%x, sensor id: 0x%x\n",
imgsensor.i2c_write_id, sensor_id);
break;
}
printk("Read sensor id fail, i2c write id: 0x%x, sensor id: 0x%x\n\n",
imgsensor.i2c_write_id, sensor_id);
retry--;
} while (retry > 0);
i++;
if (sensor_id == imgsensor_info.sensor_id)
break;
retry = 2;
}
...
}
//UINT32 OV8858R2A_SensorInit(struct SENSOR_FUNCTION_STRUCT **pfFunc)
UINT32 OV8858_MIPI_RAW_SensorInit(PSENSOR_FUNCTION_STRUCT *pfFunc)
{
/* To Do : Check Sensor status here */
if (pfFunc != NULL)
*pfFunc = &sensor_func;
return ERROR_NONE;
} /* OV5693_MIPI_RAW_SensorInit */
4、camera模块的移植,了解了代码流程后,移植也就相对简单了。
(1) 项目上要兼容二、三供camera,拿到相应的资料及数据手册和驱动代码。
有两种情况:一是项目上已经做过相应的兼容;二是点亮全新的camera模组。
先去camera驱动代码目录下看有没有相关的代码,
(2) 如果有相关的驱动代码说明平台已经兼容过该cam,你主要在分支中进行相应的配置即可,主要配置projectconfig.mk文件和hct_kernel.h文件即可。配置如下,要进行驱动和lens的匹配:
先根据邮件确认要兼容的camera型号;
(4) 由sensor id到时匹配到对应的lens
(5) lens的驱动代码位置,文件夹的名字到时会进行相应的配置
(6) 在projectconfig.mk文件和hct_kernel.h文件添加相应配置;
点亮全新的camera模组:
刚说完,活就来了,要在8.1平台上移植一个新的摄像头,s5k3h5,要点亮新摄像头,发现8.1平台上并没有移植这个摄像头,于是参考其他人的提交。
:需要的文件如下;
1、驱动代码;
2、效果参数;
3、lens的代码准备移植相关的lens
4、一些配置文件的移植。
项目经理并没有提供相关的datasheet,问了其他同事说直接从9.0上的代码移植过来就行。
也没有相关的效果参数代码,从相近的其他摄像头的效果参数代码中进行移植,把相关的名字都改成本摄像头的名字。
然后配置相关的宏控和名字保证编译通过。
如下修改点:当我们点亮一颗新摄像头是提交到主干的。
由于9.0上的代码和8.1有点区别,刚开始编译会报很多的错误,不过不要着急,一步一步慢慢修改就行,大部分是一些变量定义了没有使用。修改了之后终于能保证编译通过了。
具体的修改点查看下我的提交即可,主要是上图列举的几大块部分。
注意:各个.h文件一定要添加完整,不然会导致驱动文件都没有调用到,工模里也不会有support休息。
注意当摄像头黑屏时,又没有检查出其他问题时,可以先排除下是不是摄像头的问题,换一个试下,或把这个摄像头接到其他用到该摄像头的项目看能不能亮,我检查了两天没有进展,最后换摄像头时,摄像头拔断了,才想到换摄像头试下,结果居然就亮了,(⊙o⊙)…,顿时心情难以言喻,不过这也让学会了一些调试的方法,加自己的log打印调试,看代码跑了哪些地方。