NXP

ov5640_mipi.c分析

2019-07-12 13:52发布

data/attach/1907/s9rkvbx029posv5sdkh5krqym1sjuo4w.jpgdata/attach/1907/a0gvjj4kewo3zryr4fvjyt2gkwky4u8i.jpg ov5640芯片手册中看到这样一句话: The OV5640 supports both a digital video parallel port and a serial MIPI port. 所以ov5640既支持数字并口视频传输,同样支持mipi接口规范。
摄像头插入到开发板上面的时候,如果有匹配的驱动程序,就会调用到probe函数,先从probe函数来分析。
(一)probe函数
1.1 获取设备ID probe函数中,首先通过几个of类函数来获取pwn-gpiosrst-gpios等的值。 然后就是设置sensor_data结构体ov5640_data。每个sensor_data结构体都代表一个具体的设备,来看看这个结构体: struct sensor_data { const struct ov5642_platform_data *platform_data; struct v4l2_int_device *v4l2_int_device; struct i2c_client *i2c_client; struct v4l2_pix_format pix; struct v4l2_captureparm streamcap; bool on; //设备是否上电 /* control settings */ int brightness; int hue; int contrast; int saturation; int red; int green; int blue; int ae_mode; u32 mclk; //mclk时钟 u8 mclk_source; //mclk时钟源 struct clk *sensor_clk; int csi; void (*io_init)(void); //初始化函数 };
然后就是填充这个结构体,重点是i2c_client的填充,需要根据填充的这个client来找到对应的设备。那么怎么确定找到的设备就是我们想要的呢?就是通过读设备的设备ID。可以看到在probe函数中通过: retval= ov5640_read_reg(OV5640_CHIP_ID_HIGH_BYTE, &chip_id_high); retval= ov5640_read_reg(OV5640_CHIP_ID_LOW_BYTE, &chip_id_low);
来分别读取ov5640设备ID的高字节和低字节。我们可以看到,在ov5640_mipi.c中是这样定义的: #define OV5640_CHIP_ID_HIGH_BYTE 0x300A #define OV5640_CHIP_ID_LOW_BYTE 0x300B
我们将这两个地址去ov5640的芯片手册中搜索可以发现,
这两个地址就是ov5640设备的ID所在的地址,通过这个设备ID就能确定我们找到的设备。
1.2 ov5640_power_on函数 ov5640_power_on(dev); ov5640_power_on(dev); static int ov5640_power_on(struct device *dev) { int ret = 0; io_regulator = devm_regulator_get(dev, "DOVDD"); if (!IS_ERR(io_regulator)) { regulator_set_voltage(io_regulator, OV5640_VOLTAGE_DIGITAL_IO, OV5640_VOLTAGE_DIGITAL_IO); ret = regulator_enable(io_regulator); if (ret) { pr_err("%s:io set voltage error ", __func__); return ret; } else { dev_dbg(dev, "%s:io set voltage ok ", __func__); } } else { pr_err("%s: cannot get io voltage error ", __func__); io_regulator = NULL; } core_regulator = devm_regulator_get(dev, "DVDD"); if (!IS_ERR(core_regulator)) { regulator_set_voltage(core_regulator, OV5640_VOLTAGE_DIGITAL_CORE, OV5640_VOLTAGE_DIGITAL_CORE); ret = regulator_enable(core_regulator); if (ret) { pr_err("%s:core set voltage error ", __func__); return ret; } else { dev_dbg(dev, "%s:core set voltage ok ", __func__); } } else { core_regulator = NULL; pr_err("%s: cannot get core voltage error ", __func__); } analog_regulator = devm_regulator_get(dev, "AVDD"); if (!IS_ERR(analog_regulator)) { regulator_set_voltage(analog_regulator, OV5640_VOLTAGE_ANALOG, OV5640_VOLTAGE_ANALOG); ret = regulator_enable(analog_regulator); if (ret) { pr_err("%s:analog set voltage error ", __func__); return ret; } else { dev_dbg(dev, "%s:analog set voltage ok ", __func__); } } else { analog_regulator = NULL; pr_err("%s: cannot get analog voltage error ", __func__); } return ret; }
从上面的程序中可以看出来,它设置了三个regulator,中文翻译为”稳定器“,内核中有关于这个的模块的驱动框架,关于这个驱动框架的分析可以查看: http://www.wowotech.net/pm_subsystem/regulator_framework_overview.html 我们这里只分析与我们相关的东西。 通过regulator_set_voltage函数来为这几个regulator设置电压,分别设置为OV5640_VOLTAGE_DIGITAL_IOOV5640_VOLTAGE_DIGITAL_COREOV5640_VOLTAGE_ANALOG #define OV5640_VOLTAGE_ANALOG 2800000 #define OV5640_VOLTAGE_DIGITAL_CORE 1500000 #define OV5640_VOLTAGE_DIGITAL_IO 1800000
同时,在dts文件中,定义了它的电压为多少, ov564x_mipi: ov564x_mipi@3c { /* i2c2 driver */ compatible = "ovti,ov564x_mipi"; reg = <0x3c>; clocks = <&clks 201>; clock-names = "csi_mclk"; DOVDD-supply = <&vgen4_reg>; /* 1.8v */ AVDD-supply = <&vgen3_reg>; /* 2.8v, rev C board is VGEN3 rev B board is VGEN5 */ DVDD-supply = <&vgen2_reg>; /* 1.5v*/ pwn-gpios = <&gpio1 19 1>; /* active low: SD1_CLK */ rst-gpios = <&gpio1 20 0>; /* active high: SD1_DAT2 */ csi_id = <1>; mclk = <24000000>; mclk_source = <0>; };
可以看出来,它们设置的一致。那么在dts文件中为啥这么设置呢?肯定是根据ov5640的芯片手册中设置的: 1.3 ov5640_reset函数 ov5640_reset();
这个函数用来重置摄像头,如下所示: static void ov5640_reset(void) { /* camera reset */ gpio_set_value(rst_gpio, 1); /* camera power dowmn */ gpio_set_value(pwn_gpio, 1); msleep(5); gpio_set_value(pwn_gpio, 0); msleep(5); gpio_set_value(rst_gpio, 0); msleep(1); gpio_set_value(rst_gpio, 1); msleep(5); gpio_set_value(pwn_gpio, 1); }
这里面的rst_gpiopwn_gpio是在probe函数中通过of类函数获取的,既然是通过of类函数获得的,那么在dts文件中肯定有对应的设置: ov564x_mipi: ov564x_mipi@3c { /* i2c2 driver */ compatible = "ovti,ov564x_mipi"; reg = <0x3c>; clocks = <&clks 201>; clock-names = "csi_mclk"; DOVDD-supply = <&vgen4_reg>; /* 1.8v */ AVDD-supply = <&vgen3_reg>; /* 2.8v, rev C board is VGEN3 rev B board is VGEN5 */ DVDD-supply = <&vgen2_reg>; /* 1.5v*/ pwn-gpios = <&gpio1 19 1>; /* active low: SD1_CLK */ rst-gpios = <&gpio1 20 0>; /* active high: SD1_DAT2 */ csi_id = <1>; mclk = <24000000>; mclk_source = <0>; }; 这个函数操作的是gpio11920位,关于这两位引脚的意义还需要继续深入。它一共包含3个参数,第一个参数表示gpio1,第二个参数是否表示gpio1的哪一位??第三个参数表示默认值。比如说pwn-gpios的默认值是1,说明置0的时候是上电,置1的时候是关电。 同时,重置摄像头的时候,ov5640_reset()数设置pwn-gpiosrst-gpios写入顺序是否是固定的?
1.3 ov5640_standby函数 ov5640_standby(0); static void ov5640_standby(s32 enable) { if (enable) gpio_set_value(pwn_gpio, 1); else gpio_set_value(pwn_gpio, 0); msleep(2); }
这个函数就是根据函数的传入参数来向pwn_gpio寄存器写值,向pwn_gpio寄存器写1表示关电,0代表关电。至于向寄存器中写1代表上电还是关电,这个需要查看对应寄存器在dts文件中写进去的值。
1.4 先上电,通过ov5640_read_reg函数来获取摄像头的设备ID以后,再次使用ov5640_standby(1);来关电。
之后就是通过ov5640_int_device.priv= &ov5640_data;来将ov5640_int_device结构体的priv指向设置好的sensor_data结构体,然后通过 retval= v4l2_int_device_register(&ov5640_int_device);来将ov5640_int_device作为一个slave设备注册到v4l2框架中,在这个函数中,会将slave设备添加到int_list链表中,尝试使用v4l2_int_device_try_attach_all函数来匹配master设备。

(二)在probe函数执行完毕以后,就可以操作这个摄像头了,之后我们继续按照mxc_v4l2_capture.c这个应用程序的执行过程来完善ov5640_mipi的一些操作。首先是open函数。
2.1 vidioc_int_g_ifparm函数 open函数中,首先调用vidioc_int_g_ifparm(cam->sensor,&ifparm);函数来从slave设备中获取ifparm的信息,最终会调用到ov5640_mipi.cioctl_g_ifparm函数。先来看open函数中,它传入了两个参数:cam->sensor,ifparm;其实在ov5640_mipi.c中,它并没有从cam->sensor里面获取ifparm的信息来填充到ifparm中,而是在ioctl_g_ifparm函数中直接为ifparm中的各个成员变量赋值。主要是为ifparm.u.bt656成员赋值,包括clock_currmodeclock_minclock_max等等。
2.2 vidioc_int_g_fmt_cap函数 在这个函数中,就直接用f->fmt.pix= sensor->pix;来将sensor_data里面的pix结构体赋给了cam_fmt里面的fmt.pix结构体。
2.3 vidioc_int_s_power函数 vidioc_int_s_power(cam->sensor, 1); static int ioctl_s_power(struct v4l2_int_device *s, int on) { struct sensor_data *sensor = s->priv; if (on && !sensor->on) { if (io_regulator) if (regulator_enable(io_regulator) != 0) return -EIO; if (core_regulator) if (regulator_enable(core_regulator) != 0) return -EIO; if (gpo_regulator) if (regulator_enable(gpo_regulator) != 0) return -EIO; if (analog_regulator) if (regulator_enable(analog_regulator) != 0) return -EIO; /* Make sure power on */ ov5640_standby(0); } else if (!on && sensor->on) { if (analog_regulator) regulator_disable(analog_regulator); if (core_regulator) regulator_disable(core_regulator); if (io_regulator) regulator_disable(io_regulator); if (gpo_regulator) regulator_disable(gpo_regulator); ov5640_standby(1); } sensor->on = on; return 0; }
在这个函数中,会根据vidioc_int_s_power函数传入的第二个参数的值on来决定是否将设备上电。1表示上电,0表示关电。这里面的io_regulatorcore_regulatoranalog_regulator是在ov5640_power_on函数中获取到的,gpo_regulator应该没有设置。然后需要注意的一点是:在ioctl_s_power函数中的第二个参数如果为1的话代表上电,为0的话代表关电。但是在ov5640_standby函数中,如果为0代表上电,为1代表关电。所以,在if(on &&!sensor->on)判断语句中,如果想要确定摄像头是上电的话,需要使用ov5640_standby(0);。这一点比较拗口。
2.4 vidioc_int_dev_init函数 vidioc_int_dev_init(cam->sensor); static int ioctl_dev_init(struct v4l2_int_device *s) { struct sensor_data *sensor = s->priv; u32 tgt_xclk; /* target xclk */ u32 tgt_fps; /* target frames per secound */ int ret; enum ov5640_frame_rate frame_rate; void *mipi_csi2_info; ov5640_data.on = true; /* mclk */ tgt_xclk = ov5640_data.mclk; tgt_xclk = min(tgt_xclk, (u32)OV5640_XCLK_MAX); tgt_xclk = max(tgt_xclk, (u32)OV5640_XCLK_MIN); ov5640_data.mclk = tgt_xclk; pr_debug(" Setting mclk to %d MHz ", tgt_xclk / 1000000); /* Default camera frame rate is set in probe */ tgt_fps = sensor->streamcap.timeperframe.denominator / sensor->streamcap.timeperframe.numerator; pr_debug(" tft_fps is %d. ", tgt_fps); if (tgt_fps == 15) frame_rate = ov5640_15_fps; else if (tgt_fps == 30) frame_rate = ov5640_30_fps; else return -EINVAL; /* Only support 15fps or 30fps now. */ mipi_csi2_info = mipi_csi2_get_info(); /* enable mipi csi2 */ if (mipi_csi2_info) mipi_csi2_enable(mipi_csi2_info); else { printk(KERN_ERR "%s() in %s: Fail to get mipi_csi2_info! ", __func__, __FILE__); return -EPERM; } ret = ov5640_init_mode(frame_rate, ov5640_mode_INIT, ov5640_mode_INIT); return ret; }
2.4.1 在这个函数中,首先通过tgt_xclk= ov5640_data.mclk;来获取到mclk的值。这个ov5640_data.mclk是在probe函数中设置的。然后对tgt_xclk与摄像头允许的最大值最小值进行判断, #define OV5640_XCLK_MIN 6000000 #define OV5640_XCLK_MAX 24000000
使得tgt_xclk位于这个区间内。 然后计算tgt_fps的值,它需要的两个值同样是在probe函数中设置的。 根据tgt_fps的值来设置frame_rate的值,这个frame_rate用在后面的ov5640_init_mode函数中。
然后通过mipi_csi2_get_info函数来获取到mxc_mipi_csi2.c文件中的gmipi_csi2结构体,这个结构体是一个全局变量。获取到这个结构体以后通过mipi_csi2_enable函数来使能。具体操作是设置mipi_csi2_info结构体里面的mipi_en位为true,然后通过clk_prepare_enable函数来使能mipi_csi2_info结构体里面的cfg_clkdphy_clk
之后就是调用ov5640_init_mode函数了。
2.4.2 ov5640_init_mode函数 这个函数在ioctl_s_parm函数和ioctl_dev_init函数中调用,这个函数用来设置ov5640的模式,有以下几种模式: enum ov5640_mode { ov5640_mode_MIN = 0, ov5640_mode_VGA_640_480 = 0, ov5640_mode_QVGA_320_240 = 1, ov5640_mode_NTSC_720_480 = 2, ov5640_mode_PAL_720_576 = 3, ov5640_mode_720P_1280_720 = 4, ov5640_mode_1080P_1920_1080 = 5, ov5640_mode_QSXGA_2592_1944 = 6, ov5640_mode_QCIF_176_144 = 7, ov5640_mode_XGA_1024_768 = 8, ov5640_mode_MAX = 8, ov5640_mode_INIT = 0xff, /*only for sensor init*/ };
mxc_v4l2_capture.c这个应用程序中,可以通过-m选项来指定使用哪种模式,然后将-m后面指定的模式保存在g_capture_mode中,然后通过parm.parm.capture.capturemode= g_capture_mode;调用ioctl(fd_v4l,VIDIOC_S_PARM, &parm)函数,最终就会调用这个ov5640_init_mode函数来修改ov5640的模式。
ioctl_dev_init函数中已经设置了frame_rate,然后设置ov5640_init_mode函数其他两个参数,第二个参数表示新设置的mode,第三个参数表示原来的mode。但是需要注意的是在mxc_v4l2_capture.c中的mxc_v4l_open函数,它调用ov5640_init_mode来初始化摄像头,这时候,摄像头的初始mode和新mode都没有指定,上面说了,它们是在VIDIOC_S_PARMioctl中指定的,这时候就需要指定