在学习rk3399看到特别好的文章,这边进行转载记录一下,以防遗忘:
CSDN博主「ontheway_zero」的原文链接:RK3399教程: camera

名词解释

在现代移动设备中,常用一种接口用来连接SOC和LCD和Camera,这种接口就是MIPI。
其中SOC和LCD连接叫 DSI(DisplayCommandSet),SOC和Camera连接叫CSI(CameraSerialInterface)。

硬件连接


上图参考原文图片。
一般情况下,Camera和SOC有两个接口进行连接,分为为MIPI接口和I2C接口,其中MIPI接口用来传输图像的数据,数据传输路径为从Sensor传输到SOC。另一个接口为I2C接口,主要是用来SOC对Sensor初始化配置寄存器和摄像头参数的配置,比如要进行图像数据捕获的时候就需要通过i2c对Sensor的寄存器进行配置。

图像数据路径



由上面的两个图可以看到,光线经过Sensor之后,Sensor芯片经过ADC转换生成图像数据,然后Sensor生成的图像数据经过MIPI总线进入SOC,进入SOC之后经过ISP进行图像处理。所以由此可以可以看出,Camera驱动V4L2一定有这3部分组成,第一部分与Sensor相关的,比如控制Sensor的寄存器进行配置,这一部分是有Sensor厂家提供。第二部分和MIPI相关的,需要MIPI进行图像传输,所以驱动应该就有这一部分的驱动,这部分一般是由SOC厂家提供。第三部分就是ISP部分,有些SOC有ISP图像处理模块,经过MIPI传输的图像进入SOC之后需要在传入SOC的ISP模块对图像进一步进行加工,所以一定是有一部分驱动是描述ISP模块的。

代码路径

和硬件相关的驱动有3部分,分别为Sensor相关的,MIPI相关,ISP相关的。
Sensor: \kernel\drivers\media\i2c\ov13850.c
MIPI相关:\kernel\drivers\phy\rockchip\phy-rockchip-mipi-rx.c
ISP相关:\kernel\drivers\media\platform\rockchip\isp1\rkisp1.c

dts

我们已经知道Camera有3部分的驱动,分别是描述Sensor、MIPI相关、ISP相关的,所以在dts中也有描述这3部分的。
下面是和摄像头相关的dts

