ov5640_mipi.c分析

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\n", __func__); 
			return ret; 
		} else { 
			dev_dbg(dev, 
				"%s:io set voltage ok\n", __func__); 
		} 
	} else { 
		pr_err("%s: cannot get io voltage error\n", __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\n", __func__); 
			return ret; 
		} else { 
			dev_dbg(dev, 
				"%s:core set voltage ok\n", __func__); 
		} 
	} else { 
		core_regulator = NULL; 
		pr_err("%s: cannot get core voltage error\n", __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\n", 
				__func__); 
			return ret; 
		} else { 
			dev_dbg(dev, 
				"%s:analog set voltage ok\n", __func__); 
		} 
	} else { 
		analog_regulator = NULL; 
		pr_err("%s: cannot get analog voltage error\n", __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的芯片手册中设置的:

ov5640_mipi.c分析_第1张图片

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\n", 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.\n", 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!\n", 
		       __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中指定的,这时候就需要指定ov5640_init_mode的第二个和第三个参数都为ov5640_mode_INIT

static int ov5640_init_mode(enum ov5640_frame_rate frame_rate, 
			    enum ov5640_mode mode, enum ov5640_mode orig_mode) 
{ 
	struct reg_value *pModeSetting = NULL; 
	s32 ArySize = 0; 
	int retval = 0; 
	void *mipi_csi2_info; 
	u32 mipi_reg, msec_wait4stable = 0; 
	enum ov5640_downsize_mode dn_mode, orig_dn_mode; 

	if ((mode > ov5640_mode_MAX || mode < ov5640_mode_MIN) 
		&& (mode != ov5640_mode_INIT)) { 
		pr_err("Wrong ov5640 mode detected!\n"); 
		return -1; 
	} 

/*mode进行判断,根据上面列举的enumov5640_mode,它就是判断传入的mode是否合法。*/

	mipi_csi2_info = mipi_csi2_get_info(); //获取 mipi_csi2_info

	/* initial mipi dphy */ 
	if (!mipi_csi2_info) { 
		printk(KERN_ERR "%s() in %s: Fail to get mipi_csi2_info!\n", 
		       __func__, __FILE__); 
		return -1; 
	} 

/*判断mipi_csi2_info是否获取成功*/

	if (!mipi_csi2_get_status(mipi_csi2_info)) 
		mipi_csi2_enable(mipi_csi2_info); 

	if (!mipi_csi2_get_status(mipi_csi2_info)) { 
		pr_err("Can not enable mipi csi2 driver!\n"); 
		return -1; 
	} 

/*首先判断mipi_csi2_infomipi_en是否置位,这一位表示是否使能了mipi摄像头。如果没有使能的话,就调用mipi_csi2_enable函数来使能mipi_csi2_info里面的cfg_clkdphy_clk,然后将mipi_en置位为true。设置好以后继续调用mipi_csi2_get_status函数来确定是否使能成功。*/

	mipi_csi2_set_lanes(mipi_csi2_info); 

/*在这个函数中将mipi_csi2_info里面的lanes的值写到MIPI_CSI2_N_LANES寄存器中,

ov5640_mipi.c分析_第2张图片


而这个mipi_csi2_info里面的lanes的值是什么时候获取到的呢?在mxc_mipi_csi2.c文件中的mipi_csi2_probe函数中,通过of_property_read_u32函数来保存到mipi_csi2_info中的。

*/

	/*Only reset MIPI CSI2 HW at sensor initialize*/ 
	if (mode == ov5640_mode_INIT) 
		mipi_csi2_reset(mipi_csi2_info); 

/*如果是mode等于ov5640_mode_INIT,就表示是在ioctl_dev_init函数中调用的,就调用mipi_csi2_reset函数来初始化MIPI_CSI2相关的寄存器。这个函数在mxc_mipi_csi2.c文件中定义.

int mipi_csi2_reset(struct mipi_csi2_info *info) 
{ 
	_mipi_csi2_lock(info); 

	mipi_csi2_write(info, 0x0, MIPI_CSI2_PHY_SHUTDOWNZ); 
	mipi_csi2_write(info, 0x0, MIPI_CSI2_DPHY_RSTZ); 
	mipi_csi2_write(info, 0x0, MIPI_CSI2_CSI2_RESETN); 

	mipi_csi2_write(info, 0x00000001, MIPI_CSI2_PHY_TST_CTRL0); 
	mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL1); 
	mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL0); 
	mipi_csi2_write(info, 0x00000002, MIPI_CSI2_PHY_TST_CTRL0); 
	mipi_csi2_write(info, 0x00010044, MIPI_CSI2_PHY_TST_CTRL1); 
	mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL0); 
	mipi_csi2_write(info, 0x00000014, MIPI_CSI2_PHY_TST_CTRL1); 
	mipi_csi2_write(info, 0x00000002, MIPI_CSI2_PHY_TST_CTRL0); 
	mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL0); 

	mipi_csi2_write(info, 0xffffffff, MIPI_CSI2_PHY_SHUTDOWNZ); 
	mipi_csi2_write(info, 0xffffffff, MIPI_CSI2_DPHY_RSTZ); 
	mipi_csi2_write(info, 0xffffffff, MIPI_CSI2_CSI2_RESETN); 

	_mipi_csi2_unlock(info); 

	return 0; 
} 
EXPORT_SYMBOL(mipi_csi2_reset);

这个函数中反复向MIPI_CSI2_PHY_TST_CTRL0MIPI_CSI2_PHY_TST_CTRL1寄存器中写不同的值,到底有什么目的??明天来了把值写进去仔细看看。*/

	if (ov5640_data.pix.pixelformat == V4L2_PIX_FMT_UYVY) 
		mipi_csi2_set_datatype(mipi_csi2_info, MIPI_DT_YUV422); 
	else if (ov5640_data.pix.pixelformat == V4L2_PIX_FMT_RGB565) 
		mipi_csi2_set_datatype(mipi_csi2_info, MIPI_DT_RGB565); 
	else 
		pr_err("currently this sensor format can not be supported!\n"); 

/*根据ov5640_data.pix.pixelformat的值来设置mipi_csi2_info里面的datatype的值。通过mipi_csi2_set_datatype函数来设置。这个ov5640_data.pix.pixelformat的值是在ov5640_probe函数中设置的。从这两个判断语句中可以推断出来,在这个驱动中,指定摄像头只支持V4L2_PIX_FMT_UYVYV4L2_PIX_FMT_RGB565两种格式。*/


/*看下面的代码前,需要对enumov5640_downsize_mode进行一个初步的了解:

/* image size under 1280 * 960 are SUBSAMPLING 
 * image size upper 1280 * 960 are SCALING 
 */ 
enum ov5640_downsize_mode { 
	SUBSAMPLING, 
	SCALING, 
};

这个enum的注释写的很清楚了,当imagesize大于1280* 960时,downsize_modeSCALING,当imagesize小于1280* 960时,downsize_modeSUBSAMPLING。下面的代码主要是根据新modeov5640_downsize_mode类型和初始modeov5640_downsize_mode类型来决定使用哪个函数来切换mode模式。*/

	dn_mode = ov5640_mode_info_data[frame_rate][mode].dn_mode; 
	orig_dn_mode = ov5640_mode_info_data[frame_rate][orig_mode].dn_mode; 

/*在这里有一个二维数组ov5640_mode_info_data

staticstruct ov5640_mode_info ov5640_mode_info_data[2][ov5640_mode_MAX +1],再来看这个函数中,它会根据frame_ratemode两个下标来找到对应的元素。关于这个frame_rate,它是enumov5640_frame_rate类型的,

enum ov5640_frame_rate { 
	ov5640_15_fps, 
	ov5640_30_fps 
};

这两个值默认为01.关于enum的默认值的分析可以看:《C语言enum枚举类型解析

http://blog.csdn.net/skyflying2012/article/details/22736633

*/

	if (mode == ov5640_mode_INIT) { 
		pModeSetting = ov5640_init_setting_30fps_VGA; 
		ArySize = ARRAY_SIZE(ov5640_init_setting_30fps_VGA); 

		ov5640_data.pix.width = 640; 
		ov5640_data.pix.height = 480; 
		retval = ov5640_download_firmware(pModeSetting, ArySize); 
		if (retval < 0) 
			goto err; 

		pModeSetting = ov5640_setting_30fps_VGA_640_480; 
		ArySize = ARRAY_SIZE(ov5640_setting_30fps_VGA_640_480); 
		retval = ov5640_download_firmware(pModeSetting, ArySize); 
	} 

/*modeov5640_mode_INIT时,代表第一次初始化摄像头设备,就直接设置pModeSettingArySize的值,然后调用ov5640_download_firmware函数来初始化摄像头。在这个文件中,可以看出来ov5640_init_setting_30fps_VGA数组是一堆寄存器的地址和值的组合,通过这个函数来设置摄像头内部寄存器的值,而不是设置开发板上面的控制寄存器。这些值可以对照ov5640摄像头的芯片手册来查看。但是不理解的是,在这里设置了两次,分别为ov5640_init_setting_30fps_VGAov5640_setting_30fps_VGA_640_480*/

else if ((dn_mode == SUBSAMPLING && orig_dn_mode == SCALING) || 
			(dn_mode == SCALING && orig_dn_mode == SUBSAMPLING)) { 
		/* change between subsampling and scaling 
		 * go through exposure calucation */ 
		retval = ov5640_change_mode_exposure_calc(frame_rate, mode); 
	} 

/*因为dn_modeorig_dn_mode都是enumov5640_downsize_mode类型的,它们都只有两种值,所以当两者不同时,就通过ov5640_change_mode_exposure_calc函数来改变摄像头的mode*/

else { 
		/* change inside subsampling or scaling 
		 * download firmware directly */ 
		retval = ov5640_change_mode_direct(frame_rate, mode); 
	} 

/*dn_modeorig_dn_mode这两者相同时,就直接调用ov5640_change_mode_direct函数改变mode就行了。关于这三个函数的分析,在分析完这个函数后分析。*/

	if (retval < 0) 
		goto err; 

/*总之,当调用ov5640_init_mode函数的时候,比如在ioctl_dev_init函数中调用,它就表示第一次使用摄像头,ov5640_init_mode函数的mode模式都为ov5640_mode_INIT,然后就会在ov5640_init_mode函数中调用ov5640_download_firmware函数来设置摄像头上面的寄存器。如果是在VIDIOC_S_PARMioctl调用的时候,这时候,就可能修改ov5640_init_mode函数里面的mode模式,这时候就需要根据mode模式里面的ov5640_downsize_mode来判断是否改变了,如果改变了的话,就会调用ov5640_change_mode_exposure_calc函数来设置摄像头上面的寄存器,如果没有改变的话,就直接调用ov5640_change_mode_direct设置摄像头上面的寄存器即可。*/

	OV5640_set_AE_target(AE_Target); 

/*关于这一块的讲解查看ov5640芯片手册的《4.5AEC/AGC algorithms》这一节,主要是设置自动曝光控制(AutoExposure ControlAEC)和自动增益控制(AutoGain ControlAGC)。

ov5640_mipi.c分析_第3张图片

函数如下所示:

static int OV5640_set_AE_target(int target) 
{ 
	/* stable in high */ 
	int fast_high, fast_low; 
	AE_low = target * 23 / 25;	/* 0.92 */ 
	AE_high = target * 27 / 25;	/* 1.08 */ 

	fast_high = AE_high<<1; 
	if (fast_high > 255) 
		fast_high = 255; 
 
	fast_low = AE_low >> 1; 

	ov5640_write_reg(0x3a0f, AE_high); 
	ov5640_write_reg(0x3a10, AE_low); 
	ov5640_write_reg(0x3a1b, AE_high); 
	ov5640_write_reg(0x3a1e, AE_low); 
	ov5640_write_reg(0x3a11, fast_high); 
	ov5640_write_reg(0x3a1f, fast_low); 

	return 0; 
}

这个计算过程是摄像头相关的算法,在芯片手册中详细介绍了。

ov5640芯片上面的0x3a0f存储的是曝光时间的最大值,0x3a10存储的是曝光时间的最小值。通过这个函数可以看出来它的计算过程。

0x3a1b存储的是图像从稳定状态切换到不稳定状态时曝光时间的最大值,0x3a1e存储的是图像从稳定状态切换到不稳定状态时曝光时间的最小值。

这时候需要理解另外一个寄存器:0x56a1,这个寄存器中保存的是目标图像的亮度平均值,这个寄存器是一个只读寄存器。

ov5640_mipi.c分析_第4张图片

0x56a1寄存器的值不在{0x3a1e,0x3a1b}这个区间之内时,AEC就调整它们,并且使他们位于{0x3a10,0x3a0f}这个区间。所以这个{0x3a1e,0x3a1b}这个区间称为稳定状态区间。

0x56a1寄存器的值位于{0x3a1e,0x3a1b}这个区间的时候,就是指图像处于稳定状态。反之,则称为不稳定状态。

上面的讲解是AEC处于auto状态时,AEC就会去自动调节这些参数,同时,这个AEC支持manual模式,关于这个模式的选择,是通过操作0x3503寄存器来完成的,我们在后面的OV5640_turn_on_AE_AG()函数分析中再具体分析。

AEC处于manual模式的时候,还分为normalfast选择。normal就是支持手工一点一点地调节曝光量,fast快速的调节曝光量。究竟有多快速呢。。。{0x3a1f,0x3a11}这个区间是fast情况下的曝光区间,当0x56a1寄存器里面的值小于0x3a1f时,AEC就直接将0x56a1寄存器里面的值乘2;当0x56a1寄存器里面的值大于0x3a11时,AEC就直接将0x56a1寄存器里面的值除以2

*/

	OV5640_get_light_freq(); 

/*这个函数如下所示,这个函数的目的是获得ov5640的频闪,关于摄像头频闪的讲解,可以查看Camera图像处理原理分析-抗噪变焦 频闪 等

http://blog.csdn.net/colorant/article/details/1913334

static int OV5640_get_light_freq(void) 
{ 
	/* get banding filter value */ 
	int temp, temp1, light_freq = 0; 
	u8 tmp; 

	temp = ov5640_read_reg(0x3c01, &tmp); 

	if (temp & 0x80) { 
		/* manual */ 
		temp1 = ov5640_read_reg(0x3c00, &tmp); 
		if (temp1 & 0x04) { 
			/* 50Hz */ 
			light_freq = 50; 
		} else { 
			/* 60Hz */ 
			light_freq = 60; 
		} 
	} else { 
		/* auto */ 
		temp1 = ov5640_read_reg(0x3c0c, &tmp); 
		if (temp1 & 0x01) { 
			/* 50Hz */ 
			light_freq = 50; 
		} else { 
			/* 60Hz */ 
/* 这里是不是一个bug,应该写上:light_freq = 60; */
		} 
	} 
	return light_freq; 
}

ov5640_mipi.c分析_第5张图片

对比程序和芯片手册,可以看出来,首先会根据0x3c01bit[7]来判断是auto还是manual模式,

如果为auto模式的话,就会去读取0x3c0c寄存器的bit[0],为1的话就是50Hz的频闪,为0的话就是60Hz的频闪。

如果为manual模式的话,就会去读取0x3c00寄存器的bit[2],为1的话就是50Hz的频闪,为0的话就是60Hz的频闪。

*/

	OV5640_set_bandingfilter(); 

/*这个函数是设置摄像头的工频干扰,CMOS是行曝光,也就是在每行曝光时间决定了画面的亮度,举例:一个50HZ的光源,电压曲线为正弦曲线,那能量曲线定性分析可以认为是取了绝对值的电压曲线。那就是能量做1/100秒的周期变化。那就要求曝光的时间必须是1/100秒的整数倍。如果没有把曝光时间调整到1/100秒的整数倍,就有可能会有每行的曝光值不一样,造成同一个image上有水波纹现象。CCD是整帧同时曝光,所以,工频干扰表现的就是图像有轻微的闪烁。产生的原理与CMOS sensor的原理相似。

static void OV5640_set_bandingfilter(void) 
{ 
	int prev_VTS; 
	int band_step60, max_band60, band_step50, max_band50; 

	/* read preview PCLK */ 
	prev_sysclk = OV5640_get_sysclk(); 
	/* read preview HTS */ 
	prev_HTS = OV5640_get_HTS(); 

	/* read preview VTS */ 
	prev_VTS = OV5640_get_VTS(); 

	/* calculate banding filter */ 
	/* 60Hz */ 
	band_step60 = prev_sysclk * 100/prev_HTS * 100/120; 
	ov5640_write_reg(0x3a0a, (band_step60 >> 8)); 
	ov5640_write_reg(0x3a0b, (band_step60 & 0xff)); 

	max_band60 = (int)((prev_VTS-4)/band_step60); 
	ov5640_write_reg(0x3a0d, max_band60); 

	/* 50Hz */ 
	band_step50 = prev_sysclk * 100/prev_HTS; 
	ov5640_write_reg(0x3a08, (band_step50 >> 8)); 
	ov5640_write_reg(0x3a09, (band_step50 & 0xff)); 

	max_band50 = (int)((prev_VTS-4)/band_step50); 
	ov5640_write_reg(0x3a0e, max_band50); 
} 

