移植ov5640摄像头到imx6ull开发板(一)

目录

概要

struct ov5640{}结构体

ov5640_probe函数

总结


概要

恩智浦提供的内核源码中已经包含了ov5640的摄像头驱动代码ov5640.c和mx6s_capture.c两个文件,ov5640.c用于初始化摄像头配置,mx6s_capture.c用于捕捉画面,代码位于linux-imx-rel_imx_4.1.15_2.1.0_ga/drivers/media/paltform/mxc/

实验平台:正点原子imx6ull-mini实验板

摄像头型号:ov5640

显示屏:正点原子1024*600 LCD

如果使用的是正点原子的屏幕,想要驱动摄像头,则需要在驱动中进行相应的修改来适配屏幕分辨率。因此分析驱动代码,方便我们移植摄像头驱动。这篇文章主要是分析一下ov5640驱动中的probe函数。

struct ov5640{}结构体

在分析代码之前,需要先理清几个十分重要的结构体。

struct ov5640 {
	struct v4l2_subdev		subdev;
	struct i2c_client *i2c_client;
	struct v4l2_pix_format pix;
	const struct ov5640_datafmt	*fmt;
	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;
	u8 mclk_source;
	struct clk *sensor_clk;
	int csi;

	void (*io_init)(void);
};

首先是struct v4l2_subdev subdev;此结构体描述了摄像头设备的一些信息,如名称、设备树节点、操作函数等。如下

struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_entity entity;
#endif
	struct list_head list;
	struct module *owner;
	bool owner_v4l2_dev;
	u32 flags;
	struct v4l2_device *v4l2_dev;
	const struct v4l2_subdev_ops *ops;
	/* Never call these internal ops from within a driver! */
	const struct v4l2_subdev_internal_ops *internal_ops;
	/* The control handler of this subdev. May be NULL. */
	struct v4l2_ctrl_handler *ctrl_handler;
	/* name must be unique */
	char name[V4L2_SUBDEV_NAME_SIZE];
	/* can be used to group similar subdevs, value is driver-specific */
	u32 grp_id;
	/* pointer to private data */
	void *dev_priv;
	void *host_priv;
	/* subdev device node */
	struct video_device *devnode;
	/* pointer to the physical device, if any */
	struct device *dev;
	/* Links this subdev to a global subdev_list or @notifier->done list. */
	struct list_head async_list;
	/* Pointer to respective struct v4l2_async_subdev. */
	struct v4l2_async_subdev *asd;
	/* Pointer to the managing notifier. */
	struct v4l2_async_notifier *notifier;
	/* common part of subdevice platform data */
	struct v4l2_subdev_platform_data *pdata;
};

 struct i2c_client:描述i2c设备,ov5640摄像头通过i2c协议发收数据

struct v4l2_pix_format pix:输出图片、像素等信息,例如图片的长宽,像素点的格式以及逐行输出像素点数据等等。

struct v4l2_pix_format {
	__u32         		width;
	__u32			height;
	__u32			pixelformat;
	__u32			field;		/* enum v4l2_field */
	__u32            	bytesperline;	/* for padding, zero if unused */
	__u32          		sizeimage;
	__u32			colorspace;	/* enum v4l2_colorspace */
	__u32			priv;		/* private data, depends on pixelformat */
	__u32			flags;		/* format flags (V4L2_PIX_FMT_FLAG_*) */
	__u32			ycbcr_enc;	/* enum v4l2_ycbcr_encoding */
	__u32			quantization;	/* enum v4l2_quantization */
};

enum v4l2_colorspace:像素点格式,包含rgb、jpeg等格式。

enum v4l2_colorspace {
	/* SMPTE 170M: used for broadcast NTSC/PAL SDTV */
	V4L2_COLORSPACE_SMPTE170M     = 1,

	/* Obsolete pre-1998 SMPTE 240M HDTV standard, superseded by Rec 709 */
	V4L2_COLORSPACE_SMPTE240M     = 2,

	/* Rec.709: used for HDTV */
	V4L2_COLORSPACE_REC709        = 3,