//这部分是根Senser相关的
&i2c1 {status = "okay";ov13850: ov13850@10 {compatible = "ovti,ov13850";status = "disabled";reg = <0x10>;                     //i2c地址clocks = <&cru SCLK_CIF_OUT>;clock-names = "xvclk";/* avdd-supply = <>; *//* dvdd-supply = <>; *//* dovdd-supply = <>; *//* reset-gpios = <>; */reset-gpios = <&gpio2 10 GPIO_ACTIVE_HIGH>;   //复位脚配置pwdn-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;     //power down脚配置pinctrl-names = "rockchip,camera_default";pinctrl-0 = <&cif_clkout>;rockchip,camera-module-index = <0>;           //其他的配置功能可以查看rochchip文档rockchip,camera-module-facing = "back";rockchip,camera-module-name = "CMK-CT0116";rockchip,camera-module-lens-name = "Largan-50013A1";lens-focus = <&vm149c>;port {ucam_out0: endpoint {remote-endpoint = <&mipi_in_ucam0>;  //Sensor连接到mipi//remote-endpoint = <&mipi_in_ucam1>;data-lanes = <1 2>;};};};
};//这部分是和mipi相关
&mipi_dphy_rx0 {status = "disabled";ports {#address-cells = <1>;#size-cells = <0>;//mipi有两端一段连接Sensor,另一端连接ISPport@0 {reg = <0>;#address-cells = <1>;#size-cells = <0>;//连接到Sensormipi_in_ucam0: endpoint@1 {reg = <1>;remote-endpoint = <&ucam_out0>;data-lanes = <1 2>;};};port@1 {reg = <1>;#address-cells = <1>;#size-cells = <0>;//连接到ISPdphy_rx0_out: endpoint@0 {reg = <0>;remote-endpoint = <&isp0_mipi_in>;};};};
};//这部分是和ISP相关的
&rkisp1_0 {status = "disabled";port {#address-cells = <1>;#size-cells = <0>;//连接到MIPIisp0_mipi_in: endpoint@0 {reg = <0>;remote-endpoint = <&dphy_rx0_out>;};};
};

从dts我们可以看出Sensor和mipi和isp的连接关系,其中Sensor连接到mipi,然后mipi连接到isp。
ucam_out0 ->mipi_in_ucam0->dphy_rx0_out->isp0_mipi_in。

查看Sensor驱动ov13850.c

根据dts可以知道Sensor是挂载在i2c下的,根据dts的使用方法在ov13850.c一定会有一个struct i2c_driver,然后根据设备树的匹配规则,compatible = “ovti,ov13850”;和.of_match_table = of_match_ptr(ov13850_of_match), 进行匹配,然后i2c_driver里面的probe函数被调用,然后进入这个函数ov13850_probe,我们主要是查看ov13850_probe这个函数做了什么。

static struct i2c_driver ov13850_i2c_driver = {.driver = {.name = OV13850_NAME,.pm = &ov13850_pm_ops,.of_match_table = of_match_ptr(ov13850_of_match),},.probe      = &ov13850_probe,   //函数重要函数入口.remove      = &ov13850_remove,.id_table    = ov13850_match_id,
};//完整代码,完整代码主要是解析dts里面的数据,获取reset脚等和硬件相关的配置,可以不用看,硬件操作我们不关心,因为不同的Sensor会有不同的硬件操作。
static int ov13850_probe(struct i2c_client *client,const struct i2c_device_id *id)
{struct device *dev = &client->dev;struct device_node *node = dev->of_node;struct ov13850 *ov13850;struct v4l2_subdev *sd;char facing[2];int ret;dev_info(dev, "driver version: %02x.%02x.%02x",DRIVER_VERSION >> 16,(DRIVER_VERSION & 0xff00) >> 8,DRIVER_VERSION & 0x00ff);ov13850 = devm_kzalloc(dev, sizeof(*ov13850), GFP_KERNEL);if (!ov13850)return -ENOMEM;ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,&ov13850->module_index);ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING,&ov13850->module_facing);ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME,&ov13850->module_name);ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME,&ov13850->len_name);if (ret) {dev_err(dev, "could not get module information!\n");return -EINVAL;}ov13850->client = client;ov13850->cur_mode = &supported_modes[0];ov13850->xvclk = devm_clk_get(dev, "xvclk");if (IS_ERR(ov13850->xvclk)) {dev_err(dev, "Failed to get xvclk\n");return -EINVAL;}ret = clk_set_rate(ov13850->xvclk, OV13850_XVCLK_FREQ);if (ret < 0) {dev_err(dev, "Failed to set xvclk rate (24MHz)\n");return ret;}if (clk_get_rate(ov13850->xvclk) != OV13850_XVCLK_FREQ)dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n");ov13850->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);if (IS_ERR(ov13850->reset_gpio))dev_warn(dev, "Failed to get reset-gpios\n");ov13850->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW);if (IS_ERR(ov13850->pwdn_gpio))dev_warn(dev, "Failed to get pwdn-gpios\n");ret = ov13850_configure_regulators(ov13850);if (ret) {dev_err(dev, "Failed to get power regulators\n");return ret;}ov13850->pinctrl = devm_pinctrl_get(dev);if (!IS_ERR(ov13850->pinctrl)) {ov13850->pins_default =pinctrl_lookup_state(ov13850->pinctrl,OF_CAMERA_PINCTRL_STATE_DEFAULT);if (IS_ERR(ov13850->pins_default))dev_err(dev, "could not get default pinstate\n");ov13850->pins_sleep =pinctrl_lookup_state(ov13850->pinctrl,OF_CAMERA_PINCTRL_STATE_SLEEP);if (IS_ERR(ov13850->pins_sleep))dev_err(dev, "could not get sleep pinstate\n");}mutex_init(&ov13850->mutex);sd = &ov13850->subdev;v4l2_i2c_subdev_init(sd, client, &ov13850_subdev_ops);ret = ov13850_initialize_controls(ov13850);if (ret)goto err_destroy_mutex;ret = __ov13850_power_on(ov13850);if (ret)goto err_free_handler;ret = ov13850_check_sensor_id(ov13850, client);if (ret)goto err_power_off;#ifdef CONFIG_VIDEO_V4L2_SUBDEV_APIsd->internal_ops = &ov13850_internal_ops;sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
#endif
#if defined(CONFIG_MEDIA_CONTROLLER)ov13850->pad.flags = MEDIA_PAD_FL_SOURCE;sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR;ret = media_entity_init(&sd->entity, 1, &ov13850->pad, 0);if (ret < 0)goto err_power_off;
#endifmemset(facing, 0, sizeof(facing));if (strcmp(ov13850->module_facing, "back") == 0)facing[0] = 'b';elsefacing[0] = 'f';snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",ov13850->module_index, facing,OV13850_NAME, dev_name(sd->dev));ret = v4l2_async_register_subdev_sensor_common(sd);if (ret) {dev_err(dev, "v4l2 async register subdev failed\n");goto err_clean_entity;}pm_runtime_set_active(dev);pm_runtime_enable(dev);pm_runtime_idle(dev);return 0;err_clean_entity:
#if defined(CONFIG_MEDIA_CONTROLLER)media_entity_cleanup(&sd->entity);
#endif
err_power_off:__ov13850_power_off(ov13850);
err_free_handler:v4l2_ctrl_handler_free(&ov13850->ctrl_handler);
err_destroy_mutex:mutex_destroy(&ov13850->mutex);return ret;
}//上面的函数精简版
static int ov13850_probe(struct i2c_client *client,const struct i2c_device_id *id)
{//这个prob函数最重要的功能就是下面这两个函数。//v4l2框架将Sensor统一描述为 struct v4l2_subdev对象,这个对象里面有硬件相关的操作函数,这函数描述的对象为struct v4l2_subdev_ops,由下面的函数可以知道我们将Sensor当成一个对象sd,ov13850_subdev_ops是Sensor操作硬件的函数。struct v4l2_subdev_ops可以指向struct v4l2_subdev_ops,所以应用就可以通过ioctrl找到Sensor这个实体,然后找到硬件操作函数,从而控制寄存器的配置。v4l2_i2c_subdev_init(sd, client, &ov13850_subdev_ops);//最重要的函数,将Sensor对象sd,添加到框架的链表当中 ret = v4l2_async_register_subdev_sensor_common(sd);
}//和Sensor相关的操作函数,最后是通过ioctrl一层一层的调用到这里,
这里面的操作函数对于不同的Sensor不一定全部相同,有些Sensor的功能多一点可能操作函数就多一点。
static const struct v4l2_subdev_ops ov13850_subdev_ops = {.core    = &ov13850_core_ops,   //对Sensor控制的核心操作函数.video    = &ov13850_video_ops,  //录像的时候控制的操作函数.pad  = &ov13850_pad_ops,
};static const struct v4l2_subdev_core_ops ov13850_core_ops = {.s_power = ov13850_s_power,.ioctl = ov13850_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl32 = ov13850_compat_ioctl32,
#endif
};static const struct v4l2_subdev_video_ops ov13850_video_ops = {.s_stream = ov13850_s_stream,.g_frame_interval = ov13850_g_frame_interval,
};static const struct v4l2_subdev_pad_ops ov13850_pad_ops = {.enum_mbus_code = ov13850_enum_mbus_code,.enum_frame_size = ov13850_enum_frame_sizes,.get_fmt = ov13850_get_fmt,.set_fmt = ov13850_set_fmt,
};