关于这个函数,它首先通过OV5640_get_sysclk函数来获取系统的时钟,然后OV5640_get_HTSOV5640_get_VTS函数分别获取ov5640Horizontaltotalsize verticaltotal size,对于50Hz60Hz,有不同的计算方式,但是这个计算方法看半天都没有理解。。。

*/

	ov5640_set_virtual_channel(ov5640_data.csi); 

/*这个函数设置虚拟通道,如下所示:

static void ov5640_set_virtual_channel(int channel) 
{ 
	u8 channel_id; 

	ov5640_read_reg(0x4814, &channel_id); 
	channel_id &= ~(3 << 6); 
	ov5640_write_reg(0x4814, channel_id | (channel << 6)); 
}

但是在ov5640的芯片手册中,这几位显示的是DEBUGMODE,如下所示

*/

	/* add delay to wait for sensor stable */ 
	if (mode == ov5640_mode_QSXGA_2592_1944) { 
		/* dump the first two frames: 1/7.5*2 
		 * the frame rate of QSXGA is 7.5fps */ 
		msec_wait4stable = 267; 
	} else if (frame_rate == ov5640_15_fps) { 
		/* dump the first nine frames: 1/15*9 */ 
		msec_wait4stable = 600; 
	} else if (frame_rate == ov5640_30_fps) { 
		/* dump the first nine frames: 1/30*9 */ 
		msec_wait4stable = 300; 
	} 
	msleep(msec_wait4stable); 

