1. Camera Kernel 驱动
- msm.c: 所有qcom camera相关设备的管理,提供session、stream、event、vb2_queue等接口的创建和删除
- camera/:用于控制sensor,提供了ioctl_ops,如启动流,qbuf和dqbuf,subscribe_event等
- sensor/:具体的硬件驱动,例如sensor、eeprom、actuator、ois等
从代码结构上看 分为camera 和sensor . 从框架把Camera系统分为 msm-config 和 msm-sensor 两部分:
msm-config:用于创建v4l2_device,用于管理所有的子设备,同时生成/dev/video0设备节点,暴露控制接口给用户空间使用,主要包括了session/stream的创建与销毁,event事件的分发,另外,还创建了media_device,用于暴露枚举接口给用户来轮询查找整个内核的子设备。
节点:/sys/class/video4linux/video1
相关源码:kernel/msm-4.9/drivers/media/platform/msm/camera_v2/msm.c
创建的节点 :/vidoe*/msm-config
strlcpy(pvdev->vdev->name, "msm-config", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &msm_fops;
pvdev->vdev->ioctl_ops = &g_msm_ioctl_ops;
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
rc = video_register_device(pvdev->vdev,
VFL_TYPE_GRABBER, -1);
创建的节点 :/media*/msm_config
media_device_init(msm_v4l2_dev->mdev);
strlcpy(msm_v4l2_dev->mdev->model, MSM_CONFIGURATION_NAME, //MSM_CONFIGURATION_NAME=msm_config
sizeof(msm_v4l2_dev->mdev->model));
msm_v4l2_dev->mdev->dev = &(pdev->dev);
rc = media_device_register(msm_v4l2_dev->mdev);
"kernel/msm-4.19/drivers/media/platform/msm/camera_v2/camera/camera.c"
/vidoe*/msm-camera
strlcpy(pvdev->vdev->name, "msm-sensor", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &camera_v4l2_fops;
pvdev->vdev->ioctl_ops = &camera_v4l2_ioctl_ops;
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
pvdev->vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
rc = video_register_device(pvdev->vdev,
VFL_TYPE_GRABBER, -1);
msm-sensor:创建一个v412_device,用于管理sensor,比如sensor 的初始化,启动流,关闭流,图像buf的管理等,同时创建video3/video4设备节点暴露接口给用户空间进行访问。
节点:/sys/class/video4linux/video3 /sys/class/video4linux/video4
相关源码:kernel/msm-4.19/drivers/media/platform/msm/camera_v2/camera/camera.c
/vidoe*/msm-camera
strlcpy(pvdev->vdev->name, "msm-sensor", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &camera_v4l2_fops;
pvdev->vdev->ioctl_ops = &camera_v4l2_ioctl_ops;
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
pvdev->vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
rc = video_register_device(pvdev->vdev,
VFL_TYPE_GRABBER, -1);
/media*/msm_camera 下的节点
strlcpy(v4l2_dev->mdev->model, MSM_CAMERA_NAME, //MSM_CAMERA_NAME=msm_camera
sizeof(v4l2_dev->mdev->model));
v4l2_dev->mdev->dev = dev;
rc = media_device_register(v4l2_dev->mdev);
if (WARN_ON(rc < 0))
goto media_fail;
本文分析camera 部分, 和 sensor dts 解析部分.
-
camera
通过解析compatible = “qcom,msm-cam”;来初始化并注册好media_device 、v4l2_device、video_device 设备,同时生成/dev/media0节点。 -
Sensor 部分
通过解析compatible = “qcom,camera”;来初始化调用probe解析camera sensor的dts节点信息,保存在全局g_sctrl 数组中。
然后,上层在初始化时,依次对每个sensor下发 ioctl 参数,触发其作初始化probe ,上电check_sensor_id 及 创建对应的 /dev/videoX 节点 及 /dev/mediaX 的节点
2. msm-cam驱动
msm-cam是在dts 中定义的,
项目 xxx.dts 包含->
kernel/msm-4.19/arch/arm64/boot/dts/vendor/qcom/vendor/qcom/sdm660.dtsi ->
kernel/msm-4.19/arch/arm64/boot/dts/vendor/qcom/sdm660-camera.dtsi
qcom,msm-cam@ca00000 {
compatible = "qcom,msm-cam";
reg = <0xca00000 0x4000>;
reg-names = "msm-cam";
status = "ok";
bus-vectors = "suspend", "svs", "nominal", "turbo";
qcom,bus-votes = <0 150000000 320000000 320000000>;
qcom,gpu-limit = <700000000>;
};
kernel/msm-4.19/drivers/media/platform/msm/camera_v2/msm.c
static int msm_probe(struct platform_device *pdev)
{
struct msm_video_device *pvdev = NULL;
static struct dentry *cam_debugfs_root;
int rc = 0;
// 1. 初始化一个 v4l2_device 类型的结构体,并分配好结构体内存
msm_v4l2_dev = kzalloc(sizeof(*msm_v4l2_dev),
GFP_KERNEL);
if (WARN_ON(!msm_v4l2_dev)) {
rc = -ENOMEM;
goto probe_end;
}
pvdev = kzalloc(sizeof(struct msm_video_device),
GFP_KERNEL);
if (WARN_ON(!pvdev)) {
rc = -ENOMEM;
goto pvdev_fail;
}
// 2. 分配 video_device 结构体内存
pvdev->vdev = video_device_alloc();
if (WARN_ON(!pvdev->vdev)) {
rc = -ENOMEM;
goto video_fail;
}
#if defined(CONFIG_MEDIA_CONTROLLER)
// 3. 分配 media_device 结构体内存
msm_v4l2_dev->mdev = kzalloc(sizeof(struct media_device),
GFP_KERNEL);
if (!msm_v4l2_dev->mdev) {
rc = -ENOMEM;
goto mdev_fail;
}
// 4.初始化media_device
media_device_init(msm_v4l2_dev->mdev);
strlcpy(msm_v4l2_dev->mdev->model, MSM_CONFIGURATION_NAME,sizeof(msm_v4l2_dev->mdev->model)); //MSM_CONFIGURATION_NAME = "msm_config"
msm_v4l2_dev->mdev->dev = &(pdev->dev);
// 5. 注册 media_device , 使用的 v4l2
rc = media_device_register(msm_v4l2_dev->mdev);
/**
media_device_register()
media_devnode_register ()
device_initialize() //初始化media 创建mediaX
cdev_init() //初始化字符设备
cdev_device_add() //
device_create_file(&devnode->dev, &dev_attr_model) // 创建的节点 sys/devices/platform/soc/ca00000.qcom,msm-cam/media0/model
节点:/sys/bus/media/devices/media0 # cat model
msm_config
上层可以通过IOCTL指令:MEDIA_IOC_ENUM_ENTITIES,枚举所有的media设备,然后根据设备名称msm_config找到的/dev/video0节点。
*/
if (WARN_ON(rc < 0))
goto media_fail;
if (WARN_ON((rc == media_entity_pads_init(&pvdev->vdev->entity,
0, NULL)) < 0))
goto entity_fail;
pvdev->vdev->entity.function = QCAMERA_VNODE_GROUP_ID;
#endif
msm_v4l2_dev->notify = msm_sd_notify;
pvdev->vdev->v4l2_dev = msm_v4l2_dev;
// 6. 设置父设备为 pdev->dev (也就是 qcom,msm-cam 的设备信息)
rc = v4l2_device_register(&(pdev->dev), pvdev->vdev->v4l2_dev);
/**
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
if (v4l2_dev == NULL)
return -EINVAL;
INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
get_device(dev);
v4l2_dev->dev = dev;
if (dev == NULL) {
/* If dev == NULL, then name must be filled in by the caller */
if (WARN_ON(!v4l2_dev->name[0]))
return -EINVAL;
return 0;
}
/* Set name to driver name + device name if it is empty. */
if (!v4l2_dev->name[0])
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
dev->driver->name, dev_name(dev));
printk("v4l2_dev->name =%s \n",v4l2_dev->name); //v4l2_dev->name =msm ca00000.qcom,msm-cam
if (!dev_get_drvdata(dev))
dev_set_drvdata(dev, v4l2_dev);
return 0;
}
*/
if (WARN_ON(rc < 0))
goto register_fail;
// 7. 注册 video_device设备
strlcpy(pvdev->vdev->name, "msm-config", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &msm_fops; //绑定fops
pvdev->vdev->ioctl_ops = &g_msm_ioctl_ops;
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
rc = video_register_device(pvdev->vdev,
VFL_TYPE_GRABBER, -1); // 节点 /dev/vdieoX
/**
video_register_device()
device_register(&vdev->dev); //注册video 的节点
/sys/class # cat video4linux/video1/name
msm-config
*/
if (WARN_ON(rc < 0))
goto v4l2_fail;
#if defined(CONFIG_MEDIA_CONTROLLER)
/* FIXME: How to get rid of this messy? */
pvdev->vdev->entity.name = video_device_node_name(pvdev->vdev);
#endif
atomic_set(&pvdev->opened, 0);
// 8. 将当前 msm_video_device 结构体设为私有数据
video_set_drvdata(pvdev->vdev, pvdev);
msm_session_q = kzalloc(sizeof(*msm_session_q), GFP_KERNEL);
if (WARN_ON(!msm_session_q))
goto v4l2_fail;
// 9. 分配 msm_queue_head 结构体内存
msm_init_queue(msm_session_q);
spin_lock_init(&msm_eventq_lock);
spin_lock_init(&msm_pid_lock);
mutex_init(&ordered_sd_mtx);
mutex_init(&v4l2_event_mtx);
INIT_LIST_HEAD(&ordered_sd_list);
// 10. 创建 camera 调试目录
cam_debugfs_root = debugfs_create_dir(MSM_CAM_LOGSYNC_FILE_BASEDIR,
NULL);
if (!cam_debugfs_root) {
pr_warn("NON-FATAL: failed to create logsync base directory\n");
} else {
if (!debugfs_create_file(MSM_CAM_LOGSYNC_FILE_NAME,
0660,
cam_debugfs_root,
NULL,
&logsync_fops))
pr_warn("NON-FATAL: failed to create logsync debugfs file\n");
}
rc = cam_ahb_clk_init(pdev);
if (rc < 0) {
pr_err("%s: failed to register ahb clocks\n", __func__);
goto v4l2_fail;
}
of_property_read_u32(pdev->dev.of_node,
"qcom,gpu-limit", &gpu_limit);
goto probe_end;
v4l2_fail:
v4l2_device_unregister(pvdev->vdev->v4l2_dev);
register_fail:
#if defined(CONFIG_MEDIA_CONTROLLER)
media_entity_cleanup(&pvdev->vdev->entity);
entity_fail:
media_device_unregister(msm_v4l2_dev->mdev);
media_fail:
kzfree(msm_v4l2_dev->mdev);
mdev_fail:
#endif
video_device_release(pvdev->vdev);
video_fail:
kfree(pvdev);
pvdev_fail:
kzfree(msm_v4l2_dev);
probe_end:
return rc;
}
static struct v4l2_file_operations msm_fops = {
.owner = THIS_MODULE,
.open = msm_open,
.poll = msm_poll,
.release = msm_close,
.unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = video_ioctl2,
#endif
};
static const struct v4l2_ioctl_ops g_msm_ioctl_ops = {
.vidioc_subscribe_event = msm_subscribe_event,
.vidioc_unsubscribe_event = msm_unsubscribe_event,
.vidioc_default = msm_private_ioctl,
};
v4l2_file_operations:
* msm_open:创建event队列
* msm_poll:轮询event队列
* msm_close:释放相关资源
v4l2_ioctl_ops:
* msm_subscribe_event:订阅事件
* msm_unsubscribe_event:注销事件
3. sensor驱动
kernel/msm-4.19/drivers/media/platform/msm/camera_v2/sensor/msm_sensor_driver.c
msm_sensor_driver_platform_probe()
struct msm_sensor_ctrl_t *s_ctrl;
msm_sensor_driver_parse() //解析dts 内容
msm_sensor_driver_get_dt_data(s_ctrl); //
msm_sensor_driver_get_dt_data(s_ctrl); //
struct msm_camera_sensor_board_info *sensordata // 结构体msm_camera_sensor_board_info
of_property_read_u32(of_node, "cell-index", &cell_id); // cell_id 即为camera id
msm_sensor_get_sub_module_index() //获取马达 其他子设备的信息,包括 "qcom,actuator-src"、"qcom,ois-src"、"qcom,eeprom-src"、
msm_camera_get_dt_vreg_data() //获取供电信息
msm_sensor_driver_get_gpio_data() //读取dts 配置的gpio信息
msm_sensor_init_default_params(s_ctrl) // // 初始化默认参数
struct msm_sensor_ctrl_t *s_ctrl;
struct msm_camera_cci_client *cci_client;
s_ctrl->sensor_i2c_client->i2c_func_tbl = &msm_sensor_cci_func_tbl; //更新i2c function table
/**
static struct msm_camera_i2c_fn_t msm_sensor_cci_func_tbl = {
.i2c_read = msm_camera_cci_i2c_read,
.i2c_read_seq = msm_camera_cci_i2c_read_seq,
.i2c_write = msm_camera_cci_i2c_write,
.i2c_write_table = msm_camera_cci_i2c_write_table,
.i2c_write_seq_table = msm_camera_cci_i2c_write_seq_table,
.i2c_write_table_w_microdelay =
msm_camera_cci_i2c_write_table_w_microdelay,
.i2c_util = msm_sensor_cci_i2c_util,
.i2c_write_conf_tbl = msm_camera_cci_i2c_write_conf_tbl,
.i2c_write_table_async = msm_camera_cci_i2c_write_table_async,
.i2c_write_table_sync = msm_camera_cci_i2c_write_table_sync,
.i2c_write_table_sync_block = msm_camera_cci_i2c_write_table_sync_block,
};
*/
s_ctrl->func_tbl = &msm_sensor_func_tbl; //更新ioctrl 的function table
/**
static struct msm_sensor_fn_t msm_sensor_func_tbl = {
.sensor_config = msm_sensor_config,
#ifdef CONFIG_COMPAT
.sensor_config32 = msm_sensor_config32,
#endif
.sensor_power_up = msm_sensor_power_up,
.sensor_power_down = msm_sensor_power_down,
.sensor_match_id = msm_sensor_match_id,
};
*/
s_ctrl->sensor_v4l2_subdev_ops = &msm_sensor_subdev_ops; //更新v4l2 ops table
/**
static struct v4l2_subdev_core_ops msm_sensor_subdev_core_ops = {
.ioctl = msm_sensor_subdev_ioctl,
.s_power = msm_sensor_power,
};
static struct v4l2_subdev_ops msm_sensor_subdev_ops = {
.core = &msm_sensor_subdev_core_ops,
};
*/
g_sctrl[s_ctrl->id] = s_ctrl; // s_ctrl 初始化的值给全局变量 static struct msm_sensor_ctrl_t *g_sctrl[MAX_CAMERAS];
msm_camera_get_clk_info() // 获取clock 信息 dts 中配置的camera clk 信息 clock-names = "cam_src_clk", "cam_clk";
g_sctrl[s_ctrl->id] = s_ctrl; // s_ctrl 初始化的值给全局变量 static struct msm_sensor_ctrl_t *g_sctrl[MAX_CAMERAS];
全局CameraSensorCtrol数组g_sctrl
g_sctrl 是静态全局的一个msm_sensor_ctrl_t * 结构体指针数组,其定义如下:
static struct msm_sensor_ctrl_t *g_sctrl[MAX_CAMERAS];
前面我们在配置dts 时也发现了,通常我们要配置的camera数量是大于1的,前面代码中,我们配置了3个Camera,两个后摄,一个前摄。而这三个camera dts 中的节点都是一样的"qcom,camera"。
可以看出,驱动和设备会匹配三次,换句话说,也就是 msm_sensor_driver_platform_probe()函数会走三次,每次传递的dts节点内容是不一样的,三个camera都会依次probe 一次,
从而,当probe 完毕后,会保存三个struct msm_sensor_ctrl_t *结构体的数据保存在全局 g_sctrl 中。
至此,我们代码中,就把dts 的内容成功的转化为了msm_sensor_ctrl_t结构体保存在 全局 g_sctrl 中。
4. 总结
msm_sensor_driver_platform_probe(),这个函数主要作用还是解析DTS,但并不会真正probe camera sensor。
那问题来了,camera sensor probe 是在什么时候呢?
其实,camera probe 并不是和其他kernerl 驱动一样,在初始化时就probe,而是通过hal 层下发 probe 指令来控制probe 的。详细的看下高通mm-camera_vendor . hal 层下发指令到vendor , vednor 调用到kenel .
VIDIOC_MSM_SENSOR_INIT_CFG -> msm_sensor_driver_cmd() //“kernel/msm-4.19/drivers/media/platform/msm/camera_v2/sensor/msm_sensor_init.c”