	/*
	 * Deprecated, do not use. No driver will ever return this. This was
	 * based on a misunderstanding of the bt878 datasheet.
	 */
	V4L2_COLORSPACE_BT878         = 4,

	/*
	 * NTSC 1953 colorspace. This only makes sense when dealing with
	 * really, really old NTSC recordings. Superseded by SMPTE 170M.
	 */
	V4L2_COLORSPACE_470_SYSTEM_M  = 5,

	/*
	 * EBU Tech 3213 PAL/SECAM colorspace. This only makes sense when
	 * dealing with really old PAL/SECAM recordings. Superseded by
	 * SMPTE 170M.
	 */
	V4L2_COLORSPACE_470_SYSTEM_BG = 6,

	/*
	 * Effectively shorthand for V4L2_COLORSPACE_SRGB, V4L2_YCBCR_ENC_601
	 * and V4L2_QUANTIZATION_FULL_RANGE. To be used for (Motion-)JPEG.
	 */
	V4L2_COLORSPACE_JPEG          = 7,

	/* For RGB colorspaces such as produces by most webcams. */
	V4L2_COLORSPACE_SRGB          = 8,

	/* AdobeRGB colorspace */
	V4L2_COLORSPACE_ADOBERGB      = 9,

	/* BT.2020 colorspace, used for UHDTV. */
	V4L2_COLORSPACE_BT2020        = 10,
};

struct v4l2_captureparm streamcap:描述了摄像头采集相关的一些函数,例如采集帧率等

struct v4l2_captureparm {
	__u32		   capability;	  /*  Supported modes */
	__u32		   capturemode;	  /*  Current mode */
	struct v4l2_fract  timeperframe;  /*  Time per frame in seconds */
	__u32		   extendedmode;  /*  Driver-specific extensions */
	__u32              readbuffers;   /*  # of buffers for read */
	__u32		   reserved[4];
};

以上就是比较重要的几个结构体,可以看出ov5640摄像头驱动的编写是基于v4l2视频驱动框架,并且内核已经为我们提供好了这些结构体,在驱动中只需要对其进行初始化即可。

ov5640_probe函数

在设备与驱动匹配完成后,便进入probe函数,对ov5640摄像头驱动的配置就是在probe函数中完成。这里简单说一下匹配流程

static const struct i2c_device_id ov5640_id[] = {
	{"ov5640", 0},
	{},
};

MODULE_DEVICE_TABLE(i2c, ov5640_id);

static struct i2c_driver ov5640_i2c_driver = {
	.driver = {
		  .owner = THIS_MODULE,
		  .name  = "ov5640",
		  },
	.probe  = ov5640_probe,
	.remove = ov5640_remove,
	.id_table = ov5640_id,
};

初始化i2c_driver结构体,其中就包含了probe和remove两个最为重要的函数,这里设备与驱动的匹配方式是通过ID表匹配,匹配成功后执行probe函数。

我们开始分析probe函数中做了什么,这里列上ov5640_probe函数以及设备树的摄像头节点信息。