/*根据不同的模式来选择等待sensor稳定的时间。*/

	if (mipi_csi2_info) { 
		unsigned int i; 

		i = 0; 

		/* wait for mipi sensor ready */ 
		mipi_reg = mipi_csi2_dphy_status(mipi_csi2_info); 
		while ((mipi_reg == 0x200) && (i < 10)) { 
			mipi_reg = mipi_csi2_dphy_status(mipi_csi2_info); 
			i++; 
			msleep(10); 
		} 

		if (i >= 10) { 
			pr_err("mipi csi2 can not receive sensor clk!\n"); 
			return -1; 
		} 

/* mipi_csi2_dphy_status函数就是去读取MIPI_CSI2_PHY_STATE寄存器的值,然后保存在mipi_reg变量中。这个寄存器如下所示:

ov5640_mipi.c分析_第6张图片


可以看出来,这个寄存器的bit[9]表示sensorclocklane处于什么样的状态。注意:这一位是activelow,所以置0时表示使能。再来看这一段代码,它会一直等待这一位置0,一直等待10×10ms,如果过了这一段时间,MIPI_CSI2_PHY_STATE寄存器的bit[9]还保持为1的状态时,就打印出“mipicsi2 can not receive sensor clk!”这句话报错。

*/

		i = 0; 

		/* wait for mipi stable */ 
		mipi_reg = mipi_csi2_get_error1(mipi_csi2_info); 
		while ((mipi_reg != 0x0) && (i < 10)) { 
			mipi_reg = mipi_csi2_get_error1(mipi_csi2_info); 
			i++; 
			msleep(10); 
		} 

		if (i >= 10) { 
			pr_err("mipi csi2 can not reveive data correctly!\n"); 
			return -1; 
		} 
	} 
