目录
static struct ov5640_mode_info ov5640_mode_info_data[2][ov5640_mode_MAX + 1]
概要
在上一篇文章中,分析了驱动代码中probe函数,probe函数主要工作是初始化摄像头。但想要移植ov5640摄像头驱动,还需要梳理一下驱动代码中几个重要函数和结构体之间的关系,以及函数是如何设置参数条件的。
重要参数结构体
enum ov5640_mode
如下枚举类型,枚举了驱动代码中摄像头所支持的模式,可以看到并不支持1024*600分辨率,因此后续的移植需要添加1024*600分辨率编号
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
};
static struct ov5640_mode_info ov5640_mode_info_data[2][ov5640_mode_MAX + 1]
这个结构体比较庞大,ov5640_mode_info_data[2]将总设备分为两类,一类是15fps,一类是30fps,之后他们各自又涵盖了所有支持设备的LCD分辨率,输出宽度,输出高度等信息(不包括像素点格式)
static struct ov5640_mode_info ov5640_mode_info_data[2][ov5640_mode_MAX + 1] = {
{
{ov5640_mode_VGA_640_480, 640, 480,
ov5640_setting_15fps_VGA_640_480,
ARRAY_SIZE(ov5640_setting_15fps_VGA_640_480)},
{ov5640_mode_QVGA_320_240, 320, 240,
ov5640_setting_15fps_QVGA_320_240,
ARRAY_SIZE(ov5640_setting_15fps_QVGA_320_240)},
{ov5640_mode_NTSC_720_480, 720, 480,
ov5640_setting_15fps_NTSC_720_480,
ARRAY_SIZE(ov5640_setting_15fps_NTSC_720_480)},
{ov5640_mode_PAL_720_576, 720, 576,
ov5640_setting_15fps_PAL_720_576,
ARRAY_SIZE(ov5640_setting_15fps_PAL_720_576)},
{ov5640_mode_720P_1280_720, 1280, 720,
ov5640_setting_15fps_720P_1280_720,
ARRAY_SIZE(ov5640_setting_15fps_720P_1280_720)},
{ov5640_mode_1080P_1920_1080, 1920, 1080,
ov5640_setting_15fps_1080P_1920_1080,
ARRAY_SIZE(ov5640_setting_15fps_1080P_1920_1080)},
{ov5640_mode_QSXGA_2592_1944, 2592, 1944,
ov5640_setting_15fps_QSXGA_2592_1944,
ARRAY_SIZE(ov5640_setting_15fps_QSXGA_2592_1944)},
{ov5640_mode_QCIF_176_144, 176, 144,
ov5640_setting_15fps_QCIF_176_144,
ARRAY_SIZE(ov5640_setting_15fps_QCIF_176_144)},
{ov5640_mode_XGA_1024_768, 1024, 768,
ov5640_setting_15fps_XGA_1024_768,
ARRAY_SIZE(ov5640_setting_15fps_XGA_1024_768)},
},
{
{ov5640_mode_VGA_640_480, 640, 480,
ov5640_setting_30fps_VGA_640_480,
ARRAY_SIZE(ov5640_setting_30fps_VGA_640_480)},
{ov5640_mode_QVGA_320_240, 320, 240,
ov5640_setting_30fps_QVGA_320_240,
ARRAY_SIZE(ov5640_setting_30fps_QVGA_320_240)},
{ov5640_mode_NTSC_720_480, 720, 480,
ov5640_setting_30fps_NTSC_720_480,
ARRAY_SIZE(ov5640_setting_30fps_NTSC_720_480)},
{ov5640_mode_PAL_720_576, 720, 576,
ov5640_setting_30fps_PAL_720_576,
ARRAY_SIZE(ov5640_setting_30fps_PAL_720_576)},
{ov5640_mode_720P_1280_720, 1280, 720,
ov5640_setting_30fps_720P_1280_720,
ARRAY_SIZE(ov5640_setting_30fps_720P_1280_720)},
{ov5640_mode_1080P_1920_1080, 0, 0, NULL, 0},
{ov5640_mode_QSXGA_2592_1944, 0, 0, NULL, 0},
{ov5640_mode_QCIF_176_144, 176, 144,
ov5640_setting_30fps_QCIF_176_144,
ARRAY_SIZE(ov5640_setting_30fps_QCIF_176_144)},
{ov5640_mode_XGA_1024_768, 1024, 768,
ov5640_setting_30fps_XGA_1024_768,
ARRAY_SIZE(ov5640_setting_30fps_XGA_1024_768)},
},
};
struct ov5640_mode_info {
enum ov5640_mode mode;
u32 width;
u32 height;
struct reg_value *init_data_ptr;
u32 init_data_size;
};
struct reg_value
将要发送的数据打包成一个结构体,由写入寄存器的地址,写入的值等组成。嵌入在struct ov5640_mode_info结构体中
struct reg_value {
u16 u16RegAddr;
u8 u8Val;
u8 u8Mask;
u32 u32Delay_ms;
};
综上:struct ov5640_mode_info ov5640_mode_info_data[2][ov5640_mode_MAX + 1]描述了除像素格式之外所有设备参数的信息,这个结构体中又包含了struct reg_value和enum ov5640_mode等数据类型,说白了就是在套娃。因此如果需要添加自己的设备信息,则需要在enum ov5640_mode和struct ov5640_mode_info ov5640_mode_info_data[2][ov5640_mode_MAX + 1]添加适配的参数即可。
像素格式结构体
struct ov5640_datafmt结构体描述了输出像素格式,结构体中的code与enum v4l2_colorspace枚举的像素格式相对应。原驱动代码只列出了YUYV和JPEG等格式,后续移植摄像头需在结构体中添加rgb模式
struct ov5640_datafmt {
u32 code;
enum v4l2_colorspace colorspace;
};
static const struct ov5640_datafmt ov5640_colour_fmts[] = {
{MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG},
};
设置参数的函数
输出分辨率设置
ov5640_change_mode_direct
原驱动代码中对函数有如下注释,/* change to or back to subsampling mode set the mode directly * image size below 1280 * 960 is subsampling mode */,设置图像分辨率低于1280*960的采样方式。当然驱动代码中也存在大于1280 * 960的设置函数:ov5640_change_mode_exposure_calc
/* change to or back to subsampling mode set the mode directly
* image size below 1280 * 960 is subsampling mode */
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;
if (mode > ov5640_mode_MAX || mode < ov5640_mode_MIN) {
pr_err("Wrong ov5640 mode detected!\n");
return -1;
}
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;
/* set ov5640 to subsampling mode */
retval = ov5640_download_firmware(pModeSetting, ArySize);
/* turn on AE AG for subsampling mode, in case the firmware didn't */
ov5640_turn_on_AE_AG(1);
/* calculate banding filter */
ov5640_set_bandingfilter();
/* set AE target */
ov5640_set_AE_target(AE_Target);
/* update night mode setting */
ov5640_set_night_mode(night_mode);
/* skip 9 vysnc: start capture at 10th vsync */
if (mode == ov5640_mode_XGA_1024_768 && frame_rate == ov5640_30_fps) {
pr_warning("ov5640: actual frame rate of XGA is 22.5fps\n");
/* 1/22.5 * 9*/
msleep(400);
return retval;
}
if (frame_rate == ov5640_15_fps) {
/* 1/15 * 9*/
msleep(600);
} else if (frame_rate == ov5640_30_fps) {
/* 1/30 * 9*/
msleep(300);
}
return retval;
}
此函数后续移植过程中不用改动,但还是看一下它是如何设置参数的。
if (mode > ov5640_mode_MAX || mode < ov5640_mode_MIN) 首先判断LCD分辨率是否支持的范围内。之后ov5640_mode_info_data就排上用场了
pModeSetting = ov5640_mode_info_data[frame_rate][mode].init_data_ptr,frame_rate表示15fps或30fps即设置输出帧率,mode:在enum ov5640_mode结构体中选择输出LCD分辨率,init_data_ptr写入ov5640传感器的值即配置寄存器。后续移植的一个重要工作就是配置支持1024*600分辨率的寄存器组,类似于如下代码:
static struct reg_value ov5640_setting_30fps_XGA_1024_768[] = {
{0x3c07, 0x08, 0, 0}, {0x3820, 0x47, 0, 0}, {0x3821, 0x07, 0, 0},
{0x3814, 0x31, 0, 0}, {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0},
{0x3801, 0x00, 0, 0}, {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0},
{0x3804, 0x0a, 0, 0}, {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0},
{0x3807, 0x9b, 0, 0}, {0x3808, 0x04, 0, 0}, {0x3809, 0x00, 0, 0},
{0x380a, 0x03, 0, 0}, {0x380b, 0x00, 0, 0}, {0x380c, 0x07, 0, 0},
{0x380d, 0x68, 0, 0}, {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0},
{0x3813, 0x06, 0, 0}, {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0},
{0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x0b, 0, 0},
{0x3a03, 0x88, 0, 0}, {0x3a14, 0x0b, 0, 0}, {0x3a15, 0x88, 0, 0},
{0x4004, 0x02, 0, 0}, {0x3002, 0x1c, 0, 0}, {0x3006, 0xc3, 0, 0},
{0x4713, 0x03, 0, 0}, {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0},
{0x460c, 0x20, 0, 0}, {0x4837, 0x22, 0, 0}, {0x3824, 0x01, 0, 0},
{0x5001, 0xa3, 0, 0}, {0x3034, 0x1a, 0, 0}, {0x3035, 0x21, 0, 0},
{0x3036, 0x69, 0, 0}, {0x3037, 0x13, 0, 0},
};
当然这是1024*768分辨率的寄存器组,并不符合我们的要求,后续需要根据硬件手册配置寄存器
ov5640_download_firmware,ov5640_turn_on_AE_AG,ov5640_set_bandingfilter等函数是就写入寄存器的操作,配置相应的模式,这里不多解释。
重点要理清结构体如何包含摄像头参数信息,以及函数是如何传入参数。
无论是在enum ov5640_mode还是在struct ov5640_mode_info ov5640_mode_info_data[2][ov5640_mode_MAX + 1]添加自己所设置的一些参数信息,最终都是定位到寄存器组的设置。也就是向ov5640写入对应的寄存器和对应的值
像素点格式设置
ov5640_s_fmt
static int ov5640_s_fmt(struct v4l2_subdev *sd,
struct v4l2_mbus_framefmt *mf)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct ov5640 *sensor = to_ov5640(client);
/* MIPI CSI could have changed the format, double-check */
if (!ov5640_find_datafmt(mf->code))
return -EINVAL;
ov5640_try_fmt(sd, mf);
sensor->fmt = ov5640_find_datafmt(mf->code);
return 0;
}
从传入的形参struct v4l2_mbus_framefmt *mf可以看出此函数用于设置像素点格式
v4l2_mbus_framefmt结构体如下
struct v4l2_mbus_framefmt {
__u32 width; //输出宽度
__u32 height; //输出高度
__u32 code; //ov5640_datafmt结构体中的code
__u32 field;
__u32 colorspace;//具体像素格式
__u16 ycbcr_enc;
__u16 quantization;
__u32 reserved[6];
};
看一下ov5640_s_fmt遇到的第一个函数ov5640_find_datafmt,原型如下:函数功能就是设置ov5640_colour_fmts结构体中的像素点格式,返回所选像素点格式的地址
/* Find a data format by a pixel code in an array */
static const struct ov5640_datafmt
*ov5640_find_datafmt(u32 code)
{
int i;
for (i = 0; i < ARRAY_SIZE(ov5640_colour_fmts); i++)
if (ov5640_colour_fmts[i].code == code)
return ov5640_colour_fmts + i;
return NULL;
}
ov5640_try_fmt:在函数上方有这么一段注释/* MIPI CSI could have changed the format, double-check */,因此这个函数的功能依旧是设置像素点格式
static int ov5640_try_fmt(struct v4l2_subdev *sd,
struct v4l2_mbus_framefmt *mf)
{
const struct ov5640_datafmt *fmt = ov5640_find_datafmt(mf->code);
if (!fmt) {
mf->code = ov5640_colour_fmts[0].code;
mf->colorspace = ov5640_colour_fmts[0].colorspace;
}
mf->field = V4L2_FIELD_NONE;
return 0;
}
ov5640_g_fmt
static int ov5640_g_fmt(struct v4l2_subdev *sd,
struct v4l2_mbus_framefmt *mf)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct ov5640 *sensor = to_ov5640(client);
const struct ov5640_datafmt *fmt = sensor->fmt;
mf->code = fmt->code;
mf->colorspace = fmt->colorspace;
mf->field = V4L2_FIELD_NONE;
return 0;
}
ov5640_g_fmt函数是获取视频数据源提供的当前像素格式。ov5640_s_fmt则是设置像素格式。因此在后续移植过程中,我们主要修改ov5640_s_fmt函数。
总结
梳理了一下包含摄像头参数的结构体以及设置参数的相关函数,我们需要修改的地方主要有:
1.enum ov5640_mode枚举类型,添加自己LCD的编号
2.static struct ov5640_mode_info ov5640_mode_info_data[2][ov5640_mode_MAX + 1]结构体中添加设配自己LCD分辨率的参数
3.static const struct ov5640_datafmt ov5640_colour_fmts添加像素点格式
4.添加适配LCD分辨率的寄存器组
5.在ov5640_s_fmt函数中添加适配的设置
可以看到主要工作就是添加所使用LCD的分辨率的相关参数信息,后续开始移植ov5640摄像头驱动。