目录
概要
恩智浦提供的内核源码中已经包含了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对应的函数