camera senor -> 913 -> 914(parallel并行接口) -> imx6
i2c通信(i2c3)
913的din[x]对应914dout[x]数据脚
pclk像素时钟
vsync场同步时钟
hsync行同步时钟
这里的csi0_data8-19对应的就是914的dout0 - 11对应913的din0-11
示波器测量:
csi0_data8-17能有数据波形
pclk = 66mhz
vsync = 25hz
hsync = 20khz
pclk/hsync = 3300
hsync/vsync = 800
fps = 25fps
按照我的认知,这个摄像头规格是3300x800,帧率是25,数据位是10bit;
实际按照厂家给的,我的913摄像头规格是1280x720,帧率30,数据位8bit(只有高8位有效);
不知道是不是我的理解有问题,分辨率算出来的和实际差得有点离谱,
如果能有大神可以懂得,麻烦在评论解答一下,感谢!
&i2c3 {
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c3>;
status = "okay";
ds90ub914a: ds90ub914a@6f {
compatible = "ti,ds90ub914a-q1";
reg = <0x6f>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_video>;
clocks = <&clks IMX6QDL_CLK_CKO>;
clock-names = "csi_mclk";
//DOVDD-supply = <&vgen4_reg>; /* 1.8v */
//AVDD-supply = <&vgen3_reg>; /* 2.8v, on rev C board is VGEN3,
// on rev B board is VGEN5 */
//DVDD-supply = <&vgen2_reg>; /* 1.5v*/
//pwn-gpios = <&gpio1 16 1>; /* active low: SD1_DAT0 */
//rst-gpios = <&gpio1 17 0>; /* active high: SD1_DAT1 */
csi_id = <0>;
pdb_gpios = <&gpio4 15 0>;
mclk = <24000000>;
mclk_source = <0>;
};
pinctrl_video: video { /* parallel camera */
fsl,pins = <
MX6QDL_PAD_CSI0_DAT8__IPU1_CSI0_DATA08 0x80000000
MX6QDL_PAD_CSI0_DAT9__IPU1_CSI0_DATA09 0x80000000
MX6QDL_PAD_CSI0_DAT10__IPU1_CSI0_DATA10 0x80000000
MX6QDL_PAD_CSI0_DAT11__IPU1_CSI0_DATA11 0x80000000
MX6QDL_PAD_CSI0_DAT12__IPU1_CSI0_DATA12 0x80000000
MX6QDL_PAD_CSI0_DAT13__IPU1_CSI0_DATA13 0x80000000
MX6QDL_PAD_CSI0_DAT14__IPU1_CSI0_DATA14 0x80000000
MX6QDL_PAD_CSI0_DAT15__IPU1_CSI0_DATA15 0x80000000
MX6QDL_PAD_CSI0_DAT16__IPU1_CSI0_DATA16 0x80000000
MX6QDL_PAD_CSI0_DAT17__IPU1_CSI0_DATA17 0x80000000
MX6QDL_PAD_CSI0_DAT18__IPU1_CSI0_DATA18 0x80000000
MX6QDL_PAD_CSI0_DAT19__IPU1_CSI0_DATA19 0x80000000
MX6QDL_PAD_CSI0_VSYNC__IPU1_CSI0_VSYNC 0x80000000
MX6QDL_PAD_CSI0_PIXCLK__IPU1_CSI0_PIXCLK 0x80000000
MX6QDL_PAD_CSI0_MCLK__IPU1_CSI0_HSYNC 0x80000000
MX6QDL_PAD_KEY_ROW4__GPIO4_IO15 0x80000000
>;
};
v4l2_cap_0 {
compatible = "fsl,imx6q-v4l2-capture";
ipu_id = <0>;
csi_id = <0>;
mclk_source = <0>;
status = "okay";
};
v4l2_cap_1 {
compatible = "fsl,imx6q-v4l2-capture";
ipu_id = <0>;
csi_id = <1>;
mclk_source = <0>;
status = "okay";
};
i2c 的 clock-frequency = <400000>;这个在914芯片规格书都有提到(不过100kHz也是可以正常使用)
这支设备树我是基于imx6q开发板的ov564x设备树来修改的,只是注释掉一些没有用上的引脚,添加了一个pdb低功耗(power down)引脚;
这里配置12位的数据脚,这是根据硬件pcb板引脚图来配置的,实际使用的还是data8 - 17;
选择到imx6的通道是ipu0 csi0 ;
在这里和大家建议一下,把这部分的驱动都编译成模块(ko),然后再去insmod 去加载驱动,这样能够更加全面的了解这些驱动加载的流程
insmod ipu_bg_overlay_sdc.ko
insmod ipu_csi_enc.ko
insmod ipu_fg_overlay_sdc.ko
insmod ipu_prp_enc.ko
insmod ipu_still.ko
insmod v4l2-int-device.ko
insmod ov5642_camera.ko
insmod mxc_v4l2_capture.ko
insmod ipu_bg_overlay_sdc.ko
insmod ipu_csi_enc.ko
insmod ipu_fg_overlay_sdc.ko
insmod ipu_prp_enc.ko
insmod ipu_still.ko
insmod v4l2-int-device.ko
这几个模块为了驱动提供函数接口的,不加载,直接使用insmod ov5642_camera.ko会报找不到函数的错误
insmod mxc_v4l2_capture.ko
下面会分析
ov5642.c 位于driver/media/platform/mxc/capture
因为我修改了设备树
compatible = “ti,ds90ub914a-q1”;
所以需要在ov5642_i2c_driver结构体下添加.of_match_table = ds90ub914a_of_match
这样就可以匹配我们的设备树了
static const struct of_device_id ds90ub914a_of_match[] = {
{ .compatible = "ti,ds90ub914a-q1", },
{ }
};
static struct i2c_driver ov5642_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "ov5642",
.of_match_table = ds90ub914a_of_match,
},
.probe = ov5642_probe,
.remove = ov5642_remove,
.id_table = ov5642_id,
};
下面分析ov5642_probe
static int ov5642_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;
int RegVal;
int pdb_gpio;
pr_info("starting %s\n",__func__);
/* ov5642 pinctrl */
pinctrl = devm_pinctrl_get_select_default(dev);//这个是初始化pin脚,设备树里面的pinctrl_video;
if (IS_ERR(pinctrl)) {
dev_err(dev, "ov5642 setup pinctrl failed!");
return PTR_ERR(pinctrl);
}
/* request power down pin */
/*接下来就用#if 0 ... #endif注释掉了一些没用用的代码*/
#if 0
pwn_gpio = of_get_named_gpio(dev->of_node, "pwn-gpios", 0);
if (!gpio_is_valid(pwn_gpio)) {
dev_warn(dev, "no sensor pwdn pin available");
return -EINVAL;
}
retval = devm_gpio_request_one(dev, pwn_gpio, GPIOF_OUT_INIT_HIGH,
"ov5642_pwdn");
if (retval < 0)
return retval;
/* request reset pin */
rst_gpio = of_get_named_gpio(dev->of_node, "rst-gpios", 0);
if (!gpio_is_valid(rst_gpio)) {
dev_warn(dev, "no sensor reset pin available");
return -EINVAL;
}
retval = devm_gpio_request_one(dev, rst_gpio, GPIOF_OUT_INIT_HIGH,
"ov5642_reset");
if (retval < 0)
return retval;
#endif
pdb_gpio = of_get_named_gpio(dev->of_node, "pdb_gpios", 0);//初始化pdb引脚
printk("pdb_gpio_id = %d\n",pdb_gpio);
if (!gpio_is_valid(pdb_gpio)) {
dev_err(dev, "no sensor pdb pin available\n");
return -ENODEV;
}
gpio_direction_input(pdb_gpio);//设置pdb引脚为输入
/* Set initial values for the sensor struct. */
memset(&ov5642_data, 0, sizeof(ov5642_data));
ov5642_data.sensor_clk = devm_clk_get(dev, "csi_mclk");//根据设备树ds90ub914a: ds90ub914a@6f来获取
if (IS_ERR(ov5642_data.sensor_clk)) {
/* assuming clock enabled by default */
ov5642_data.sensor_clk = NULL;
dev_err(dev, "clock-frequency missing or invalid\n");
return PTR_ERR(ov5642_data.sensor_clk);
}
retval = of_property_read_u32(dev->of_node, "mclk",//根据设备树ds90ub914a: ds90ub914a@6f来获取
(u32 *) &(ov5642_data.mclk));
if (retval) {
dev_err(dev, "mclk missing or invalid\n");
return retval;
}
retval = of_property_read_u32(dev->of_node, "mclk_source",//根据设备树ds90ub914a: ds90ub914a@6f来获取
(u32 *) &(ov5642_data.mclk_source));
if (retval) {
dev_err(dev, "mclk_source missing or invalid\n");
return retval;
}
retval = of_property_read_u32(dev->of_node, "csi_id",//根据设备树ds90ub914a: ds90ub914a@6f来获取
&(ov5642_data.csi));
if (retval) {
dev_err(dev, "csi_id missing or invalid\n");
return retval;
}
clk_prepare_enable(ov5642_data.sensor_clk);
/*这里是根据913摄像头的规格来配置的
//ov5642_data.io_init = ov5642_reset;
ov5642_data.i2c_client = client;
ov5642_data.pix.pixelformat = V4L2_PIX_FMT_UYVY;
ov5642_data.pix.width = 1280;
ov5642_data.pix.height = 720;
ov5642_data.streamcap.capability = V4L2_MODE_HIGHQUALITY |
V4L2_CAP_TIMEPERFRAME;
ov5642_data.streamcap.capturemode = 0;
ov5642_data.streamcap.timeperframe.denominator = DEFAULT_FPS;
ov5642_data.streamcap.timeperframe.numerator = 1;//帧率 = denominator/numerator, 为什么要做这个除法,而不直街写
ds90ub914q_device_init();//对914做初始化,下面会分析这个函数
ds90ub913q_device_init();//对913做初始化,下面会分析这个函数
//ov5642_power_on(&client->dev);
//ov5642_reset();
//ov5642_standby(0);
#if 0
retval = ov5642_read_reg(OV5642_CHIP_ID_HIGH_BYTE, &chip_id_high);
if (retval < 0 || chip_id_high != 0x56) {
pr_warning("camera ov5642 is not found\n");
clk_disable_unprepare(ov5642_data.sensor_clk);
return -ENODEV;
}
retval = ov5642_read_reg(OV5642_CHIP_ID_LOW_BYTE, &chip_id_low);
if (retval < 0 || chip_id_low != 0x42) {
pr_warning("camera ov5642 is not found\n");
clk_disable_unprepare(ov5642_data.sensor_clk);
return -ENODEV;
}
ov5642_standby(1);
#endif
ov5642_int_device.priv = &ov5642_data;
retval = v4l2_int_device_register(&ov5642_int_device);//注册到链表中,等待注册成设备节点,下面会分析
clk_disable_unprepare(ov5642_data.sensor_clk);
pr_info("camera ov5642 is found\n");
return retval;
}
static int ds90ub914q_write(unsigned char reg_addr,unsigned char value)
{
int ret;
unsigned char buf[2];
buf[0] = reg_addr;
buf[1] = value;
ret = i2c_master_send(ov5642_data.i2c_client, buf, 2);
printk("%s:0x%x = 0x%x\n",__func__,buf[0],buf[1]);
return ret;
}
static int ds90ub914q_device_init(void)
{
int i = 0;
unsigned char reg_data[] = {
0x07, 0xB2,//mapped 913 i2c add as 0x59,修改这个寄存器就可以把913映射出来到i2c下,然后操作这个地址来修改913的寄存器
};
/*soft reset*/
if(ds90ub914q_write(DS90UB914Q_REG_Reset,0x07)<0)//DS90UB914Q_REG_Reset = 0x01,修改这寄存器可以让914寄存器重置
return -1;
udelay(50);
for(i = 0; i < sizeof(reg_data); i += 2){
if(ds90ub914q_write(reg_data[i], reg_data[i+1]) < 0){
return -1;
}
udelay(10);
}
return 0;
}
static int ds90ub913q_write(unsigned char reg_addr,unsigned char value)
{
int ret;
unsigned char buf[2];
struct i2c_msg msg[1];
msg[0].addr = 0x59;
buf[0] = reg_addr;
buf[1] = value;
msg[0].addr = 0x59;
msg[0].buf = buf;
msg[0].len = 2;
msg[0].flags = 0;
ret = i2c_transfer(ov5642_data.i2c_client->adapter, msg, 1);
printk("%s:0x%x = 0x%x\n",__func__,buf[0],buf[1]);
return ret;
}
static ds90ub913q_device_init(void){
int i = 0;
/*这三个寄存器的设置是摄像头厂家给的,具体为什么要这么做没有说明*/
unsigned char reg_data[] = {
0x03, 0xdd,//Pass-Through and Automatically Acknowledge I2C Remote Write
0x0d, 0x11,//Chip 913 Host RST and Host FrameSync Signal
0x0d, 0x99,//Chip 913 Host RST and Host FrameSync Signal
}
/*soft reset*/
if(ds90ub913q_write(0x01,0x33)<0)//重置913寄存器
return -1;
udelay(50);
for(i = 0; i < sizeof(reg_data); i += 2){
if(ds90ub913q_write(reg_data[i], reg_data[i+1]) < 0){
return -1;
}
udelay(10);
}
return 0;
}
ds90ub913q_write函数可以实现同一路i2c下不同地址的设备的修改
从同事了解到i2c 中 adapter可以理解为i2c3,然后通过i2c_msg这个结构体来修改地址(默认是0x6f , 914的地址),最后用i2c_transfer函数写913的寄存器。
ds90ub913q_write和ds90ub914q_write是我自己添加的,也可以用ov5642_write。
最后使用
v4l2_int_device_register(&ov5642_int_device);
static struct v4l2_int_slave ov5642_slave = {
.ioctls = ov5642_ioctl_desc,
.num_ioctls = ARRAY_SIZE(ov5642_ioctl_desc),
};
static struct v4l2_int_device ov5642_int_device = {
.module = THIS_MODULE,
.name = "ov5642",
.type = v4l2_int_type_slave,
.u = {
.slave = &ov5642_slave,
},
};
这个函数目的是注册到链表当中,等待把函数中的设备名,ioctls等信息注册到设备节点中
这里之所以说是等待,因为还要使用同一目录下mxc_v4l2_capture.c,也就是我们前面说的要加载insmod mxc_v4l2_capture.ko,这样才会真正完成设备的注册。
mxc_v4l2_capture.c是不用修改的,它主要是通过你在ov5642_probe函数中设置的摄像头参数来修改ipu和csi相关的寄存器参数
static int mxc_v4l2_probe(struct platform_device *pdev)
{
/* Create cam and initialize it. */
cam_data *cam = kmalloc(sizeof(cam_data), GFP_KERNEL);
if (cam == NULL) {
pr_err("ERROR: v4l2 capture: failed to register camera\n");
return -1;
}
init_camera_struct(cam, pdev);//初始化摄像头参数
pdev->dev.release = camera_platform_release;
/* Set up the v4l2 device and register it*/
cam->self->priv = cam;
v4l2_int_device_register(cam->self);//这个注册一个master设备,然后和ov5642这个slave设备匹配
/* register v4l video device */
if (video_register_device(cam->video_dev, VFL_TYPE_GRABBER, video_nr)//这里才会真正注册一个/dev/videoX设备节点
< 0) {
kfree(cam);
cam = NULL;
pr_err("ERROR: v4l2 capture: video_register_device failed\n");
return -1;
}
pr_debug(" Video device registered: %s #%d\n",
cam->video_dev->name, cam->video_dev->minor);
if (device_create_file(&cam->video_dev->dev,
&dev_attr_fsl_v4l2_capture_property))
dev_err(&pdev->dev, "Error on creating sysfs file"
" for capture\n");
if (device_create_file(&cam->video_dev->dev,
&dev_attr_fsl_v4l2_overlay_property))
dev_err(&pdev->dev, "Error on creating sysfs file"
" for overlay\n");
if (device_create_file(&cam->video_dev->dev,
&dev_attr_fsl_csi_property))
dev_err(&pdev->dev, "Error on creating sysfs file"
" for csi number\n");
return 0;
}
init_camera_struct(cam, pdev);这个函数虽然是初始化摄像头参数,但是并不是真正把你前面ov5642_probe设置的参数设置进去,只是设置了它默认的参数。
v4l2_int_device_register(cam->self);这个是注册一个master设备到链表中,然后会和之前ov5642中注册到链表中slave设备匹配。我猜想应该每一个slave都要对应一个master,然后slave设备会和最近一个注册的master设备匹配。
video_register_device(cam->video_dev, VFL_TYPE_GRABBER, video_nr)然后利用这个函数才真正注册出一个供应用层使用的设备节点/dev/videoX,一般是video0;
在linux系统中,使用imx6q原生的系统,在/unit_test/目录下会有很多视频的测试工具。
我个人使用的是mxc_v4l2_capture.out,可以保存捕获的视频到本地。
如./mxc_v4l2_capture.out -ow 1024 -oh 768 -f UYVY /tmp/video.yuv,这个命令的意思是生成分辨率是1024x768,采集格式UYVY的/tmp/video.yuv文件。
int v4l_capture_setup(void){
...
if (ioctl(fd_v4l, VIDIOC_S_PARM, &parm) < 0)
{
printf("VIDIOC_S_PARM failed\n");
return -1;
}
...
}
在mxc_v4l2_capture.out源码中可以看到ioctl(fd_v4l, VIDIOC_S_PARM, &fmt);这个就是真正把ov5642.c的参数设置ipu和csi的寄存器中。
在这里只要你调用ioclt就会调用前面mxc_v4l2_capture.c的mxc_v4l_do_ioctl这个函数,根据case来做相应的操作。
static long mxc_v4l_do_ioctl(struct file *file,
unsigned int ioctlnr, void *arg)
{
...
case VIDIOC_S_PARM: {
struct v4l2_streamparm *parm = arg;
pr_debug(" case VIDIOC_S_PARM\n");
if (cam->sensor)
retval = mxc_v4l2_s_param(cam, parm);
else {
pr_err("ERROR: v4l2 capture: slave not found!\n");
retval = -ENODEV;
}
break;
}
...
}
这里的mxc_v4l2_s_param就是帮助设置参数的函数
static int mxc_v4l2_s_param(cam_data *cam, struct v4l2_streamparm *parm)
{
struct v4l2_ifparm ifparm;
struct v4l2_format cam_fmt;
struct v4l2_streamparm currentparm;
ipu_csi_signal_cfg_t csi_param;
u32 current_fps, parm_fps;
int err = 0;
pr_debug("In mxc_v4l2_s_param\n");
if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
pr_err(KERN_ERR "mxc_v4l2_s_param invalid type\n");
return -EINVAL;
}
/* Stop the viewfinder */
if (cam->overlay_on == true)
stop_preview(cam);
currentparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* First check that this device can support the changes requested. */
err = vidioc_int_g_parm(cam->sensor, ¤tparm);
if (err) {
pr_err("%s: vidioc_int_g_parm returned an error %d\n",
__func__, err);
goto exit;
}
current_fps = currentparm.parm.capture.timeperframe.denominator
/ currentparm.parm.capture.timeperframe.numerator;
parm_fps = parm->parm.capture.timeperframe.denominator
/ parm->parm.capture.timeperframe.numerator;
pr_debug(" Current capabilities are %x\n",
currentparm.parm.capture.capability);
pr_debug(" Current capturemode is %d change to %d\n",
currentparm.parm.capture.capturemode,
parm->parm.capture.capturemode);
pr_debug(" Current framerate is %d change to %d\n",
current_fps, parm_fps);
/* This will change any camera settings needed. */
err = vidioc_int_s_parm(cam->sensor, parm);
if (err) {
pr_err("%s: vidioc_int_s_parm returned an error %d\n",
__func__, err);
goto exit;
}
/* If resolution changed, need to re-program the CSI */
/* Get new values. */
vidioc_int_g_ifparm(cam->sensor, &ifparm);
csi_param.data_width = 0;
csi_param.clk_mode = 0;
csi_param.ext_vsync = 0;
csi_param.Vsync_pol = 0;
csi_param.Hsync_pol = 0;
csi_param.pixclk_pol = 0;
csi_param.data_pol = 0;
csi_param.sens_clksrc = 0;
csi_param.pack_tight = 0;
csi_param.force_eof = 0;
csi_param.data_en_pol = 0;
csi_param.data_fmt = 0;
csi_param.csi = cam->csi;
csi_param.mclk = 0;
pr_debug(" clock_curr=mclk=%d\n", ifparm.u.bt656.clock_curr);
if (ifparm.u.bt656.clock_curr == 0)
csi_param.clk_mode = IPU_CSI_CLK_MODE_CCIR656_INTERLACED;
else
csi_param.clk_mode = IPU_CSI_CLK_MODE_GATED_CLK;
csi_param.pixclk_pol = ifparm.u.bt656.latch_clk_inv;
if (ifparm.u.bt656.mode == V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT) {
csi_param.data_width = IPU_CSI_DATA_WIDTH_8;
} else if (ifparm.u.bt656.mode
== V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT) {
csi_param.data_width = IPU_CSI_DATA_WIDTH_10;
} else {
csi_param.data_width = IPU_CSI_DATA_WIDTH_8;
}
csi_param.Vsync_pol = ifparm.u.bt656.nobt_vs_inv;
csi_param.Hsync_pol = ifparm.u.bt656.nobt_hs_inv;
csi_param.ext_vsync = ifparm.u.bt656.bt_sync_correct;
/* if the capturemode changed, the size bounds will have changed. */
cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt);
pr_debug(" g_fmt_cap returns widthxheight of input as %d x %d\n",
cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height);
csi_param.data_fmt = cam_fmt.fmt.pix.pixelformat;
cam->crop_bounds.top = cam->crop_bounds.left = 0;
cam->crop_bounds.width = cam_fmt.fmt.pix.width;
cam->crop_bounds.height = cam_fmt.fmt.pix.height;
/*
* Set the default current cropped resolution to be the same with
* the cropping boundary(except for tvin module).
*/
if (cam->device_type != 1) {
cam->crop_current.width = cam->crop_bounds.width;
cam->crop_current.height = cam->crop_bounds.height;
}
/* This essentially loses the data at the left and bottom of the image
* giving a digital zoom image, if crop_current is less than the full
* size of the image. */
ipu_csi_set_window_size(cam->ipu, cam->crop_current.width,
cam->crop_current.height, cam->csi);
ipu_csi_set_window_pos(cam->ipu, cam->crop_current.left,
cam->crop_current.top,
cam->csi);
ipu_csi_init_interface(cam->ipu, cam->crop_bounds.width,
cam->crop_bounds.height,
cam_fmt.fmt.pix.pixelformat, csi_param);
exit:
if (cam->overlay_on == true)
start_preview(cam);
return err;
}
vidioc_int_s_parm,vidioc_int_s_parm和vidioc_int_g_ifparm,vidioc_int_g_fmt_cap都是可以在ov5642.c中找到相应的位置的。
ipu_csi_set_window_size,ipu_csi_set_window_pos,ipu_csi_init_interface这三函数就是设置ipu和csi相关寄存器的函数
ipu_csi_set_window_size会根据应用层给的分辨率来设置视频的分辨率
ipu_csi_set_window_pos会把视频显示位置上下左右移动
ipu_csi_init_interface代码
int32_t
ipu_csi_init_interface(struct ipu_soc *ipu, uint16_t width, uint16_t height,
uint32_t pixel_fmt, ipu_csi_signal_cfg_t cfg_param)
{
uint32_t data = 0;
uint32_t csi = cfg_param.csi;
void __iomem *plx_membase;
//printk("%s:%s pixelfmt = %d\n",__FILE__,__func__,pixel_fmt);
/* Set SENS_DATA_FORMAT bits (8, 9 and 10)
RGB or YUV444 is 0 which is current value in data so not set
explicitly
This is also the default value if attempts are made to set it to
something invalid. */
switch (pixel_fmt) {
case IPU_PIX_FMT_YUYV:
cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_YUYV;
break;
case IPU_PIX_FMT_UYVY:
cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY;
break;
case IPU_PIX_FMT_RGB24:
case IPU_PIX_FMT_BGR24:
case IPU_PIX_FMT_YUV444:
cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB_YUV444;
break;
case IPU_PIX_FMT_GENERIC:
case IPU_PIX_FMT_GENERIC_16:
cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER;
break;
case IPU_PIX_FMT_RGB565:
cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB565;
break;
case IPU_PIX_FMT_RGB555:
cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB555;
break;
default:
return -EINVAL;
}
/* Set the CSI_SENS_CONF register remaining fields */
data |= cfg_param.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT |
cfg_param.data_fmt << CSI_SENS_CONF_DATA_FMT_SHIFT |
cfg_param.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT |
cfg_param.Vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT |
cfg_param.Hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT |
cfg_param.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT |
cfg_param.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT |
cfg_param.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT |
cfg_param.pack_tight << CSI_SENS_CONF_PACK_TIGHT_SHIFT |
cfg_param.force_eof << CSI_SENS_CONF_FORCE_EOF_SHIFT |
cfg_param.data_en_pol << CSI_SENS_CONF_DATA_EN_POL_SHIFT;
_ipu_get(ipu);
mutex_lock(&ipu->mutex_lock);
ipu_csi_write(ipu, csi, data, CSI_SENS_CONF);
/* Setup sensor frame size */
ipu_csi_write(ipu, csi, (width - 1) | (height - 1) << 16, CSI_SENS_FRM_SIZE);
/* Set CCIR registers */
if (cfg_param.clk_mode == IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE) {
ipu_csi_write(ipu, csi, 0x40030, CSI_CCIR_CODE_1);
ipu_csi_write(ipu, csi, 0xFF0000, CSI_CCIR_CODE_3);
} else if (cfg_param.clk_mode == IPU_CSI_CLK_MODE_CCIR656_INTERLACED) {
if (width == 720 && height == 625) {
/* PAL case */
/*
* Field0BlankEnd = 0x6, Field0BlankStart = 0x2,
* Field0ActiveEnd = 0x4, Field0ActiveStart = 0
*/
ipu_csi_write(ipu, csi, 0x40596, CSI_CCIR_CODE_1);
/*
* Field1BlankEnd = 0x7, Field1BlankStart = 0x3,
* Field1ActiveEnd = 0x5, Field1ActiveStart = 0x1
*/
ipu_csi_write(ipu, csi, 0xD07DF, CSI_CCIR_CODE_2);
ipu_csi_write(ipu, csi, 0xFF0000, CSI_CCIR_CODE_3);
} else if (width == 720 && height == 525) {
/* NTSC case */
/*
* Field0BlankEnd = 0x7, Field0BlankStart = 0x3,
* Field0ActiveEnd = 0x5, Field0ActiveStart = 0x1
*/
ipu_csi_write(ipu, csi, 0xD07DF, CSI_CCIR_CODE_1);
/*
* Field1BlankEnd = 0x6, Field1BlankStart = 0x2,
* Field1ActiveEnd = 0x4, Field1ActiveStart = 0
*/
ipu_csi_write(ipu, csi, 0x40596, CSI_CCIR_CODE_2);
ipu_csi_write(ipu, csi, 0xFF0000, CSI_CCIR_CODE_3);
} else {
dev_err(ipu->dev, "2.Unsupported CCIR656 interlaced "
"video mode\n");
mutex_unlock(&ipu->mutex_lock);
_ipu_put(ipu);
return -EINVAL;
}
_ipu_csi_ccir_err_detection_enable(ipu, csi);
} else if ((cfg_param.clk_mode ==
IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR) ||
(cfg_param.clk_mode ==
IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR) ||
(cfg_param.clk_mode ==
IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR) ||
(cfg_param.clk_mode ==
IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR)) {
ipu_csi_write(ipu, csi, 0x40030, CSI_CCIR_CODE_1);
ipu_csi_write(ipu, csi, 0xFF0000, CSI_CCIR_CODE_3);
_ipu_csi_ccir_err_detection_enable(ipu, csi);
} else if ((cfg_param.clk_mode == IPU_CSI_CLK_MODE_GATED_CLK) ||
(cfg_param.clk_mode == IPU_CSI_CLK_MODE_NONGATED_CLK)) {
_ipu_csi_ccir_err_detection_disable(ipu, csi);
}
dev_info(ipu->dev, "CSI_SENS_CONF = 0x%08X\n",
ipu_csi_read(ipu, csi, CSI_SENS_CONF));
//plx_membase = ioremap(0x2600000, 0x30000);
//printk("BEN:CSI_CONF:addr = %lx\n",(u_long)plx_membase);
//printk("CSI_SENS_CONF = 0x%08X\n",readl((u_long)plx_membase));
mutex_unlock(&ipu->mutex_lock);
_ipu_put(ipu);
return 0;
}
ipu_csi_write(ipu, csi, data, CSI_SENS_CONF);
这个写函数,就会把设置的参数写到给CSI_SENS_CONF这个寄存器中
通过读这个寄存器就可以知道你设置的参数生效没。
这时候可以把/tmp/video.yuv拷出来,在电脑上播放,可以使用pyuv播放
这个时候就到我最绝望奔溃的时候,图像出来了,可是全绿的,一点彩色点都没有,把imx6,914,913三份规格书图像显示的都看了一遍都没有发现。后来就开始漫长的调试过程。
首先排除913摄像头自身的问题,因为在其他平台是可以正常使用的。
可以排除是格式设置问题,但是这个图像很清晰,如果是分辨率,yuv格式设置不对是会有花屏的现象,不过你还是看到彩色点。
数据丢失问题,测量913的input data脚,914的output data脚的频率,发现两端是一样的,所以数据是没有丢失的。
这时候就去考虑imx6q处理的问题
这个是ov5642原生代码的接口配置,使用了CSI0_DATA12 - 19来接收数据
pinctrl_ipu1_2: ipu1grp-2 { /* parallel camera */
fsl,pins = <
/*
MX6QDL_PAD_CSI0_DAT12__IPU1_CSI0_DATA12 0x80000000
MX6QDL_PAD_CSI0_DAT13__IPU1_CSI0_DATA13 0x80000000
MX6QDL_PAD_CSI0_DAT14__IPU1_CSI0_DATA14 0x80000000
MX6QDL_PAD_CSI0_DAT15__IPU1_CSI0_DATA15 0x80000000
MX6QDL_PAD_CSI0_DAT16__IPU1_CSI0_DATA16 0x80000000
MX6QDL_PAD_CSI0_DAT17__IPU1_CSI0_DATA17 0x80000000
MX6QDL_PAD_CSI0_DAT18__IPU1_CSI0_DATA18 0x80000000
MX6QDL_PAD_CSI0_DAT19__IPU1_CSI0_DATA19 0x80000000
MX6QDL_PAD_CSI0_DATA_EN__IPU1_CSI0_DATA_EN 0x80000000
MX6QDL_PAD_CSI0_PIXCLK__IPU1_CSI0_PIXCLK 0x80000000
MX6QDL_PAD_CSI0_MCLK__IPU1_CSI0_HSYNC 0x80000000
MX6QDL_PAD_CSI0_VSYNC__IPU1_CSI0_VSYNC 0x80000000
MX6QDL_PAD_SD1_DAT1__GPIO1_IO17 0x80000000
MX6QDL_PAD_SD1_DAT0__GPIO1_IO16 0x80000000
*/
>;
};
我使用的是IPU1_CSI0_DATA10 - 17来接收数据
pinctrl_video: video { /* parallel camera */
fsl,pins = <
MX6QDL_PAD_CSI0_DAT8__IPU1_CSI0_DATA08 0x80000000
MX6QDL_PAD_CSI0_DAT9__IPU1_CSI0_DATA09 0x80000000
MX6QDL_PAD_CSI0_DAT10__IPU1_CSI0_DATA10 0x80000000
MX6QDL_PAD_CSI0_DAT11__IPU1_CSI0_DATA11 0x80000000
MX6QDL_PAD_CSI0_DAT12__IPU1_CSI0_DATA12 0x80000000
MX6QDL_PAD_CSI0_DAT13__IPU1_CSI0_DATA13 0x80000000
MX6QDL_PAD_CSI0_DAT14__IPU1_CSI0_DATA14 0x80000000
MX6QDL_PAD_CSI0_DAT15__IPU1_CSI0_DATA15 0x80000000
MX6QDL_PAD_CSI0_DAT16__IPU1_CSI0_DATA16 0x80000000
MX6QDL_PAD_CSI0_DAT17__IPU1_CSI0_DATA17 0x80000000
MX6QDL_PAD_CSI0_DAT18__IPU1_CSI0_DATA18 0x80000000
MX6QDL_PAD_CSI0_DAT19__IPU1_CSI0_DATA19 0x80000000
MX6QDL_PAD_CSI0_VSYNC__IPU1_CSI0_VSYNC 0x80000000
MX6QDL_PAD_CSI0_PIXCLK__IPU1_CSI0_PIXCLK 0x80000000
MX6QDL_PAD_CSI0_MCLK__IPU1_CSI0_HSYNC 0x80000000
MX6QDL_PAD_KEY_ROW4__GPIO4_IO15 0x80000000
>;
};
想要和ov5642一样,就是使用CSI0_DATA12 - 19来接收数据,这样只能修改硬件
如下飞线
这样接收到的图像就是正常的了
后来nxp论坛上看到
使用I.MX6Q CSI并行接口时,如果是YUV或者BT.656格式 8bit模式,确实是需要接在D12~D19上的。
也可以参考这位大哥的文章
https://blog.csdn.net/yanbixing123/article/list/7?
有什么问题大家可以在评论留言,谢谢