err: 
	return retval; 
}

/*这一段代码就是通过mipi_csi2_get_error1函数来读取MIPI_CSI2_ERR1寄存器的值。这个寄存器表示MIPI控制寄存器中是否有错误发生,如果都没有错误的话,这个寄存器里面的值都应该是0.关于这个寄存器中每一位的含义就不分析了。*/


至此,ov5640_init_mode函数就大致分析完毕了,也就代表ioctl_dev_init函数分析完毕。这个ioctl_dev_init函数是mxc_v4l2_open函数中的最后一个函数。


对于应用程序中调用的其他ioctl函数,大致过程都是相似的,不会涉及到芯片上面寄存器的设置,大部分都是与ov5640_probe函数中sensor_data结构体相关,就不再分析他们了。


下面分析ov5640_init_mode函数中没有分析的三个函数:

ov5640_download_firmware
ov5640_change_mode_exposure_calc
ov5640_change_mode_direct

这三个函数是关于芯片设置最重要的函数。在ov5640_init_mode函数中,会根据第二个参数和第三个参数的不同来选择执行哪一路过程。当mode== ov5640_mode_INIT时,会直接执行ov5640_download_firmware函数来设置ov5640摄像头。


先来看ov5640_download_firmware函数:

/* download ov5640 settings to sensor through i2c */ 
static int ov5640_download_firmware(struct reg_value *pModeSetting, s32 ArySize) 
{ 
	register u32 Delay_ms = 0; 
	register u16 RegAddr = 0; 
	register u8 Mask = 0; 
	register u8 Val = 0; 
	u8 RegVal = 0; 
	int i, retval = 0; 

	for (i = 0; i < ArySize; ++i, ++pModeSetting) { 
		Delay_ms = pModeSetting->u32Delay_ms; 
		RegAddr = pModeSetting->u16RegAddr; 
		Val = pModeSetting->u8Val; 
		Mask = pModeSetting->u8Mask; 

		if (Mask) { 
			retval = ov5640_read_reg(RegAddr, &RegVal); 
			if (retval < 0) 
				goto err; 

			RegVal &= ~(u8)Mask; 
			Val &= Mask; 
			Val |= RegVal; 
		} 

		retval = ov5640_write_reg(RegAddr, Val); 
		if (retval < 0) 
			goto err; 

		if (Delay_ms) 
			msleep(Delay_ms); 
	} 
err: 
	return retval; 
}

