目的:自然景观->摄像头模块->接口->S3C2440的摄像头控制器->LCD
1.CMOS摄像头基础
本次使用的白问网提供的ov7740摄像头模组,基础机构如下:
1.1摄像头参数
OV7740_CSP_DS_1.51.pdf—ov7740的datasheet中的参数可知:
支持输出格式:RAW RGB与YUV格式
- RAW RGB与RGB的区别是什么?
答:所谓的RAW RGB就是只有红绿蓝三种颜色的数据。而RGB数据,它不仅只表示红绿蓝
三种颜色,而且还能表示由红绿蓝组合成的任何一种颜色。 - RGB、YUV又分别是什么?
答:RGB、YUV(Y(亮度信号)、U(R-Y的色差信号)、V(B-Y的色差信号)组成)是两种完全不同的颜色空间,它们之间可以相互转换。转换公式如下:
Y = 0.299R + 0.587G + 0.114BU = -0.147R - 0.289G + 0.436BV = 0.615R - 0.515G - 0.100BR = Y + 1.14VG = Y - 0.39U - 0.58VB = Y + 2.03U
输出分辨率为:VGA(640480)、QVGA(240320)、CIF(352288)、更小的任意大小
有效感光阵列的大小:656488 = 320128(30W)指感光区域内单像素点的数量,像素越多,拍摄画面幅面就越大,可拍摄的画面的细节就越多;
镜头的大小(lens size):1/5寸(指感光区域对角线距离,尺寸越大,材料成本越高);
像素点颗粒的大小(pixel size): 4.2um * 4.2um(指单个感光元件的长宽尺寸,也称单像素的开口尺寸,开口尺寸越大,单位时间内进入的光量就越大,芯片整体性能就相对较高,最终拍摄画面的整体画质相对较优秀)
- 单像素尺寸是图像传感器一个相当关键的参数,也就是都号称1200万像素的相机和手机,相机的效果远远好于手机的原因。手机由于体积限制,尽管像素很多,但每个像素都很小,拍摄瞬间进光量也小,成像质量就自然差一些了。
- 以上三个参数,都是用来描述感光阵列,即使同为30W像素的摄像头,如果它的镜头尺寸大小越小,那么对应的像素点颗粒的大小就越小,从而感光性就越差,进而拍摄的效果就越差。
输入时钟频率(input clock frequency): 6~27MHz
- 即0V7740摄像头模组的工作频率范围。0V7740摄像头模组的工作时钟范围,这个将由SOC提供给CMOS
扫描模式(scan mode): 连续扫描§
- 扫描方式一般分为”逐行扫描”§和”隔行扫描”(I)两种。
逐行扫描:每一帧图像由电子束顺序地一行接着一行连续扫描而成;
隔行扫描:把每一帧图像通过两场扫描完成则是隔行扫描,两场扫描中,第一场(奇数场)只扫描奇数行,依次扫描1、3、5…行,而第二场(偶数场)只扫描偶数行,依次扫描2、4、6…行。
隔行扫描技术在传送信号带宽不够的情况下起了很大作用,逐行扫描和隔行扫描的显示效果主要区别在稳定性上面,隔行扫描的行间闪烁比较明显,逐行扫描克服了隔行扫描的缺点,画面平滑自然无闪烁。
1.2 功能处理
从图上可以看出处理流程分为三个部分:
-
image sensor core(ISC)----pdf第四章
图像翻转、增益大小调整、黑电平校准、饱和度的控制、OTP存储器 -
image sensor processor(ISP)—pdf第五章
提供测试功能、镜头补偿功能、自动白平衡、颜色空间的转换功能(RAW RGB->RGB、RGB->YUV)、窗口功能(自动裁剪图片)、缩小放大功能; -
image output interface(ISI)—pdf第六章
功能:RAW RGB/YUV(图片数据格式)、VGA/QVGA、BT601/BT656
BT601有独立的行同步信号线、帧同步信号线,而BT656是将这两种信号内嵌到数据中;
2.硬件原理图
2.1ov7740对外的接口如下:
这是ov7740暴露出来的接口,可以看到CAMERA相关的数据外,还有两根IIC的数据线和时钟线。
CAMRST – 复位CMOS摄像头模块
CAMCLK – 摄像头模块工作的系统时钟(24MHz)
CAM_HREF – 行同步信号
CAM_VSYNC – 帧同步信号
CAM_PCLK – 像素时钟
CAMDATA0~7-- 数据线
- IIC数据线和时钟线的作用是什么?
答:s3c2440通过IIC总线,操作OV7740的寄存器来进行设置的,控制ov7740输出正确的格式。
①:初始化:对摄像头模块进行相应的初始化操作,让摄像头模块能够正常的输出摄像头数据;
②:控制: 设置亮度、旋转、缩放等操作;
2.2 对应s3c2440接口
CAM数据类:
CAM控制类:
摄像头工作流程:
首先SOC输出CAMCLKOUT给摄像头提供时钟,然后控制CAMRST复位摄像头,再通过I2C初始化、设置摄像头,最后在HREF、VSYNC和PCLK的控制下,通过D0~D7这八根数据线将数据发给SOC。
3 CAMERA 控制器
摄像头的采集的数据CPU一般不直接处理,主控芯片里面集成了Camera控制器FIMC(FullyInteractive Mobile Camera)来处理。
摄像头先把图像数据传给Camera控制器,经过控制器处理(裁剪拉升后直接预览或者编码)之后交给CPU处理。
实际上摄像头工作需要的时钟(MCLK)也是FIMC给它提供的。
摄像头的驱动主要分为两部分:
一部分由模组厂家或者sensor厂家提供的初始化代码,通常是一段数组,通过I2C 总线来控制,用于设置sensor 寄存器,使用时一般不需要修改,如需调整,也由模组厂家完成;
另外一部分是应用处理器端的代码,这部分需要各个平台自行开发,在Exynos4412中就是CAMIF和FIMC。
CMOS摄像头模块,实质上是一个I2C设备。通过I2C设置、控制摄像头,SOC的摄像头控制器(CAMIF和FIMC)负责数据的处理。
因此后面CMOS驱动的核心就是:设置OV7740控制器,使其按想要的方式输出数据以及传输数据。设置camera interface控制器,使其能够接收ov7740传进来的数据并处理
3. 1用户手册设计的寄存器
如电路图所示,设计GPE控制器,GPJ控制器的设置。
因此打开用户手册,寻找相应的寄存器设置。517页进行camera interface的设置
这是所有涉及的寄存器
- 问:为什么原理图中涉及IIC(GPE管脚)而程序中并没有涉及?
答:原因是程序并不是我们手动的输入IIC管脚的输入输出数据,而是调用i2c_smbus_read_byte_data()内核所提供的的函数进行读写。(所以在实验步骤时会提前先加载IIC的驱动,以便数据的读写)
4. 程序编写
4.1 分配设置一个结构体
驱动的第一步:先写驱动的入口、出口函数,并用相关的宏定义入口、出口函数。
static int cmos_ov7740_drv_init(void)
{/* 1.2.注册 */i2c_add_driver(&cmos_ov7740_driver);return 0;
}static void cmos_ov7740_drv_exit(void)
{i2c_del_driver(&cmos_ov7740_driver);
}module_init(cmos_ov7740_drv_init);
module_exit(cmos_ov7740_drv_exit);MODULE_LICENSE("GPL");
第二步:分配设置一个IIC结构体、并在入口函数注册这个结构体
/* 1.1. 分配、设置一个i2c_driver */
static struct i2c_driver cmos_ov7740_driver = {.driver = {.name = "cmos_ov7740",.owner = THIS_MODULE,},.probe = cmos_ov7740_probe,.remove = __devexit_p(cmos_ov7740_remove),.id_table = cmos_ov7740_id_table,
};
static const struct i2c_device_id cmos_ov7740_id_table[] = {{ "cmos_ov7740", 0 },{}
};
i2c_device_id :包含两部分内容,名字和是否有私有数据。
struct i2c_device_id {char name[I2C_NAME_SIZE];kernel_ulong_t driver_data; /* Data private to the driver */
};
4.2在probe函数中注册一个实际操作对象的结构体–video device
在内核比对id之后会自动调用probe()函数:
在probe函数中进行实际的操作,进行video设备驱动的分配设置与注册
/* 2.1. 分配、设置一个video_device结构体 */
static struct video_device cmos_ov7740_vdev = {.fops = &cmos_ov7740_fops,.ioctl_ops = &cmos_ov7740_ioctl_ops,.release = cmos_ov7740_release,.name = "cmos_ov7740",
};
填充其中的fops结构体、release函数、ops函数集
static const struct v4l2_file_operations cmos_ov7740_fops = {.owner = THIS_MODULE,.open = cmos_ov7740_open,.release = cmos_ov7740_close,.unlocked_ioctl = video_ioctl2,.read = cmos_ov7740_read,
};
static void cmos_ov7740_release(struct video_device *vdev)
{unsigned int order;order = get_order(buf_size);free_pages(img_buff[0].virt_base, order);img_buff[0].phy_base = (unsigned long)NULL;free_pages(img_buff[1].virt_base, order);img_buff[1].phy_base = (unsigned long)NULL; free_pages(img_buff[2].virt_base, order);img_buff[2].phy_base = (unsigned long)NULL; free_pages(img_buff[3].virt_base, order);img_buff[3].phy_base = (unsigned long)NULL;
}
/* 应用程序通过读的方式读取摄像头的数据 */
static ssize_t cmos_ov7740_read(struct file *filep, char __user *buf, size_t count, loff_t *pos)
{size_t end;int i;end = min_t(size_t, buf_size, count);/*等待数据 无数据休眠,可以被打断*/wait_event_interruptible(cam_wait_queue, ev_cam);for(i=0;i<4;i++){if(copy_to_user(buf, (void *)img_buff[i].virt_base, end)){return -EFAULT;}}ev_cam = 0;return end;
}
static const struct v4l2_ioctl_ops cmos_ov7740_ioctl_ops = {// 表示它是一个摄像头设备.vidioc_querycap = cmos_ov7740_vidioc_querycap,/* 用于列举、获得、测试、设置摄像头的数据的格式 */.vidioc_enum_fmt_vid_cap = cmos_ov7740_vidioc_enum_fmt_vid_cap,.vidioc_g_fmt_vid_cap = cmos_ov7740_vidioc_g_fmt_vid_cap,.vidioc_try_fmt_vid_cap = cmos_ov7740_vidioc_try_fmt_vid_cap,.vidioc_s_fmt_vid_cap = cmos_ov7740_vidioc_s_fmt_vid_cap,/* 缓冲区操作: 申请/查询/放入队列/取出队列 */.vidioc_reqbufs = cmos_ov7740_vidioc_reqbufs,/* 说明: 因为我们是通过读的方式来获得摄像头数据,因此查询/放入队列/取出队列这些操作函数将不在需要 */
#if 0.vidioc_querybuf = myuvc_vidioc_querybuf,.vidioc_qbuf = myuvc_vidioc_qbuf,.vidioc_dqbuf = myuvc_vidioc_dqbuf,
#endif// 启动/停止.vidioc_streamon = cmos_ov7740_vidioc_streamon,.vidioc_streamoff = cmos_ov7740_vidioc_streamoff,
};
最后在probe函数中注册这个video device结构体
/* 2.2.注册 */if(video_register_device(&cmos_ov7740_vdev, VFL_TYPE_GRABBER, -1)){printk("unable to register video device\n");}
4.3填充probe函数与remove函数
cmos_ov7740_remove:remove函数如往常一样进行内存的释放,映射地址的关闭、中断的释放、以及video_unregister_device()
static int __devexit cmos_ov7740_remove(struct i2c_client *client)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);iounmap(GPJCON);iounmap(GPJDAT);iounmap(GPJUP);iounmap(CISRCFMT);iounmap(CIWDOFST);iounmap(CIGCTRL);iounmap(CIPRCLRSA1);iounmap(CIPRCLRSA2);iounmap(CIPRCLRSA3);iounmap(CIPRCLRSA4);iounmap(CIPRTRGFMT);iounmap(CIPRCTRL);iounmap(CIPRSCPRERATIO);iounmap(CIPRSCPREDST);iounmap(CIPRSCCTRL);iounmap(CIPRTAREA);iounmap(CIIMGCPT);iounmap(SRCPND);iounmap(INTPND);iounmap(SUBSRCPND);free_irq(IRQ_S3C2440_CAM_C, NULL);free_irq(IRQ_S3C2440_CAM_P, NULL);video_unregister_device(&cmos_ov7740_vdev);return 0;
}
cmos_ov7740_probe:probe函数中需要进行所有涉及的寄存器的映射以及所有操作流程的安排。
static int __devinit cmos_ov7740_probe(struct i2c_client *client,const struct i2c_device_id *id)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/*2.3硬件相关*//*2.3.1 映射相应的寄存器*/GPJCON = ioremap(0x560000d0, 4);GPJDAT = ioremap(0x560000d4, 4);GPJUP = ioremap(0x560000d8, 4);CISRCFMT = ioremap(0X4F000000, 4);CIWDOFST = ioremap(0X4F000004, 4);CIGCTRL = ioremap(0X4F000008, 4);CIPRCLRSA1 = ioremap(0X4F00006C, 4);CIPRCLRSA2 = ioremap(0X4F000070, 4);CIPRCLRSA3 = ioremap(0X4F000074, 4);CIPRCLRSA4 = ioremap(0X4F000078, 4);CIPRTRGFMT = ioremap(0X4F00007C, 4);CIPRCTRL = ioremap(0X4F000080, 4);CIPRSCPRERATIO = ioremap(0x4F000084, 4);CIPRSCPREDST = ioremap(0x4F000088, 4);CIPRSCCTRL = ioremap(0x4F00008C, 4);CIPRTAREA = ioremap(0x4F000090, 4);CIIMGCPT = ioremap(0x4F0000A0, 4);SRCPND = ioremap(0X4A000000, 4);INTPND = ioremap(0X4A000010, 4);SUBSRCPND = ioremap(0X4A000018, 4);/*2.3.2设置相应的 GPIO用于CAMIF*/cmos_ov7740_gpio_cfg();/*2.3.3 复位一下CAMIF控制器*/cmos_ov7740_camif_reset();/*2.3.4 设置使能时钟(使能HCLK,使能并设置CAMCLK = 24Mhz)*/cmos_ov7740_clk_cfg();/*2.3.5 复位一下摄像头模块 原因:IIC能够正常操作CMOS模块的内部的寄存器的前提是:*提供符合它需求的系统时钟(CAMCLK)*需要给它一个复位信号 *//*通过操作CAMIF控制器中相应的寄存器,让CAMRST发出复位信号,从而复位摄像头接口,具体操作见驱动源码*/cmos_ov7740_reset();/*2.3.6 通过IIC总线,初始化摄像头模块*/cmos_ov7740_client = client;cmos_ov7740_init();/*2.3.7 注册中断*//*编码中断 CAM_C编码通道 cmos_ov7740_camif_irq_c中断函数*/if (request_irq(IRQ_S3C2440_CAM_C, cmos_ov7740_camif_irq_c, IRQF_DISABLED , "CAM_C", NULL))printk("%s:request_irq failed\n", __func__);/*预览中断 CAM_P预览通道 cmos_ov7740_camif_irq_p中断函数*/if (request_irq(IRQ_S3C2440_CAM_P, cmos_ov7740_camif_irq_p, IRQF_DISABLED , "CAM_P", NULL))printk("%s:request_irq failed\n", __func__);/* 2.2.注册 */if(video_register_device(&cmos_ov7740_vdev, VFL_TYPE_GRABBER, -1)){printk("unable to register video device\n");}return 0;
}
4.3.1设置相应的 GPIO用于CAMIF
将GPJCON ,GPJDAT ,GPJUP (必须上拉,所有IIC设备必须有上拉电阻的存在)使能
/*设置相应的 GPIO用于CAMIF*/
static void cmos_ov7740_gpio_cfg(void)
{*GPJCON = 0x2aaaaaa;*GPJDAT = 0;/*使能上拉电阻*/*GPJUP = 0;
}
4.3.2复位一下CAMIF控制器
CIGCTRL :Global control register 控制软件复位
static void cmos_ov7740_camif_reset(void)
{/*传输方式为bt601*/*CISRCFMT |= (1<<31);/*复位camif控制器*/*CIGCTRL |= (1<<31);mdelay(10);*CIGCTRL &= ~(1<<31);mdelay(10);}
4.3.3设置使能时钟(使能HCLK,使能并设置CAMCLK = 24Mhz)
这是所有需要时钟的通过函数,首先获得一个时钟,然后使能这个时钟,最后再获得另外一个时钟,设置这个时钟的频率,从而设置时钟频率
static void cmos_ov7740_clk_cfg(void)
{struct clk *camif_clk;struct clk *camif_upll_clk;/*使能CAMIF时钟源(HCLK)像素时钟--由ov7740提供的*/camif_clk = clk_get(NULL,"camif");if(!camif_clk || IS_ERR(camif_clk)){printk(KERN_INFO"failed to get CAMIF clock source!\n");}clk_enable(camif_clk);/*使能并设置CAMCLK = 24MHZ(CAMCLKOUT)由usb pll(96Mhz)分频而得,设置给ov7740*/camif_upll_clk = clk_get(NULL,"camif-upll");if(!camif_upll_clk || IS_ERR(camif_upll_clk)){printk(KERN_INFO"failed to get CAMIF_UPLL clock source!\n");}clk_set_rate(camif_upll_clk, 24000000);mdelay(100);
}
注:时钟信息在2手册255页从LOCKTIME开始。
4.3.4复位一下摄像头模块(OV7740复位一下)
cmos_ov7740_reset:
/*2.3.5 复位一下摄像头模块 原因:IIC能够正常操作CMOS模块的内部的寄存器的前提是:*提供符合它需求的系统时钟(CAMCLKOUT)*需要给它一个复位信号 *//*通过操作CAMIF控制器中相应的寄存器,让CAMRST发出复位信号,从而复位摄像头接口,具体操作见驱动源码*/
static void cmos_ov7740_reset(void)
{*CIGCTRL |= (1<<30);mdelay(30);*CIGCTRL &= ~(1<<30);mdelay(30);*CIGCTRL |= (1<<30);mdelay(30);
}
4.3.5初始化摄像头模块
cmos_ov7740_client = client;cmos_ov7740_init();
static void cmos_ov7740_init(void)
{unsigned int mid;int i;/* 读 *//*读厂家ID*//*MSB高8位*/mid = i2c_smbus_read_byte_data(cmos_ov7740_client, 0x0a)<<8;/*LSB低8位*/mid |= i2c_smbus_read_byte_data(cmos_ov7740_client, 0x0b);printk("manufacture ID = 0x%4x\n", mid);/* 写 :将摄像头初始化输出格式为640x480,30fps的,YUV422输出格式*/for(i = 0; i < OV7740_INIT_REGS_SIZE; i++){i2c_smbus_write_byte_data(cmos_ov7740_client, ov7740_setting_30fps_VGA_640_480[i].regaddr, ov7740_setting_30fps_VGA_640_480[i].value);mdelay(2);}
}
4.3.6中断的注册
每次摄像头采集完一帧数据时,在下一帧开始前会产生中断
/*2.3.7 注册中断*//*编码中断 CAM_C编码通道 cmos_ov7740_camif_irq_c中断函数*/if (request_irq(IRQ_S3C2440_CAM_C, cmos_ov7740_camif_irq_c, IRQF_DISABLED , "CAM_C", NULL))printk("%s:request_irq failed\n", __func__);/*预览中断 CAM_P预览通道 cmos_ov7740_camif_irq_p中断函数*/if (request_irq(IRQ_S3C2440_CAM_P, cmos_ov7740_camif_irq_p, IRQF_DISABLED , "CAM_P", NULL))printk("%s:request_irq failed\n", __func__);
static irqreturn_t cmos_ov7740_camif_irq_c(int irq, void *dev_id)
{return IRQ_HANDLED;
}static irqreturn_t cmos_ov7740_camif_irq_p(int irq, void *dev_id)
{/*中断发生不变的法则,请中断,处理*//* 清中断 */*SRCPND = 1<<6;*INTPND = 1<<6;*SUBSRCPND = 1<<12;ev_cam = 1;wake_up_interruptible(&cam_wait_queue);return IRQ_HANDLED;
}
4.4启动摄像头
除了关于buf的有关寄存器,其余寄存器都在这里设置
static int cmos_ov7740_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{unsigned int Main_burst, Remained_burst;/** CISRCFMT : bit[31]--选择传输方式为BT601或者BT656bit[30]--设置偏移值(0 正常情况下 forYCbCr)bit[29]--保留位,必须设置为0.bit[28:16]--设置源图片的水平像素值(640)bit[15:14]--设置源图片的颜色顺序(0x0c--->0x2)bit[12:0]--设置源图片的垂直像素值(480)*/*CISRCFMT |= (0<<30) | (0<<29) | (CAM_SRC_HSIZE <<16) | (CAM_ORDER_CbYCrY<<14) | (CAM_SRC_VSIZE << 0); /** CIWDOFST :bit[31] ---1 表示使能窗口功能, 0 表示不适用窗口功能bit[30,15:12]--清楚溢出标志位bit[26:16]--水平方向的裁剪的大小bit[10:0]--垂直方向的裁剪的大小*/*CIWDOFST |= (1<<30) | (0xf<<12);*CIWDOFST |= (1<<31) | (WinHorOfst << 16) | (WinVerOfst <<0);SRC_Width = CAM_SRC_HSIZE - 2*WinHorOfst;SRC_Height = CAM_SRC_VSIZE - 2*WinVerOfst;/**CIGCTRL:bit[31] --软件复位CAMIF控制器bit[30] --用于复位外部摄像头模块bit[29] --保留位,必须设置为1bit[28:27] --用于选择信号源 00表示输入源实际外部摄像头数据 后面三个都是虚拟的数据bit[26] --设置像素时钟的极性 0表示不翻转 1表示翻转bit[25] --设置VSYNC(帧同步信号)的极性( 0 低高低)bit[24] --设置HREF(行同步信号)的极性 0* */*CIGCTRL |= (1<<29) | (0<<27) | (0<<26) | (0<<25) | (0<<24);/** CIPRCTRL:bit[23:19] --主突发长度(main burst):就是DMA传输时的最大完整长度bit[18:14] --剩余突发长度(remained burst):最后一次需要搬运的字节bit[2] --是否使能LastIRQ功能:一般每一次数据采集前触发一次中断,LastIRQ最后一针采集完才触发中断*/CalculateBurstSize(bytesperline,&Main_burst,&Remained_burst);*CIPRCTRL = (Main_burst<<19) | (Remained_burst<<14) | (0<<2);/**CIPRSCPRERATIO:bit[31:28] --预览缩放的变化系数(SHfactor)bit[22:16] --预览缩放的水平比(PreHorRatio)bit[6:0] --预览缩放的垂直比(PreVerRatio)**CIPRSCPREDST:bit[27:16] --预览缩放的目标宽度(PreDstWidth)bit[11:0] --预览缩放的目标高度(PreDstHeight)**CIPRSCCTRL:bit[31] --固定设置为1(与缩放无关)bit[30] --设置图像输出格式是RGB16(0)或RGB24(1)bit[29:28] --用来告诉摄像头控制器图片是缩小还是放大(ScaleUpDown)bit[24:16] --预览主缩放的水平比(MainHorRatio)bit[15] --预览缩放开始bit[8:0] --预览主缩放的垂直比(MainVerRatio)*/cmos_ov7740_calculate_scaler_info();*CIPRSCPRERATIO = (sc.SHfactor<<28) | (sc.PreHorRatio<<16) | (sc.PreVerRatio<<0);*CIPRSCPREDST = (sc.PreDstWidth<<16) | (sc.PreDstHeight<<0);*CIPRSCCTRL |=(1<<31)| (sc.ScaleUpDown<<28) | (sc.MainHorRatio<<16) | (sc.MainVerRatio<<0);/**CIPRTAREA: 表示预览通道的目标区域(实际图片大小)*/*CIPRTAREA = TargetHsize_Pr * TargetVsize_Pr;/**CIIMGCPT:bit[31] --用来使能摄像仪控制器bit[30] --使能编码通道bit[29] --使能预览通道*/*CIIMGCPT = (1<<31) | (1<<29);*CIPRSCCTRL |= (1<<15);return 0;
}
static void cmos_ov7740_calculate_scaler_info(void)
{unsigned int sx, sy, tx, ty;sx = SRC_Width; /*源图片水平宽度*/sy = SRC_Height; /*源图片垂直高度*/tx = TargetHsize_Pr; /*目的图片水平宽度*/ty = TargetVsize_Pr; /*目的图片垂直高度*//*__func__表示函数名字*/printk("%s: SRC_in(%d, %d), Target_out(%d, %d)\n", __func__, sx, sy, tx, ty);camif_get_scaler_factor(sx, tx, &sc.PreHorRatio, &sc.H_Shift);camif_get_scaler_factor(sy, ty, &sc.PreVerRatio, &sc.V_Shift);sc.PreDstWidth = sx / sc.PreHorRatio;sc.MainHorRatio = (sx<<8) / (tx << sc.H_Shift);sc.PreDstHeight = sy / sc.PreVerRatio;sc.MainVerRatio = (sy<<8) / (ty << sc.V_Shift);sc.SHfactor = 10 - (sc.H_Shift + sc.V_Shift);sc.ScaleUpDown = (tx >= sx) ? 1 : 0;
}
static void CalculateBurstSize(unsigned int hSize, unsigned int *mainBurstSize,unsigned int *remainBurstSize)
{unsigned int tmp;tmp = (hSize/4) % 16; //hSize/4多少个字switch (tmp){case 0:*mainBurstSize = 16;*remainBurstSize = 16;break;case 4:*mainBurstSize = 16;*remainBurstSize = 4;break;case 8:*mainBurstSize = 16;*remainBurstSize = 8;break;default:tmp = (hSize/4) % 8;switch (tmp){case 0:*mainBurstSize = 8;*remainBurstSize = 8;break;case 4:*mainBurstSize = 8;*remainBurstSize = 4;break;default:*mainBurstSize = 4;tmp = (hSize/4) % 4;*remainBurstSize = (tmp) ? tmp : 4;break;}break;}}
static void camif_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift)
{if(src >= 64*tar) {return;}else if(src >= 32*tar) {*ratio = 32; *shift = 5;}else if(src >= 16*tar) {*ratio = 16; *shift = 4;}else if(src >= 8*tar) {*ratio = 8; *shift = 3;}else if(src >= 4*tar) {*ratio = 4; *shift = 2;}else if(src >= 2*tar) {*ratio = 2; *shift = 1;}else {*ratio = 1; *shift = 0;}
}
其余ioctl函数(参考uvc驱动的ioctl函数)
static irqreturn_t cmos_ov7740_camif_irq_p(int irq, void *dev_id)
{/*中断发生不变的法则,请中断,处理*//* 清中断 */*SRCPND = 1<<6;*INTPND = 1<<6;*SUBSRCPND = 1<<12;ev_cam = 1;wake_up_interruptible(&cam_wait_queue);return IRQ_HANDLED;
}/* A2 参考 uvc_v4l2_do_ioctl */
static int cmos_ov7740_vidioc_querycap(struct file *file, void *priv,struct v4l2_capability *cap)
{memset(cap, 0, sizeof *cap);strcpy(cap->driver, "cmos_ov7740");strcpy(cap->card, "cmos_ov7740");cap->version = 2;cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE;return 0;
}/* A3 列举支持哪种格式* 参考: uvc_fmts 数组*/
static int cmos_ov7740_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,struct v4l2_fmtdesc *f)
{struct cmos_ov7740_fmt *fmt;if (f->index >= ARRAY_SIZE(formats))return -EINVAL;fmt = &formats[f->index];strlcpy(f->description, fmt->name, sizeof(f->description));f->pixelformat = fmt->fourcc;return 0;
}/* A4 返回当前所使用的格式 */
static int cmos_ov7740_vidioc_g_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{return 0;
}/* A5 测试驱动程序是否支持某种格式, 强制设置该格式 * 参考: uvc_v4l2_try_format* myvivi_vidioc_try_fmt_vid_cap*/
static int cmos_ov7740_vidioc_try_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE){return -EINVAL;}if ((f->fmt.pix.pixelformat != V4L2_PIX_FMT_RGB565) && (f->fmt.pix.pixelformat != V4L2_PIX_FMT_RGB24))return -EINVAL;return 0;
}/* A6 参考 myvivi_vidioc_s_fmt_vid_cap */
static int cmos_ov7740_vidioc_s_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{int ret = cmos_ov7740_vidioc_try_fmt_vid_cap(file, NULL, f);if (ret < 0)return ret;TargetHsize_Pr = f->fmt.pix.width;TargetVsize_Pr = f->fmt.pix.height;if(f->fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565){*CIPRSCCTRL &= ~(1<<30);f->fmt.pix.bytesperline = (f->fmt.pix.width * 16) >> 3;f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;buf_size = f->fmt.pix.sizeimage;bytesperline = f->fmt.pix.bytesperline;}else if(f->fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24){*CIPRSCCTRL |= (1<<30);f->fmt.pix.bytesperline = (f->fmt.pix.width * 32) >> 3;f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;buf_size = f->fmt.pix.sizeimage;bytesperline = f->fmt.pix.bytesperline;}/*CIPRTRGFMT:* bit[28:16] --表示目标图片的水平像素大小(TargetHsize_Pr)bit[15:14] --是否旋转,我们这个驱动不旋转bit[12:0] --表示目标图片的垂直像素大小(TargetVsize_Pr)* */*CIPRTRGFMT = (TargetHsize_Pr<<16) | (0x0<<14) | (TargetVsize_Pr<<0);return 0;
}static int cmos_ov7740_vidioc_reqbufs(struct file *file, void *priv,struct v4l2_requestbuffers *p)
{unsigned int order;order = get_order(buf_size);img_buff[0].order = order;img_buff[0].virt_base = __get_free_pages(GFP_KERNEL | __GFP_DMA, img_buff[0].order);if((unsigned long)NULL == img_buff[0].virt_base){goto erro0;}img_buff[0].phy_base = __virt_to_phys(img_buff[0].virt_base);order = get_order(buf_size);img_buff[1].order = order;img_buff[1].virt_base = __get_free_pages(GFP_KERNEL | __GFP_DMA, img_buff[1].order);if((unsigned long)NULL == img_buff[1].virt_base){goto erro1;}img_buff[1].phy_base = __virt_to_phys(img_buff[0].virt_base);order = get_order(buf_size);img_buff[2].order = order;img_buff[2].virt_base = __get_free_pages(GFP_KERNEL | __GFP_DMA, img_buff[2].order);if((unsigned long)NULL == img_buff[2].virt_base){goto erro2;}img_buff[2].phy_base = __virt_to_phys(img_buff[2].virt_base);order = get_order(buf_size);img_buff[3].order = order;img_buff[3].virt_base = __get_free_pages(GFP_KERNEL | __GFP_DMA, img_buff[3].order);if((unsigned long)NULL == img_buff[3].virt_base){goto erro3;}img_buff[3].phy_base = __virt_to_phys(img_buff[0].virt_base);*CIPRCLRSA1 = img_buff[0].phy_base;*CIPRCLRSA2 = img_buff[1].phy_base;*CIPRCLRSA3 = img_buff[2].phy_base;*CIPRCLRSA4 = img_buff[3].phy_base;/*执行到erro3后悔继续往下执行erro2,erro1,erro0*/return 0;
erro3:free_pages(img_buff[2].virt_base, order);img_buff[2].phy_base = (unsigned long)NULL;
erro2:free_pages(img_buff[1].virt_base, order);img_buff[1].phy_base = (unsigned long)NULL;
erro1:free_pages(img_buff[0].virt_base, order);img_buff[0].phy_base = (unsigned long)NULL;
erro0:return -ENOMEM;
}
总结
对于CMOS的摄像头,我们是在开发板设置相应的寄存器,只需要找到芯片手册,设置对应位即可!
遗留问题:
CAMPCLK(像素时钟)是由谁产生,它与CAMCLKOUT之间有什么关系?