完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
RK3399 camera
名词解释 在现代移动设备中,常用一种接口用来连接SOC和LCD和Camera,这种接口就是MIPI。 其中SOC和LCD连接叫 DSI(DisplayCommandSet),SOC和Camera连接叫CSI(DisplaySerialInterface)。 硬件连接 一般情况下,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: kerneldriversmediai2cov13850.c MIPI相关:kerneldriversphyrockchipphy-rockchip-mipi-rx.c ISP相关:kerneldriversmediaplatformrockchipisp1rkisp1.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,另一端连接ISP port@0 { reg = <0>; #address-cells = <1>; #size-cells = <0>; //连接到Sensor mipi_in_ucam0: endpoint@1 { reg = <1>; remote-endpoint = <&ucam_out0>; data-lanes = <1 2>; }; }; port@1 { reg = <1>; #address-cells = <1>; #size-cells = <0>; //连接到ISP dphy_rx0_out: endpoint@0 { reg = <0>; remote-endpoint = <&isp0_mipi_in>; }; }; }; }; //这部分是和ISP相关的 &rkisp1_0 { status = "disabled"; port { #address-cells = <1>; #size-cells = <0>; //连接到MIPI isp0_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 xvclkn"); 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 24MHzn"); ov13850->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ov13850->reset_gpio)) dev_warn(dev, "Failed to get reset-gpiosn"); ov13850->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW); if (IS_ERR(ov13850->pwdn_gpio)) dev_warn(dev, "Failed to get pwdn-gpiosn"); ret = ov13850_configure_regulators(ov13850); if (ret) { dev_err(dev, "Failed to get power regulatorsn"); 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 pinstaten"); 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 pinstaten"); } 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_API sd->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; #endif memset(facing, 0, sizeof(facing)); if (strcmp(ov13850->module_facing, "back") == 0) facing[0] = 'b'; else facing[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 failedn"); 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 sysconn"); return -ENODEV; } } priv->regmap_grf = grf; drv_data = of_id->data; for (i = 0; i < drv_data->num_clks; i++) { priv->clks = devm_clk_get(dev, drv_data->clks); if (IS_ERR(priv->clks)) dev_dbg(dev, "Failed to get %sn", drv_data->clks); } 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 : %dn", 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_probe ret = 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这个宏定义,可以查看到在kerneldriversmediav4l2-corev4l2-ioctl.c里面有如下的定于。 所以我们可以知道,应用层使用ioctl使用VIDIOC_STREAMON这个命令的时候,会调用到kernel里面的v4l_streamon这个函数,下面我们就跟踪这个函数看看如何是一步,一步调用到MIPI驱动里面的函数,设置MIPI控制器的寄存器的。 v4l_streamon ops->vidioc_streamon(file, fh, *(unsigned int *)arg); //代码中查找vidioc_streamon,看看最后调用那个函数,最后查找到是调用,kerneldriversmediaplatformsoc_camerasoc_camera.c 中的soc_camera_streamon soc_camera_streamon v4l2_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_stream mipidphy_s_stream_start mipidphy_get_sensor_data_rate mipidphy_update_sensor_mbus priv->stream_on(priv, sd); csi_mipidphy_stream_on;//最终调用到这里,这个函数就是设置mipi的寄存器,初始化MIPI,开始接收图像 // in code kerneldriversphyrockchipphy-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.range_h >= priv->data_rate_mbps) { hsfreq = hsfreq_ranges.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.range_h + 1); hsfreq = hsfreq_ranges.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的驱动框架大概就是这个流程。 |
|
|
|
你正在撰写答案
如果你是对答案或其他答案精选点评或询问,请使用“评论”功能。
1924 浏览 1 评论
synopsys 的design ware:DW_fpv_div,浮点数除法器,默认32位下,想提升覆盖率(TMAX),如果用功能case去提升覆盖率呢?
2406 浏览 1 评论
RK3588 GStreamer调试四路鱼眼摄像头四宫格显示报错
5193 浏览 1 评论
【飞凌嵌入式OK3576-C开发板体验】RKNN神经网络-YOLO图像识别
254 浏览 0 评论
【飞凌嵌入式OK3576-C开发板体验】SSH远程登录网络配置及CAN通讯
1336 浏览 0 评论
浏览过的版块 |
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 12:48 , Processed in 0.755133 second(s), Total 66, Slave 57 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号