先看这个函数的注释:通过I2C总线下载设置到ov5640摄像头中。意思就是我们在驱动中写好ov5640摄像头上面寄存器的配置以后,通过这个函数设置进去。

再来看这个函数,这个函数设置的核心是reg_value结构体,这个结构体如下所示:

struct reg_value { 
	u16 u16RegAddr; 
	u8 u8Val; 
	u8 u8Mask; 
	u32 u32Delay_ms; 
};

它就包含了所要设置一个寄存器所需要的所有元素:地址,值,掩码,延迟时间。以ov5640_init_setting_30fps_VGA[]为例:

static struct reg_value ov5640_init_setting_30fps_VGA[] = { 
	{0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 5}, {0x3008, 0x42, 0, 0}, 
	{0x3103, 0x03, 0, 0}, {0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0}, 
	{0x3034, 0x18, 0, 0}, {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0},
。。。。。。。。。。。。。。。。。。
};

通过这个函数,就能够把这个结构体数组里面的值都设置到ov5640中去。而这个结构体数组中寄存器的值怎么来的就是关键了,理论上可以参考芯片手册一位一位地设置,但是厂家应该会提供这个初始化数组的。



下面来看第二个函数ov5640_change_mode_exposure_calc

/* sensor changes between scaling and subsampling 
 * go through exposure calcualtion 
 */