static int ov5640_probe(struct i2c_client *client,
			const struct i2c_device_id *id)					//初始化摄像头
{
	struct pinctrl *pinctrl;
	struct device *dev = &client->dev;
	int retval;
	u8 chip_id_high, chip_id_low;

	/* ov5640 pinctrl */
	pinctrl = devm_pinctrl_get_select_default(dev);
	if (IS_ERR(pinctrl)) {
		dev_err(dev, "setup pinctrl failed\n");
		return PTR_ERR(pinctrl);
	}

	/* request power down pin */
	pwn_gpio = of_get_named_gpio(dev->of_node, "pwn-gpios", 0);//获取电源引脚
	if (!gpio_is_valid(pwn_gpio)) {
		dev_err(dev, "no sensor pwdn pin available\n");
		return -ENODEV;
	}
	retval = devm_gpio_request_one(dev, pwn_gpio, GPIOF_OUT_INIT_HIGH,
					"ov5640_pwdn");								//设置电源引脚电平
	if (retval < 0)
		return retval;

	/* request reset pin */
	rst_gpio = of_get_named_gpio(dev->of_node, "rst-gpios", 0); //获取reset引脚
	if (!gpio_is_valid(rst_gpio)) {
		dev_err(dev, "no sensor reset pin available\n");
		return -EINVAL;
	}
	retval = devm_gpio_request_one(dev, rst_gpio, GPIOF_OUT_INIT_HIGH,
					"ov5640_reset");							//设置reset引脚电平
	if (retval < 0)
		return retval;

	/* Set initial values for the sensor struct. */
	memset(&ov5640_data, 0, sizeof(ov5640_data));				//清零ov5640结构体		
	ov5640_data.sensor_clk = devm_clk_get(dev, "csi_mclk");		//获取csi时钟
	if (IS_ERR(ov5640_data.sensor_clk)) {
		dev_err(dev, "get mclk failed\n");
		return PTR_ERR(ov5640_data.sensor_clk);
	}

	retval = of_property_read_u32(dev->of_node, "mclk",
					&ov5640_data.mclk);							//获取设备树摄像头的时钟频率描述		
	if (retval) {
		dev_err(dev, "mclk frequency is invalid\n");
		return retval;
	}

	retval = of_property_read_u32(dev->of_node, "mclk_source",	//获取设备树中摄像头的时钟源描述
					(u32 *) &(ov5640_data.mclk_source));
	if (retval) {
		dev_err(dev, "mclk_source invalid\n");
		return retval;
	}

	retval = of_property_read_u32(dev->of_node, "csi_id",		//获取设备树中摄像头的id描述
					&(ov5640_data.csi));
	if (retval) {
		dev_err(dev, "csi_id invalid\n");
		return retval;
	}

	/* Set mclk rate before clk on */
	ov5640_set_clk_rate();										//设置时钟频率

	clk_prepare_enable(ov5640_data.sensor_clk);										

	ov5640_data.io_init = ov5640_reset;							//重置摄像头
	ov5640_data.i2c_client = client;							//i2c从机设备
	ov5640_data.pix.pixelformat = V4L2_PIX_FMT_RGB565;			//RGB565格式
	ov5640_data.pix.width = 640;								//图像宽度
	ov5640_data.pix.height = 480;								//图像高度
	ov5640_data.pix.field = V4L2_FIELD_NONE;					//逐行输出像素数据			
	ov5640_data.pix.colorspace = V4L2_COLORSPACE_SRGB;			//[red,green,blue]数组,对应RGB566格式
	ov5640_data.streamcap.capability = V4L2_MODE_HIGHQUALITY |	//capability需要包含V4L2_CAP_TIMEPERFRAME字段,得以让应用调用
					   V4L2_CAP_TIMEPERFRAME;
	ov5640_data.streamcap.capturemode = V4L2_CAP_TIMEPERFRAME;
	ov5640_data.streamcap.timeperframe.denominator = DEFAULT_FPS;//默认设置帧率为30FPS
	ov5640_data.streamcap.timeperframe.numerator = 1;			//	numerator/denominator表示图像采集的周期(采集一帧图像需要多少秒)

	ov5640_regulator_enable(&client->dev);		//电源管理系统				

	ov5640_reset();		//复位摄像头

	ov5640_power_down(0);	//电源控制

	retval = ov5640_read_reg(OV5640_CHIP_ID_HIGH_BYTE, &chip_id_high);//读取寄存器地址0X300A,得到摄像头ID高八位字节
	if (retval < 0 || chip_id_high != 0x56) {
		clk_disable_unprepare(ov5640_data.sensor_clk);
		pr_warning("camera ov5640 is not found\n");
		return -ENODEV;
	}
	retval = ov5640_read_reg(OV5640_CHIP_ID_LOW_BYTE, &chip_id_low);	//读取寄存器地址0X300B,得到摄像头ID低八位字节
	if (retval < 0 || chip_id_low != 0x40) {
		clk_disable_unprepare(ov5640_data.sensor_clk);
		pr_warning("camera ov5640 is not found\n");
		return -ENODEV;
	}

	retval = init_device();												//初始化摄像头
	if (retval < 0) {
		clk_disable_unprepare(ov5640_data.sensor_clk);					
		pr_warning("camera ov5640 init failed\n");
		ov5640_power_down(1);
		return retval;
	}

	clk_disable(ov5640_data.sensor_clk);			//关闭摄像头

	v4l2_i2c_subdev_init(&ov5640_data.subdev, client, &ov5640_subdev_ops);	//v4l2_subdev初始化

	retval = v4l2_async_register_subdev(&ov5640_data.subdev);
	if (retval < 0)
		dev_err(&client->dev,
					"%s--Async register failed, ret=%d\n", __func__, retval);

	pr_info("camera ov5640, is found\n");
	return retval;
}