查看MIPI相关驱动phy-rockchip-mipi-rx.c

MIPI相关的驱动主要是用来配置MIPI接口的寄存器,然后在使能接收MIPI接口等功能。其入口其实是和Sensor差不多的,只不过是使用struct platform_driver而已与struct i2c_driver类似,最后还是调用probe函数。

//MIPI相关驱动的入口函数rockchip_mipidphy_probe
static struct platform_driver rockchip_isp_mipidphy_driver = {.probe = rockchip_mipidphy_probe,.remove = rockchip_mipidphy_remove,.driver = {.name = "rockchip-mipi-dphy-rx",.pm = &rockchip_mipidphy_pm_ops,.of_match_table = rockchip_mipidphy_match_id,},
};//全部代码,下面会精简代码
static int rockchip_mipidphy_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;struct v4l2_subdev *sd;struct mipidphy_priv *priv;struct regmap *grf;struct resource *res;const struct of_device_id *of_id;const struct dphy_drv_data *drv_data;int i, ret;priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);if (!priv)return -ENOMEM;priv->dev = dev;of_id = of_match_device(rockchip_mipidphy_match_id, dev);if (!of_id)return -EINVAL;grf = syscon_node_to_regmap(dev->parent->of_node);if (IS_ERR(grf)) {grf = syscon_regmap_lookup_by_phandle(dev->of_node,"rockchip,grf");if (IS_ERR(grf)) {dev_err(dev, "Can't find GRF syscon\n");return -ENODEV;}}priv->regmap_grf = grf;drv_data = of_id->data;for (i = 0; i < drv_data->num_clks; i++) {priv->clks[i] = devm_clk_get(dev, drv_data->clks[i]);if (IS_ERR(priv->clks[i]))dev_dbg(dev, "Failed to get %s\n", drv_data->clks[i]);}priv->grf_regs = drv_data->grf_regs;priv->txrx_regs = drv_data->txrx_regs;priv->csiphy_regs = drv_data->csiphy_regs;priv->drv_data = drv_data;if (drv_data->ctl_type == MIPI_DPHY_CTL_CSI_HOST) {res = platform_get_resource(pdev, IORESOURCE_MEM, 0);priv->csihost_base_addr = devm_ioremap_resource(dev, res);priv->stream_on = csi_mipidphy_stream_on;priv->stream_off = csi_mipidphy_stream_off;} else {priv->stream_on = mipidphy_txrx_stream_on;priv->txrx_base_addr = NULL;res = platform_get_resource(pdev, IORESOURCE_MEM, 0);priv->txrx_base_addr = devm_ioremap_resource(dev, res);if (IS_ERR(priv->txrx_base_addr))priv->stream_on = mipidphy_rx_stream_on;priv->stream_off = NULL;}sd = &priv->sd;v4l2_subdev_init(sd, &mipidphy_subdev_ops);sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-dphy-rx");sd->dev = dev;platform_set_drvdata(pdev, &sd->entity);ret = rockchip_mipidphy_media_init(priv);if (ret < 0)return ret;pm_runtime_enable(&pdev->dev);drv_data->individual_init(priv);return 0;
}static int rockchip_mipidphy_media_init(struct mipidphy_priv *priv)
{int ret;priv->pads[MIPI_DPHY_RX_PAD_SOURCE].flags =MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT;priv->pads[MIPI_DPHY_RX_PAD_SINK].flags =MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;ret = media_entity_init(&priv->sd.entity,MIPI_DPHY_RX_PADS_NUM, priv->pads, 0);if (ret < 0)return ret;ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(priv->dev, &priv->notifier,sizeof(struct sensor_async_subdev), 0,rockchip_mipidphy_fwnode_parse);if (ret < 0)return ret;if (!priv->notifier.num_subdevs)return -ENODEV;    /* no endpoint */priv->sd.subdev_notifier = &priv->notifier;priv->notifier.ops = &rockchip_mipidphy_async_ops;ret = v4l2_async_subdev_notifier_register(&priv->sd, &priv->notifier);if (ret) {dev_err(priv->dev,"failed to register async notifier : %d\n", ret);v4l2_async_notifier_cleanup(&priv->notifier);return ret;}return v4l2_async_register_subdev(&priv->sd);
}static int rockchip_mipidphy_probe(struct platform_device *pdev)
{//分配一个struct v4l2_subdev *sd;//将mipidphy_subdev_ops放置到struct v4l2_subdev *sd//大家看到这里应该就明白了,不管是Sensor还是MIPI相关的还是ISP相关的,在V4L2的架构中,都会描述为一个对象struct v4l2_subdev,这个对象里面有对硬件操作的函数struct v4l2_subdev_ops,然后在将这个对象struct v4l2_subde注册到v4l2_async_register_subdev,从而注册到V4L2的框架的链表当中。然后将Sensor和MIPI和ISP绑定起来,形成一个完整的Camera驱动。v4l2_subdev_init(sd, &mipidphy_subdev_ops);//将port绑定起来,还记得我们dts上面的port么?就是将Sensor和MIPI和ISP绑定起来。ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(priv->dev, &priv->notifier,sizeof(struct sensor_async_subdev), 0,rockchip_mipidphy_fwnode_parse);//将对象struct v4l2_subdev注册到V4L2框架中。v4l2_async_register_subdev(&priv->sd);

查看ISP相关驱动rkisp1.c

//入口ISP函数rkisp1_plat_probe
static struct platform_driver rkisp1_plat_drv = {.driver = {.name = DRIVER_NAME,.of_match_table = of_match_ptr(rkisp1_plat_of_match),.pm = &rkisp1_plat_pm_ops,},.probe = rkisp1_plat_probe,.remove = rkisp1_plat_remove,
};//下面是isp代码从入口到注册到v4l2_device_register_subdev流程,原理和上面的Sensor类似,都是分配struct v4l2_subdev对象,然后注册到V4L2的架构当中。
rkisp1_plat_proberet = rkisp1_register_platform_subdevs(isp_dev);ret = rkisp1_register_isp_subdev(dev, &dev->v4l2_dev);v4l2_subdev_init(sd, &rkisp1_isp_sd_ops);ret = v4l2_device_register_subdev(v4l2_dev, sd);

总结

通过上面可以知道不管是Sensor还是MIPI还是ISP,统一描述为struct v4l2_subdev对象,然后将这个对象struct v4l2_subdev通过不同函数注册到V4L2框架中。比如Sensor使用v4l2_async_register_subdev_sensor_common(),MIPI使用v4l2_async_register_subdev(&priv->sd);,ISP使用v4l2_device_register_subdev(v4l2_dev, sd);,其实功能都是一样的。

应用层ioctl如何调用到驱动

我们可以在linux console终端下面 ls /dev/video* 可以查看到我们的Camera设备,由此可见我们的Camera也是一个字符型设备,只是为了摄像头的复杂性,框架做的比较复杂而已,既然是字符型设备,肯定就少不了我们很熟悉的struct file_operations这个结构体里面的ioctl。
一般来说,摄像头驱动需要实现与向核心层提交下面十几个ioctl接口
VIDIOC_REQBUFS:分配内存
VIDIOC_QUERYCAP:查询驱动功能
VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
VIDIOC_S_FMT:设置当前驱动的频捕获格式
VIDIOC_G_FMT:读取当前驱动的频捕获格式
VIDIOC_TRY_FMT:验证当前驱动的显示格式
VIDIOC_CROPCAP:查询驱动的修剪能力
VIDIOC_S_CROP:设置视频信号的边框
VIDIOC_G_CROP:读取视频信号的边框
VIDIOC_QBUF:把数据放回缓存队列
VIDIOC_DQBUF:把数据从缓存中读取出来
VIDIOC_STREAMON:开始图像捕获
VIDIOC_STREAMOFF:结束图像捕获

我们以VIDIOC_STREAMON这个ioctl这个命令开始,一步一步探索应用成是如何使用这个命令,然后一步一步从应用到kernel层一步一步调用下来的。
我们在代码中搜索VIDIOC_STREAMON这个宏定义,可以查看到在kernel\drivers\media\v4l2-core\v4l2-ioctl.c里面有如下的定义。

所以我们可以知道,应用层使用ioctl使用VIDIOC_STREAMON这个命令的时候,会调用到kernel里面的v4l_streamon这个函数,下面我们就跟踪这个函数看看如何是一步,一步调用到MIPI驱动里面的函数,设置MIPI控制器的寄存器的。

v4l_streamonops->vidioc_streamon(file, fh, *(unsigned int *)arg);//代码中查找vidioc_streamon,看看最后调用那个函数,最后查找到是调用,\kernel\drivers\media\platform\soc_camera\soc_camera.c 中的soc_camera_streamonsoc_camera_streamonv4l2_subdev_call(sd, video, s_stream, 1);  ici->ops->s_stream(icd, 1);  //到这里搜索s_stream,最后发现是有可能是调用mipidphy_s_stream,代码在phy-rockchip-mipi-rx.c,还记得phy-rockchip-mipi-rx.c么,就是我们说的Camera驱动有三部分,分别是Sensor相关的,MIPI相关的,ISP相关的,其他的部分MIPI相关的,ISP相关的,也是类似,下面都是以MIPI相关举例。static const struct v4l2_subdev_video_ops mipidphy_video_ops = {.g_mbus_config = mipidphy_g_mbus_config,.s_stream = mipidphy_s_stream,
};//下面继续追踪mipidphy_s_stream这个函数
mipidphy_s_streammipidphy_s_stream_startmipidphy_get_sensor_data_ratemipidphy_update_sensor_mbuspriv->stream_on(priv, sd);csi_mipidphy_stream_on;//最终调用到这里,这个函数就是设置mipi的寄存器,初始化MIPI,开始接收图像// in code kernel\drivers\phy\rockchip\phy-rockchip-mipi-rx.c
static int csi_mipidphy_stream_on(struct mipidphy_priv *priv,struct v4l2_subdev *sd)
{struct v4l2_subdev *sensor_sd = get_remote_sensor(sd);struct mipidphy_sensor *sensor = sd_to_sensor(priv, sensor_sd);const struct dphy_drv_data *drv_data = priv->drv_data;const struct hsfreq_range *hsfreq_ranges = drv_data->hsfreq_ranges;int num_hsfreq_ranges = drv_data->num_hsfreq_ranges;int i, hsfreq = 0;write_grf_reg(priv, GRF_DVP_V18SEL, 0x1);/* phy start */write_csiphy_reg(priv, CSIPHY_CTRL_PWRCTL, 0xe4);/* set data lane num and enable clock lane */write_csiphy_reg(priv, CSIPHY_CTRL_LANE_ENABLE,((GENMASK(sensor->lanes - 1, 0) << MIPI_CSI_DPHY_CTRL_DATALANE_ENABLE_OFFSET_BIT) |(0x1 << MIPI_CSI_DPHY_CTRL_CLKLANE_ENABLE_OFFSET_BIT) | 0x1));/* Reset dphy analog part */write_csiphy_reg(priv, CSIPHY_CTRL_PWRCTL, 0xe0);usleep_range(500, 1000);/* Reset dphy digital part */write_csiphy_reg(priv, CSIPHY_CTRL_DIG_RST, 0x1e);write_csiphy_reg(priv, CSIPHY_CTRL_DIG_RST, 0x1f);/* not into receive mode/wait stopstate */write_grf_reg(priv, GRF_DPHY_CSIPHY_FORCERXMODE, 0x0);/* enable calibration */if (priv->data_rate_mbps > 1500) {write_csiphy_reg(priv, CSIPHY_CLK_CALIB_ENABLE, 0x80);if (sensor->lanes > 0x00)write_csiphy_reg(priv, CSIPHY_LANE0_CALIB_ENABLE, 0x80);if (sensor->lanes > 0x01)write_csiphy_reg(priv, CSIPHY_LANE1_CALIB_ENABLE, 0x80);if (sensor->lanes > 0x02)write_csiphy_reg(priv, CSIPHY_LANE2_CALIB_ENABLE, 0x80);if (sensor->lanes > 0x03)write_csiphy_reg(priv, CSIPHY_LANE3_CALIB_ENABLE, 0x80);}/* set clock lane and data lane */for (i = 0; i < num_hsfreq_ranges; i++) {if (hsfreq_ranges[i].range_h >= priv->data_rate_mbps) {hsfreq = hsfreq_ranges[i].cfg_bit;break;}}if (i == num_hsfreq_ranges) {i = num_hsfreq_ranges - 1;dev_warn(priv->dev, "data rate: %lld mbps, max support %d mbps",priv->data_rate_mbps, hsfreq_ranges[i].range_h + 1);hsfreq = hsfreq_ranges[i].cfg_bit;}csi_mipidphy_wr_ths_settle(priv, hsfreq, MIPI_DPHY_LANE_CLOCK);if (sensor->lanes > 0x00)csi_mipidphy_wr_ths_settle(priv, hsfreq, MIPI_DPHY_LANE_DATA0);if (sensor->lanes > 0x01)csi_mipidphy_wr_ths_settle(priv, hsfreq, MIPI_DPHY_LANE_DATA1);if (sensor->lanes > 0x02)csi_mipidphy_wr_ths_settle(priv, hsfreq, MIPI_DPHY_LANE_DATA2);if (sensor->lanes > 0x03)csi_mipidphy_wr_ths_settle(priv, hsfreq, MIPI_DPHY_LANE_DATA3);write_grf_reg(priv, GRF_DPHY_CSIPHY_CLKLANE_EN, 0x1);write_grf_reg(priv, GRF_DPHY_CSIPHY_DATALANE_EN,GENMASK(sensor->lanes - 1, 0));return 0;
}