static int ov5640_change_mode_exposure_calc(enum ov5640_frame_rate frame_rate, 
				enum ov5640_mode mode) 
{ 
	struct reg_value *pModeSetting = NULL; 
	s32 ArySize = 0; 
	u8 average; 
	int prev_shutter, prev_gain16; 
	int cap_shutter, cap_gain16; 
	int cap_sysclk, cap_HTS, cap_VTS; 
	int light_freq, cap_bandfilt, cap_maxband; 
	long cap_gain16_shutter; 
	int retval = 0; 

	/* check if the input mode and frame rate is valid */ 
	pModeSetting = 
		ov5640_mode_info_data[frame_rate][mode].init_data_ptr; 
	ArySize = 
		ov5640_mode_info_data[frame_rate][mode].init_data_size; 

	ov5640_data.pix.width = 
		ov5640_mode_info_data[frame_rate][mode].width; 
	ov5640_data.pix.height = 
		ov5640_mode_info_data[frame_rate][mode].height; 

	if (ov5640_data.pix.width == 0 || ov5640_data.pix.height == 0 || 
		pModeSetting == NULL || ArySize == 0) 
		return -EINVAL; 

	/* auto focus */ 
	/* OV5640_auto_focus();//if no af function, just skip it */ 

	/* turn off AE/AG */ 
	OV5640_turn_on_AE_AG(0); 

/*这个函数的意思是根据传入的参数的值来决定打开还是关闭autoAE/AG。关于这个的寄存器地址是0x3503,可以看出来,想要关闭auto模式的话,只需要将bit[1:0]设置为0x03即可,打开auto的话,将bit[1:0]清空即可。OV5640_turn_on_AE_AG这个函数正是这么做的。

ov5640_mipi.c分析_第7张图片
static void OV5640_turn_on_AE_AG(int enable) 
{ 
	u8 ae_ag_ctrl; 

	ov5640_read_reg(0x3503, &ae_ag_ctrl); 
	if (enable) { 
		/* turn on auto AE/AG */ 
		ae_ag_ctrl = ae_ag_ctrl & ~(0x03); 
	} else { 
		/* turn off AE/AG */ 
		ae_ag_ctrl = ae_ag_ctrl | 0x03; 
	} 
	ov5640_write_reg(0x3503, ae_ag_ctrl); 
}

*/


	/* read preview shutter */ 
	prev_shutter = OV5640_get_shutter(); 
	if ((binning_on()) && (mode != ov5640_mode_720P_1280_720) 
			&& (mode != ov5640_mode_1080P_1920_1080)) 
		prev_shutter *= 2; 

/*这个OV5640_get_shutter()函数就是去读取ov5640芯片上面的0x3500~0x3502地址里面的值,然后根据图示构造出Exposure的值,返回保存到prev_shutter中。在这里,shutter就是曝光时间的意思。

ov5640_mipi.c分析_第8张图片

在这里有一个函数:binning_on()

static bool binning_on(void) 
{ 
	u8 temp; 
	ov5640_read_reg(0x3821, &temp); 
	temp &= 0xfe; 
	if (temp) 
		return true; 
	else 
		return false; 
}

关于binning的概念和理,可以查看《sensorskippingand binning 模式

http://blog.csdn.net/sloan6/article/details/8242713

ov5640的芯片手册中,可以看到,与这个概念有关的寄存器位于0x3821bit[0],但是个人感觉这个函数写的有问题,只涉及0x3821bit[0]位,如果只打算比较bit[0]而不改变其他位的话,应该:

if(temp & 0x01),根本不需要temp&= 0xfe操作,经过这一个操作的话,假如其他位有不为0的,那么这个temp的值就不为0.

ov5640_mipi.c分析_第9张图片

*/

	/* read preview gain */ 
	prev_gain16 = OV5640_get_gain16(); 

/*看看OV5640_get_gain16()这个函数:

static int OV5640_get_gain16(void) 
{ 
	 /* read gain, 16 = 1x */ 
	int gain16; 
	u8 temp; 

	gain16 = ov5640_read_reg(0x350a, &temp) & 0x03; 
	gain16 = (gain16<<8) + ov5640_read_reg(0x350b, &temp); 

	return gain16; 
}

从这个函数中很显然就能猜出来这个previewgain保存在0x350a0x350b这两个寄存器中,看看芯片手册:

ov5640_mipi.c分析_第10张图片


最终这个函数读取这两个寄存器的值,然后保存在prev_gain16变量中。

*/

	/* get average */ 
	ov5640_read_reg(0x56a1, &average); 

/*这里直接使用ov5640_read_reg函数来读取0x56a1的值保存在&average中:

ov5640_mipi.c分析_第11张图片

*/


	/* turn off night mode for capture */ 
	OV5640_set_night_mode(); 