设备树:

ov5640: ov5640@3c {
		compatible = "ovti,ov5640";
		reg = <0x3c>;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_csi1
						&csi_pwn_rst>;
		clocks = <&clks IMX6UL_CLK_CSI>;
		clock-names = "csi_mclk";
		pwn-gpios = <&gpio1 4 1>;
		rst-gpios = <&gpio1 2 0>;
		csi_id = <0>;
		mclk = <24000000>;
		mclk_source = <0>;
		status = "okay";
		port {
			ov5640_ep: endpoint {
				remote-endpoint = <&csi1_ep>;
			};
		};
	};
};

10:pinctrl = devm_pinctrl_get_select_default(dev);//选择”default"状态下的引脚

17:pwn_gpio = of_get_named_gpio(dev->of_node, "pwn-gpios", 0);//获取电源引脚

22.retval = devm_gpio_request_one(dev, pwn_gpio, GPIOF_OUT_INIT_HIGH,"ov5640_pwdn");                                                                                                          //设置电源引脚电平

28:rst_gpio = of_get_named_gpio(dev->of_node, "rst-gpios", 0); //获取reset引脚

33:retval = devm_gpio_request_one(dev, rst_gpio, GPIOF_OUT_INIT_HIGH,"ov5640_reset");                                                                                                                    //设置reset引脚电平

40:ov5640_data.sensor_clk = devm_clk_get(dev, "csi_mclk");        //打开csi时钟

46:retval = of_property_read_u32(dev->of_node, "mclk",       &ov5640_data.mclk);                                                                 //读取设备树摄像头节点中的时钟频率描述,mclk = <24000000>;

53:retval = of_property_read_u32(dev->of_node, "mclk_source", (u32 *) &(ov5640_data.mclk_source));        //获取设备树中摄像头的时钟源描述mclk_source = <0>;

60:retval = of_property_read_u32(dev->of_node, "csi_id", &(ov5640_data.csi));

                                                        //获取设备树中摄像头的id描述 csi_id = <0>;

68:ov5640_set_clk_rate();                                                                     //设置时钟频率

我们进到ov5640_set_clk_rate函数中看一下

static int ov5640_set_clk_rate(void)
{
	u32 tgt_xclk;	/* target xclk */
	int ret;

	/* 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);
	ret = clk_set_rate(ov5640_data.sensor_clk, ov5640_data.mclk);
	if (ret < 0)
		pr_debug("set rate filed, rate=%d\n", ov5640_data.mclk);
	return ret;
}

函数主要保证时钟频率范围在OV5640_XCLK_MIN和OV5640_XCLK_MAX之间,宏定义如下

#define OV5640_XCLK_MIN 6000000

#define OV5640_XCLK_MAX 24000000

从72~83便是开始初始化之前所说几个关于v4l2驱动框架的重要结构体,v4l2需要对应用层提供接口。

首先72:ov5640_data.io_init = ov5640_reset;                            //重置摄像头

static inline void ov5640_reset(void)
{
	/* camera reset */
	gpio_set_value_cansleep(rst_gpio, 1);

	/* camera power down */
	gpio_set_value_cansleep(pwn_gpio, 1);
	msleep(10);//msleep(5);
	gpio_set_value_cansleep(pwn_gpio, 0);
	msleep(10);//msleep(5);
	gpio_set_value_cansleep(rst_gpio, 0);
	msleep(5);//msleep(1);
	gpio_set_value_cansleep(rst_gpio, 1);
	msleep(10); // msleep(5);
	gpio_set_value_cansleep(pwn_gpio, 1);
}

主要内容是根据硬件手册配置电源引脚和reset引脚重置摄像头