全部总结

由上面分析我们可以知道在Camera的框架中在不同板子或者说不同平台,有3部分是需要实现的,第一部分Sensor相关即不同板子摄像头选型肯定是不一样的,这部分的驱动就是要实现Sensor的初始化,寄存器的配置等,比如启动图像捕获的时候,需要配置寄存器,从应用层会使用一个ioctl一步一步的调用下来到驱动中,所以这部分是Sensor相关的,需要Sensor提供相应的驱动。第二部分MIPI相关,比如我们常用的SOC和Sensor连接就是使用MIPI接口,所以就需要实现MIPI相关部分的驱动,这部分一般来说是由SOC厂商进行提供。第三部分ISP相关部分,如果我们的SOC平台如果有ISP模块那么就有ISP模块部分的驱动代码,这部分也是SOC厂商提供的。从dts的配置中我们可以看到,驱动的绑定路径为,从Sensor连接到MIPI然后MIPI连接到ISP,所以说Sensor捕获到的数据通过MIPI传入ISP,然后通过ISP处理后传动应用层进行处理,整个Camera的驱动框架大概就是这个流程。
————————————————
版权声明:本文为CSDN博主「ontheway_zero」的原创文章
原文链接:https://blog.csdn.net/qq_27809619/article/details/117326942
感谢博主的总结 感觉整理的很好。