/*这个函数如下所示,这个函数与上面的binning_on()函数做比较的话,这个函数的设置是正确的,因为这个函数只想设置0x3a00bit[2]0,在不改变其他位的基础上面,通过mode&= 0xfb的形式是最好的。

static void OV5640_set_night_mode(void) 
{ 
	 /* read HTS from register settings */ 
	u8 mode; 

	ov5640_read_reg(0x3a00, &mode); 
	mode &= 0xfb; 
	ov5640_write_reg(0x3a00, mode); 
}

ov5640_mipi.c分析_第12张图片

*/


	/* turn off overlay */ 
	/* ov5640_write_reg(0x3022, 0x06);//if no af function, just skip it */ 

	OV5640_stream_off(); 

	/* Write capture setting */ 
	retval = ov5640_download_firmware(pModeSetting, ArySize); 
	if (retval < 0) 
		goto err; 

/*最终这个函数里面也是调用ov5640_download_firmware函数来将从ov5640_mode_info_data数组中获取到的ov5640中寄存器的值写到对应的寄存器中。*/

	/* read capture VTS */ 
	cap_VTS = OV5640_get_VTS(); 
	cap_HTS = OV5640_get_HTS(); 
	cap_sysclk = OV5640_get_sysclk(); 

/*先来看前两个函数,读取寄存器的值来获取verticalzisehorizontalsize的值。寄存器如下所示:

ov5640_mipi.c分析_第13张图片


这个OV5640_get_sysclk()函数有点复杂,以后再分析。

*/


	/* calculate capture banding filter */ 
	light_freq = OV5640_get_light_freq(); 
	if (light_freq == 60) { 
		/* 60Hz */ 
		cap_bandfilt = cap_sysclk * 100 / cap_HTS * 100 / 120; 
	} else { 
		/* 50Hz */ 
		cap_bandfilt = cap_sysclk * 100 / cap_HTS; 
	} 
	cap_maxband = (int)((cap_VTS - 4)/cap_bandfilt); 

	/* calculate capture shutter/gain16 */ 
	if (average > AE_low && average < AE_high) { 
		/* in stable range */ 
		cap_gain16_shutter = 
		  prev_gain16 * prev_shutter * cap_sysclk/prev_sysclk 
		  * prev_HTS/cap_HTS * AE_Target / average; 
	} else { 
		cap_gain16_shutter = 
		  prev_gain16 * prev_shutter * cap_sysclk/prev_sysclk 
		  * prev_HTS/cap_HTS; 
	} 

	/* gain to shutter */ 
	if (cap_gain16_shutter < (cap_bandfilt * 16)) { 
		/* shutter < 1/100 */ 
		cap_shutter = cap_gain16_shutter/16; 
		if (cap_shutter < 1) 
			cap_shutter = 1; 

		cap_gain16 = cap_gain16_shutter/cap_shutter; 
		if (cap_gain16 < 16) 
			cap_gain16 = 16; 
	} else { 
		if (cap_gain16_shutter > 
				(cap_bandfilt * cap_maxband * 16)) { 
			/* exposure reach max */ 
			cap_shutter = cap_bandfilt * cap_maxband; 
			cap_gain16 = cap_gain16_shutter / cap_shutter; 
		} else { 
			/* 1/100 < (cap_shutter = n/100) =< max */ 
			cap_shutter = 
			  ((int) (cap_gain16_shutter/16 / cap_bandfilt)) 
			  *cap_bandfilt; 
			cap_gain16 = cap_gain16_shutter / cap_shutter; 
		} 
	} 

	/* write capture gain */ 
	OV5640_set_gain16(cap_gain16); 

	/* write capture shutter */ 
	if (cap_shutter > (cap_VTS - 4)) { 
		cap_VTS = cap_shutter + 4; 
		OV5640_set_VTS(cap_VTS); 
	} 
	OV5640_set_shutter(cap_shutter); 

	OV5640_stream_on(); 

err: 
	return retval; 
}

/*后面这些计算应该都是摄像头中定义的,有点看不懂,以后再分析吧*/


最后看看ov5640_change_mode_direct函数:

static int ov5640_change_mode_direct(enum ov5640_frame_rate frame_rate, 
				enum ov5640_mode mode) 
{ 
	struct reg_value *pModeSetting = NULL; 
	s32 ArySize = 0; 
	int retval = 0; 

	/* check if the input mode and frame rate is valid */ 
	pModeSetting = 
		ov5640_mode_info_data[frame_rate][mode].init_data_ptr; 
	ArySize = 
		ov5640_mode_info_data[frame_rate][mode].init_data_size; 

	ov5640_data.pix.width = 
		ov5640_mode_info_data[frame_rate][mode].width; 
	ov5640_data.pix.height = 
		ov5640_mode_info_data[frame_rate][mode].height; 

	if (ov5640_data.pix.width == 0 || ov5640_data.pix.height == 0 || 
		pModeSetting == NULL || ArySize == 0) 
		return -EINVAL; 

	/* turn off AE/AG */ 
	OV5640_turn_on_AE_AG(0); 