74:ov5640_data.pix.pixelformat = V4L2_PIX_FMT_RGB565;         //配置像素点数据格式为RGB565格式

75:ov5640_data.pix.width = 640;                               //输出图像宽度

76:ov5640_data.pix.height = 480;                              //输出图像高度

77:ov5640_data.pix.field = V4L2_FIELD_NONE;                   //逐行输出像素数据,例如上面设置了宽度为640,那每次就输出640个像素点数据。

78: ov5640_data.pix.colorspace = V4L2_COLORSPACE_SRGB;         //[red,green,blue]数组,对应RGB565格式  

79:ov5640_data.streamcap.capability = V4L2_MODE_HIGHQUALITY | V4L2_CAP_TIMEPERFRAME;//capability需要包含V4L2_CAP_TIMEPERFRAME字段,得以让应用层调用

82:ov5640_data.streamcap.timeperframe.denominator = DEFAULT_FPS;//设置帧率为30FPS

有宏定义:#define DEFAULT_FPS 30

83:ov5640_data.streamcap.timeperframe.numerator = 1;          //  numerator/denominator表示图像采集的周期(采集一帧图像需要多少秒)

85:ov5640_regulator_enable(&client->dev);     //电源管理系统,但这里好像并没有用到。

91~102,读取0x300A和0x300B寄存器,得到摄像头ID高八位和低八位,并判断是否与连接设备匹配

104:retval = init_device();    //init_device函数中的ov5640_init_mode是初始化摄像头的主要函数

static int ov5640_init_mode(void)//初始化OV5640摄像头模式
{
	struct reg_value *pModeSetting = NULL;
	int ArySize = 0, retval = 0;

	ov5640_soft_reset();//软件复位

	pModeSetting = ov5640_global_init_setting;
	ArySize = ARRAY_SIZE(ov5640_global_init_setting);
	retval = ov5640_download_firmware(pModeSetting, ArySize);//根据芯片手册配置ov5640寄存器
	if (retval < 0)
		goto err;

	pModeSetting = ov5640_init_setting_30fps_VGA;
	ArySize = ARRAY_SIZE(ov5640_init_setting_30fps_VGA);
	retval = ov5640_download_firmware(pModeSetting, ArySize);
	if (retval < 0)
		goto err;

	/* change driver capability to 2x according to validation board.
	 * if the image is not stable, please increase the driver strength.
	 */
	ov5640_driver_capability(1);			//驱动能力
	ov5640_set_bandingfilter();
	ov5640_set_AE_target(AE_Target);
	ov5640_set_night_mode(night_mode);

	/* skip 9 vysnc: start capture at 10th vsync */
	msleep(300);

	/* turn off night mode */
	night_mode = 0;

	/* auto focus */
	ov5640_auto_focus();		//自动对焦
err:
	return retval;
}

这里需要根据厂家提供的ov5640硬件手册配置寄存器,感兴趣的可以看ov5640手册。

114:v4l2_i2c_subdev_init(&ov5640_data.subdev, client, &ov5640_subdev_ops); 

比较重要的一个函数,我们一般在应用层使用v4l2框架调用ioctl的命令来打开,关闭或者配置摄像头,而ov5640摄像头是通过i2c协议传输数据完成初始化和配置摄像头的一些关键参数。所以这个函数的主要作用就是建立i2c和v4l2下subdevice的联系。

probe函数到这里基本就完成了对ov5640的初始化设置。当然例如图片宽度、长度、帧率、以及像素点格式这些并不是设置死的。代码中列出了大多模式下的寄存器配置。我在这就不一一列举了。

在代码中是通过ov5640_change_mode_direct、ov5640_change_mode、ov5640_change_mode_exposure_calc等函数来改变摄像头的设置。

在mx6s_capture.c代码中则是提供了应用层调用ioctl的各类函数,例如设置分辨率,像素格式等。

总结

ov5640摄像头驱动是基于v4l2框架进行编写,这样应用层才能提供对应的接口函数。

ov5640摄像头驱动中的主要工作:

1.初始化Linux内核提供有关v4l2驱动框架的结构体

2.通过i2c协议读写寄存器,配置摄像头

3.提供应用层ioctl对应的函数

  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值