RK3399调试camera记录相关推荐

  1. rk3399调试camera

    我们进入 camera 调试部分 找了很久,终于在我朋友手上借来一块摄像头接口比较多的开发板,预留两路 mipi摄像头接口.一路 CIF 摄像头接口,当然对于 USB 摄像头就直接接Usb 即可! 直 ...

  2. 2021-06-07 RK3328 Android 10 上调试es7243记录

                  RK3328 Android 10 上调调试es7243记录 一.es7243电路图. 二.修改的部分,解决没有mclk的问题. commit b86b0275bf898f ...

  3. Lora Ra-01模块初步调试问题记录

    Lora Ra-01模块初步调试问题记录 问题描述:购置的Ra-01模块,下载官方驱动程序,通信不成功,收发端只能串口输出"串口1初始化成功" 开发环境:MDK-ARM V4.12 ...

  4. 东方马达步进电机AZM66AK-HS100+AZD-KD调试经验记录

    东方马达步进电机AZM66AK-HS100+AZD-KD调试经验记录(20170803YC) 本次总结主要实现的功能如下:用MEXE02软件在步进电机驱动器中设置好控制动作,每个控制动作有一个特定的编 ...

  5. 至少有一个JAR被扫描用于TLD但尚未包含TLD。 为此记录器启用调试日志记录,以获取已扫描但未在其中找到TLD的完整JAR列表。

    24-Jul-2022 17:18:15.259 信息 [RMI TCP Connection(3)-127.0.0.1] org.apache.jasper.servlet.TldScanner.s ...

  6. TN3399 rk3399自用说明记录

    TN3399 rk3399自用说明记录 Android系统的使用过程 必要工具 在R99系统前提下刷入station OS的system.img 1.解包 目前试过无法开机的 打包解包相关命令 RK3 ...

  7. nRF52832调试相关记录

    nRF52832调试相关记录 | DD'NotesnRF52832调试相关记录背景以前业余用nRF52832做了个物联网小项目(蓝牙微微网),做了些相关的调试记录 nRF52832寄存器类型 Task ...

  8. 【RK3399】[Android 6.0] linux4.4 调试8723BU记录过程

    8723BU USB-WIFI模块调试过程 本人的硬件平台是RK3399 内核版本LINUX4.4 Android 6. 一:先调试底层 kernel 8723BU模块 因为当时RK平台支持很多RTL ...

  9. [转载]Android开发常用调试技术记录

    ANDROID 调试技术: 1)Ps 指令 ls –l /proc/27/ cat /proc/27/cmdline       #cmdline文件表示了这个进程所在的命令行. cat /proc/ ...