	OV5640_stream_off(); 

	/* Write capture setting */ 
	retval = ov5640_download_firmware(pModeSetting, ArySize); 
	if (retval < 0) 
		goto err; 

	OV5640_stream_on(); 

	OV5640_turn_on_AE_AG(1); 

err: 
	return retval; 
}

经过上一个函数的分析,这个函数看起来就相当简单了。



3.OV5640_get_sysclk 函数分析

static int OV5640_get_sysclk(void) 
{ 
	 /* calculate sysclk */ 
	int xvclk = ov5640_data.mclk / 10000; 
	int temp1, temp2; 
	int Multiplier, PreDiv, VCO, SysDiv, Pll_rdiv; 
	int Bit_div2x = 1, sclk_rdiv, sysclk; 
	u8 temp; 

/*ov5640_data.mclk的值是在ov5640_probe函数中同dts文件中读取的,为24000000.*/

	int sclk_rdiv_map[] = {1, 2, 4, 8}; 

	temp1 = ov5640_read_reg(0x3034, &temp); 
	temp2 = temp1 & 0x0f; 
	if (temp2 == 8 || temp2 == 10) 
		Bit_div2x = temp2 / 2; 

/*读取0x3034寄存器的值,取低4位除以2后保存在Bit_div2x中。从芯片手册中可以看出来,0x3034的低4位是MIPIbit mode,那么为什么还要除以2呢?在《ov5640_PLL_diagram.jpg》图片的左下角有这样一句话:note6MIPISCLK= (4 or 5) * PCLK if 2 lanes;= (8 or 10) * PCLK if 1lane。再看Bit_div2xPLLdiagram中对应的模块是BITdivider,而现在使用的是2lanes模式,所以需要除以2.

*/

	temp1 = ov5640_read_reg(0x3035, &temp); 
	SysDiv = temp1>>4; 
	if (SysDiv == 0) 
		SysDiv = 16; 

/*读取0x3035寄存器的高4位保存在SysDiv中,看芯片手册中的解释是系统时钟分频器。在《ov5640_PLL_diagram.jpg》图片中对应SYSdivider0模块。


*/


	temp1 = ov5640_read_reg(0x3036, &temp); 
	Multiplier = temp1; 

/*读取0x3036寄存器的值保存在Multiplier中,看芯片手册中的解释是PLL倍频器。在《ov5640_PLL_diagram.jpg》图片中对应multiplier模块。


*/


	temp1 = ov5640_read_reg(0x3037, &temp); 
	PreDiv = temp1 & 0x0f; 
	Pll_rdiv = ((temp1 >> 4) & 0x01) + 1; 

/*读取0x3037寄存器的低4位保存在PreDiv中,读取0x3037寄存器的bit[4]保存在Pll_rdiv中。

PreDiv在《ov5640_PLL_diagram.jpg》图片中对应pre-divider模块,Pll_rdiv在《ov5640_PLL_diagram.jpg》图片中对应PLLR divider模块,因为上图中的解释是:如果bit[4]1的话,就分成2份,所以在代码中将从bit[4]中读取出来的值加1*/

	temp1 = ov5640_read_reg(0x3108, &temp); 
	temp2 = temp1 & 0x03; 
	sclk_rdiv = sclk_rdiv_map[temp2]; 

/*读取0x3108寄存器的低2位保存在temp2中,然后将这个temp2作为下标来从这个数组里面取值:

intsclk_rdiv_map[] = {1, 2, 4, 8};

为什么要从这个数组里面取值呢?看芯片手册中,SCLKpll_clk1/2,1/4, 1/8。没法用两位来表示8,所以选择这种方式来选取值。

ov5640_mipi.c分析_第14张图片

*/


	VCO = xvclk * Multiplier / PreDiv; 

/*这个VCO压控振荡VoltageControlledOscillatorPLL锁相环)的组成部分,在PLL中,一般是先分频,然后再经过VCO增频,这里先计算的是VCO的输出。在ov5640_PLL_diagram.jpg》中就是PLL1后的输出。*/

	sysclk = VCO / SysDiv / Pll_rdiv * 2 / Bit_div2x / sclk_rdiv; 

/*剩下的就是计算过程,根据图《ov5640_PLL_diagram.jpg》就可以写出这个计算过程。*/

	return sysclk; 
}

ov5640_PLL_diagram.jpg》是我们网上看到的一个有关ov5640的时钟控制图,我下载的芯片手册都没有这个图,所以一直对这个函数不理解,通过这个图,这个函数的设置过程就很清楚了,《ov5640_PLL_diagram.jpg》图片如下所示:

ov5640_mipi.c分析_第15张图片

经过上面的步骤就可以得出OV5640的系统时钟参数。



参考:

CMOSSensor的调试经验分享

http://blog.csdn.net/yapingmcu/article/details/37817727


你可能感兴趣的:(i.MX6-IPU子系统,IPU,NXP,arm,imx6,内核)