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-gpios,rst-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_IO,OV5640_VOLTAGE_DIGITAL_CORE和OV5640_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_gpio和pwn_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>;
};
这个函数操作的是gpio1的19和20位,关于这两位引脚的意义还需要继续深入。它一共包含3个参数,第一个参数表示gpio1,第二个参数是否表示gpio1的哪一位??第三个参数表示默认值。比如说pwn-gpios的默认值是1,说明置0的时候是上电,置1的时候是关电。
同时,重置摄像头的时候,ov5640_reset()函数设置pwn-gpios和rst-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.c的ioctl_g_ifparm函数。先来看open函数中,它传入了两个参数:cam->sensor,ifparm;其实在ov5640_mipi.c中,它并没有从cam->sensor里面获取ifparm的信息来填充到ifparm中,而是在ioctl_g_ifparm函数中直接为ifparm中的各个成员变量赋值。主要是为ifparm.u.bt656成员赋值,包括clock_curr,mode,clock_min,clock_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_regulator,core_regulator和analog_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_clk和dphy_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中指定的,这时候就需要指定