最新文章

  1. 使用机智云APP控制战舰V3 (转)
  2. php对象持久化,在 Oracle 中完成 PHP5 对象的持久
  3. Swift - 使用set,get确保索引加减在正常的范围内
  4. python 变量引用_Python 知识要点:变量及引用
  5. .net中调用exchange服务器发邮件
  6. poj2965 The Pilots Brothers' refrigerator
  7. BigAnt service大蚂蚁服务端
  8. python 实例 cadu_【示例详解】AutoCAD处理控件Aspose.CAD 8月新更!支持加载大型DWG文件...
  9. 你对java的看法 论文,一篇文章让你真正了解Java
  10. Linux字符集的修改方法
  11. C# 如何处理抛出的异常,或者已知的错误
  12. android删除进度条,android – 在RecylerView上设置进度条,并在加载数据后删除
  13. Java数据结构之链表的基本操作
  14. Smobiler实现手机弹窗
  15. 《Python数据科学手册》—学习笔记
  16. 计算机组成原理带符号的阵列乘法器,计算机组成原理阵列乘法器课程设计报告精选.doc...
  17. 高盐废水如何处理,离子交换树脂在高盐废水中的应用
  18. ECDSA VS Schnorr signature VS BLS signature
  19. 统计学基础之:均值-中位数-众数-极差-中程数-方差-标准差-变异系数
  20. 计算机中软件和硬件的简单介绍

热门文章

  1. 抖音死亡计算机在线测,抖音死亡计算器怎么玩?抖音死亡计算器测试入口
  2. Scratch制作俄罗斯方块消除游戏
  3. 黑马在线教育数仓实战2
  4. (批处理)把文件夹中所有的照片按拍摄日期和时间批量重命名
  5. 机器学习_BP神经网络——全网最全、最细公式推导
  6. 一个月份是第几季度输出
  7. cass化粪池_北票玻璃钢化粪池规格,玻璃钢脱硫塔
  8. java.lang.Integer connot be cast to class java.lang.String
  9. 一加手机怎么root权限_一加五,怎么获取ROOT权限
  10. 至联云分析:FIL上线后值多少钱?