视频监控—从零写CMOS摄像头驱动
- 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3),OV7740摄像头
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 参考资料:OV7740_CSP_DS_1.51 datasheet、S3C2440 datasheet
- 开发环境:Linux-4.13.0-41内核(虚拟机)、arm-linux-gcc-4.3.2工具链、linux-3.4.2内核(开发版根文件系统)
- 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3
目录
-
视频监控—从零写CMOS摄像头驱动 - 一、目的
- 二、S3C2440摄像头接口寄存器介绍
- 1、CMOS摄像头接口相关的寄存器
- 1.1 源格式寄存器(CISRCFMT)
- 1.2 窗口选择寄存器(CIWDOFST)
- 1.3 全局控制寄存器(CIGCTRL)
- 2、 CMOS摄像头预览通道相关的寄存器
- 2.1 RGB1起始地址寄存器(CIPRCLRSA1)
- 2.2 RGB2起始地址寄存器(CIPRCLRSA2)
- 2.3 RGB3起始地址寄存器(CIPRCLRSA3)
- 2.4 RGB4起始地址寄存(CIPRCLRSA4)
- 2.5 目标预览格式寄存器(CIPRTRGFMT)
- 2.6 预览DMA控制寄存器(CIPRCTRL)
- 2.7 预览前计数器控制寄存器1(CIPRSCPRERATIO)
- 2.8 预览前计数器控制寄存器2(CIPRSCPREDST)
- 2.9 预览主计数器控制寄存器(CIPRSCCTRL)
- 2.10 预览DMA目标面积寄存器(CIPRTAREA)
- 2.11 预览状态寄存器(CIIMGCPT)
- 三、程序编写
- 1、cmos_ov7740_drv.c框架
- 2、cmos_ov7740_dev.c框架
- 3、完善cmos_ov7740_drv.c
- 3.1 摄像头的初始化
- 3.2 相关的ioctl操作函数
- 3.3 应用程序读出数据函数
- 4、完整的cmos_ov7740_drv.c程序
一、目的
根据硬件OV7740,编写一个CMOS摄像头驱动程序:
- 使用预览模式,传输方式为BT601
- 源数据分辨率640 * 480,30fps,CbYCrY颜色格式
- 输出视频数据为480 * 270,30fps,RGB565颜色格式
注:此驱动程序是用在学习的时候编写的,不适合企业的应用。
二、S3C2440摄像头接口寄存器介绍
下面是寄存器的中文介绍,如果英文阅读能力较强的就可以直接看手册了。
1、CMOS摄像头接口相关的寄存器
1.1 源格式寄存器(CISRCFMT)
1.2 窗口选择寄存器(CIWDOFST)
1.3 全局控制寄存器(CIGCTRL)
2、 CMOS摄像头预览通道相关的寄存器
2.1 RGB1起始地址寄存器(CIPRCLRSA1)
2.2 RGB2起始地址寄存器(CIPRCLRSA2)
2.3 RGB3起始地址寄存器(CIPRCLRSA3)
2.4 RGB4起始地址寄存(CIPRCLRSA4)
2.5 目标预览格式寄存器(CIPRTRGFMT)
2.6 预览DMA控制寄存器(CIPRCTRL)
- Example 1: Target image size: QCIF (horizontal Y width = 176 pixels. 1 pixel = 1 Byte. 1 word = 4 pixel)
176 / 4 = 44 word
44 % 8 = 4 → main burst = 8, remained burst = 4 - Example 2: Target image size: VGA (horizontal Y width = 640 pixels. 1 pixel = 1 Byte. 1 word = 4 pixel)
640 / 4 = 160 word
160 % 16 = 0 → main burst = 16, remained burst = 16 - Example 3: Target image size: QCIF (horizontal C width = 88 pixels. 1 pixel = 1 Byte. 1 word = 4 pixel)
88 / 4 = 22 word
22 % 4 = 2 → main burst = 4, remained burst = 2 (HTRANS==INCR)
2.7 预览前计数器控制寄存器1(CIPRSCPRERATIO)
2.8 预览前计数器控制寄存器2(CIPRSCPREDST)
2.9 预览主计数器控制寄存器(CIPRSCCTRL)
2.10 预览DMA目标面积寄存器(CIPRTAREA)
2.11 预览状态寄存器(CIIMGCPT)
三、程序编写
1、cmos_ov7740_drv.c框架
对于CMOS摄像头,在前面的博文【2.6 视频监控—CMOS摄像头的硬件原理】已经介绍了,CMOS摄像头模块也是一个I2C设备,需要编写符合IIC设备的架构的驱动,从而实现初始化和灵活的控制。
对于一个I2C的总线驱动,需要xxx_dev.c
设备文件与xxx_drv.c
设备驱动文件,二者可根据结构体中的变量.name
来进行匹配,当一致时,会调用.probe
函数,在这个函数里面可以实现我们具体要做的事情。
- 编写一个I2C设备驱动,其步骤:分配、设置、注册一个
i2c_driver
结构体:
/*!* 设置i2c设备驱动结构体*/
static struct i2c_driver s_cmos_ov7740_drv = {.driver = {.name = "cmos_0v7740",.owner = THIS_MODULE,},.probe = cmos_ov7740_probe,.remove = __devexit_p(cmos_ov7740_remove),.id_table = s_cmos_ov7740_id_table,
};/** @brief cmos_ov7740_dev初始化函数(入口函数)* @return 0:成功 -1:失败*/
static int cmos_ov7740_drv_init(void)
{i2c_add_driver(&s_cmos_ov7740_drv);return 0;
}/** @brief cmos_ov7740_dev退出函数(出口函数)* @return 无*/
static void cmos_ov7740_drv_exit(void)
{video_unregister_device(&s_cmos_ov7740_vdev);i2c_del_driver(&s_cmos_ov7740_drv);
}
- 在
i2c_driver
结构体的.probe
函数中,注册一个video_device
结构体 - 对于
video_device
结构体,需要分配、设置、注册这个结构体:
3.1 在video_device结构体
中的.fops
,在v4l2_file_operations
结构体中包含了对设备实际操作open、close、read
;
3.2 在video_device结构体
中的.unlocked_ioctl
,保存的是与该设备ioctl
操作相关的结构体v4l2_ioctl_ops
。
/*!* 分配、设置v4l2_ioctl_ops结构体*/
static const struct v4l2_ioctl_ops s_cmos_ov7740_ioctl_ops = {// 表示它是一个摄像头设备.vidioc_querycap = cmos_ov7740_querycap,/* 用于列举、获得、测试、设置摄像头的数据的格式 */.vidioc_enum_fmt_vid_cap = cmos_ov7740_enum_fmt_vid_cap,.vidioc_g_fmt_vid_cap = cmos_ov7740_g_fmt_vid_cap,.vidioc_try_fmt_vid_cap = cmos_ov7740_try_fmt_vid_cap,.vidioc_s_fmt_vid_cap = cmos_ov7740_s_fmt_vid_cap,/* 缓冲区操作: 申请 */.vidioc_reqbufs = cmos_ov7740_reqbufs,/* 使用的是read方式读数据,/查询/放入队列/取出队列 操作不需要*///.vidioc_querybuf = cmos_ov7740_querybuf,//.vidioc_qbuf = cmos_ov7740_qbuf,//.vidioc_dqbuf = cmos_ov7740_dqbuf,/* 查询/获得/设置属性 *///.vidioc_queryctrl = cmos_ov7740_queryctrl,//.vidioc_g_ctrl = cmos_ov7740_g_ctrl,//.vidioc_s_ctrl = cmos_ov7740_s_ctrl,/* 启动/停止 */.vidioc_streamon = cmos_ov7740_streamon,.vidioc_streamoff = cmos_ov7740_streamoff,
};/*!* * @brief 关闭cmos_ov7740_fops设备文件*/
static int cmos_ov7740_close(struct file *file)
{cmos_ov7740_streamoff(NULL, NULL, 0);return 0;
}/*!* @brief 应用程序读出数据函数*/
static ssize_t cmos_ov7740_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{return 0;
}static const struct v4l2_file_operations s_cmos_ov7740_fops = {.owner = THIS_MODULE,.open = cmos_ov7740_open,.release = cmos_ov7740_close,.unlocked_ioctl = video_ioctl2,.read = cmos_ov7740_read
};/** @brief 必须的函数,否则在加载驱动时会出错* @return 无*/
static void cmos_ov7740_release(struct video_device *vdev)
{}/*!* 分配、设置video_device结构体*/
static struct video_device s_cmos_ov7740_vdev = {.name = "cmos_ov7740",.release = cmos_ov7740_release,.fops = &s_cmos_ov7740_fops,.ioctl_ops = &s_cmos_ov7740_ioctl_ops,
};
- 最终框架
经过对函数的补充定义,得到以下框架:
/******************************************************************************** Copyleft (c) 2021 Kcode** @file cmos_ov7740_drv.c* @brief cmos_0v7740摄像头的驱动文件* @author K* @version 0.0.1* @date 2021-07-26* @license MulanPSL-1.0** 文件修改历史:* <时间> | <版本> | <作者> | <描述>* 2021-07-29 | v0.0.1 | Kcode | cmos_0v7740摄像头的驱动文件* -----------------------------------------------------------------------------******************************************************************************/#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/videodev2.h>
#include <linux/wait.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <asm/atomic.h>
#include <asm/unaligned.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>
#include <media/videobuf-core.h>/*!* @brief Step1:打开cmos_ov7740_fops设备文件*/
static int cmos_ov7740_open(struct file *file)
{return 0;
}/*!* @brief Step2:查询是否为USB摄像头设备* 参考:uvc_v4l2_do_ioctl()*/
static int cmos_ov7740_querycap(struct file *file, void *priv,struct v4l2_capability *cap)
{ return 0;
}/*!* @brief Step3:列举USB摄像头设备所支持的格式format* 参考:uvc_fmt()*/
static int cmos_ov7740_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{return 0;
}/*!* @brief Step4:返回当前所使用的格式*/
static int cmos_ov7740_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{return 0;
}/*!* @brief Step5:测试驱动程序是否支持某种格式,强制设定格式* 参考:uvc_v4l2_try_format()/myvivi_vidioc_try_fmt_vid_cap()*/
static int cmos_ov7740_try_fmt_vid_cap(struct file *file,void *priv, struct v4l2_format *f)
{return 0;
}/*!* @brief Step6:设置所支持的格式* 参考:myvivi_vidioc_s_fmt_vid_cap()*/
static int cmos_ov7740_s_fmt_vid_cap(struct file *file,void *priv, struct v4l2_format *f)
{return 0;
}/*!* @brief Step7:为该设备申请若干个缓冲区,分配头部信息* 参考:uvc_alloc_buffers()* @return 正数:返回成功分配内存的大小,负数:分配失败*/
static int cmos_ov7740_reqbufs(struct file *file,void *priv, struct v4l2_requestbuffers *p)
{return 0;
}/*!* @brief Step8:启动数据传输* 参考:uvc_video_enable()-->uvc_commit_video()/uvc_init_video()* @return 0:成功*/
static int cmos_ov7740_streamon(struct file *file, void *priv, enum v4l2_buf_type t)
{return 0;
}/*!* @brief Step0:关闭设备* 参考:uvc_video_enable()-->uvc_commit_video()/uvc_init_video()* @return 0:成功*/
static int cmos_ov7740_streamoff(struct file *file, void *priv, enum v4l2_buf_type t)
{return 0;
}/*!* 分配、设置v4l2_ioctl_ops结构体*/
static const struct v4l2_ioctl_ops s_cmos_ov7740_ioctl_ops = {// 表示它是一个摄像头设备.vidioc_querycap = cmos_ov7740_querycap,/* 用于列举、获得、测试、设置摄像头的数据的格式 */.vidioc_enum_fmt_vid_cap = cmos_ov7740_enum_fmt_vid_cap,.vidioc_g_fmt_vid_cap = cmos_ov7740_g_fmt_vid_cap,.vidioc_try_fmt_vid_cap = cmos_ov7740_try_fmt_vid_cap,.vidioc_s_fmt_vid_cap = cmos_ov7740_s_fmt_vid_cap,/* 缓冲区操作: 申请 */.vidioc_reqbufs = cmos_ov7740_reqbufs,/* 使用的是read方式读数据,/查询/放入队列/取出队列 操作不需要*///.vidioc_querybuf = cmos_ov7740_querybuf,//.vidioc_qbuf = cmos_ov7740_qbuf,//.vidioc_dqbuf = cmos_ov7740_dqbuf,/* 查询/获得/设置属性 *///.vidioc_queryctrl = cmos_ov7740_queryctrl,//.vidioc_g_ctrl = cmos_ov7740_g_ctrl,//.vidioc_s_ctrl = cmos_ov7740_s_ctrl,/* 启动/停止 */.vidioc_streamon = cmos_ov7740_streamon,.vidioc_streamoff = cmos_ov7740_streamoff,
};/*!* * @brief 关闭cmos_ov7740_fops设备文件*/
static int cmos_ov7740_close(struct file *file)
{cmos_ov7740_streamoff(NULL, NULL, 0);return 0;
}/*!* @brief 应用程序读出数据函数*/
static ssize_t cmos_ov7740_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{return 0;
}static const struct v4l2_file_operations s_cmos_ov7740_fops = {.owner = THIS_MODULE,.open = cmos_ov7740_open,.release = cmos_ov7740_close,.unlocked_ioctl = video_ioctl2,.read = cmos_ov7740_read
};/** @brief 必须的函数,否则在加载驱动时会出错* @return 无*/
static void cmos_ov7740_release(struct video_device *vdev)
{}/*!* 分配、设置video_device结构体*/
static struct video_device s_cmos_ov7740_vdev = {.name = "cmos_ov7740",.release = cmos_ov7740_release,.fops = &s_cmos_ov7740_fops,.ioctl_ops = &s_cmos_ov7740_ioctl_ops,
};/** @brief 在总线中找到对应的设备文件后就调用probe函数* @return 0:成功 其他值:失败*/
static int __devinit cmos_ov7740_probe(struct i2c_client *client,const struct i2c_device_id *id)
{int ret;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/*! * 注册结构体 * -1:自动分配次设备号*/ret = video_register_device(&s_cmos_ov7740_vdev, VFL_TYPE_GRABBER, -1);if (ret) {printk("unable to register video device (error=%i).\n", ret);return ret;}return 0;
}/** @brief 移除函数* @return 0:成功 -1:失败*/
static int __devexit cmos_ov7740_remove(struct i2c_client *client)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/*!* 支持设备的名字*/
static const struct i2c_device_id s_cmos_ov7740_id_table[] = {{ "cmos_ov7740", 0 },{}
};/*!* 设置i2c设备驱动结构体*/
static struct i2c_driver s_cmos_ov7740_drv = {.driver = {.name = "cmos_0v7740",.owner = THIS_MODULE,},.probe = cmos_ov7740_probe,.remove = __devexit_p(cmos_ov7740_remove),.id_table = s_cmos_ov7740_id_table,
};/** @brief cmos_ov7740_dev初始化函数(入口函数)* @return 0:成功 -1:失败*/
static int cmos_ov7740_drv_init(void)
{i2c_add_driver(&s_cmos_ov7740_drv);return 0;
}/** @brief cmos_ov7740_dev退出函数(出口函数)* @return 无*/
static void cmos_ov7740_drv_exit(void)
{video_unregister_device(&s_cmos_ov7740_vdev);i2c_del_driver(&s_cmos_ov7740_drv);
}module_init(cmos_ov7740_drv_init);
module_exit(cmos_ov7740_drv_exit);
MODULE_LICENSE("GPL");
2、cmos_ov7740_dev.c框架
/******************************************************************************** Copyleft (c) 2021 Kcode** @file cmos_ov7740_dev.c* @brief cmos_0v7740摄像头的设备文件* @author K* @version 0.0.1* @date 2021-07-26* @license MulanPSL-1.0** 文件修改历史:* <时间> | <版本> | <作者> | <描述>* 2021-07-29 | v0.0.1 | Kcode | cmos_0v7740摄像头的设备文件* -----------------------------------------------------------------------------******************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>/*!* 板载的I2C设备信息* cmos_ov7740为设备名字* 0x21为设备地址* 写 -- 0x42(01000010)* 读 -- 0x43(01000011)* 8bit的地址 = 7bit设备地址 + 1bit的读/写控制位* 设备地址 = 0100001 = 0x21*/
static struct i2c_board_info s_cmos_ov7740_info = { I2C_BOARD_INFO("cmos_ov7740", 0x21),
};/* 用来挂接与适配器匹配成功的从设备i2c_client的一个链表头 */
static struct i2c_client *s_cmos_ov7740_client;/** @brief cmos_ov7740_dev注册函数(入口函数)* @return 0:成功 -1:失败*/static int cmos_ov7740_dev_init(void){struct i2c_adapter *i2c_adap; /**< 适配器 *//* 获取设备号为0的adpter,也就是adapter->nr == 0 */i2c_adap = i2c_get_adapter(0);/*!* 直接使用 i2c_new_device创建client自动注册到i2c_bus_type中去,* client->name == "cmos_ov7740" ,client->addr = 0x21 */s_cmos_ov7740_client = i2c_new_device(i2c_adap, &s_cmos_ov7740_info);/* 使用完后需要释放 */i2c_put_adapter(i2c_adap); return 0;}/** @brief cmos_ov7740_dev退出函数(出口函数)* @return 无*/
static void cmos_ov7740_dev_exit(void)
{i2c_unregister_device(s_cmos_ov7740_client);
}/* 修饰 */
module_init(cmos_ov7740_dev_init);
module_exit(cmos_ov7740_dev_exit);/* 协议 */
MODULE_LICENSE("GPL");
3、完善cmos_ov7740_drv.c
下面会挑选一些比较有代表性的编写问题来介绍代码
3.1 摄像头的初始化
摄像头的初始化的实现是在cmos_ov7740_probe
函数实现,即cmos_oc7740_dev.c
设备文件与cmos_oc7740_drv.c
设备驱动文件,二者匹配一致时,所调用的.probe
函数
主要步骤:
- 映射相关的寄存器
- 设置相应的GPIO用于CAMIF
- 复位一下CAMIF的控制器
- 设置、使能时钟(使能HCLK,使能并设置CAMCLK = 24MHz)
- 复位一下摄像头模块,原因:IIC能够正常操作CMOS摄像头模块内部的寄存器的前提如下
– 提供符合它需求的系统时钟(CAMCLK)
– 需要给它一个复位信号 - 通过IIC总线初始化摄像头模块
代码实现如下:
/** @brief 映射摄像头相关的寄存器* @return 无*/
static void cmos_ov7740_reg_map(void)
{/* CAMERA GPIO */gpjcon = ioremap(0x560000d0, 4);gpjdata = ioremap(0x560000d4, 4);gpjup = ioremap(0x560000d8, 4);/* CAMERA IF */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);}/** @brief 设置相应的GPIO用于CMOS摄像头的CAMIF* @return 无*/
static void cmos_ov7740_gpio_cfg(void)
{*gpjcon = 0x2aaaaaa; /**< 查看手册可知所有控制位设置为10 */*gpjdata = 0;*gpjup = 0; /**< 使能上拉电阻 */
}/** @brief 复位一下CAMIF的控制器* @return 无*/
static void cmos_ov7740_camif_reset(void)
{/* 传输方式为BT601 */*cisrcfmt |= (1 << 31);;/* 复位 */*cigctrl |= (1 << 31);mdelay(10);/* 清零:正常工作 */*cigctrl &= ~(1 << 31);mdelay(10);
}/** @brief 设置、使能时钟(使能HCLK,使能并设置CAMCLK = 24MHz)* @return 无*/
static void cmos_ov7740_clk_cfg(void)
{struct clk *camif_clk;struct clk *camif_upll_clk;/* 获取时钟"camif" */camif_clk = clk_get(NULL, "camif");if (!camif_clk || IS_ERR(camif_clk)) {printk(KERN_ERR"failed to get CAMIF clock source\n");return ;}/* 使能时钟 */clk_enable(camif_clk);/* 获取时钟"camif-upll" */camif_upll_clk = clk_get(NULL, "camif-upll");if (!camif_upll_clk || IS_ERR(camif_upll_clk)) {printk(KERN_ERR"failed to get CAMCLK clock source\n");return ;}/* 设置时钟CAMCLK = 24MHz */clk_set_rate(camif_upll_clk, 24000000);mdelay(50);
}/** @brief 复位摄像头,复位时序:1->0->1* @note 1、S3C2440提供的复位时序(CAMIF)为:0->1->0(0:正常工作的电平,1:复位电平)* 实验证明,该复位时序与使用的OV7740需要的复位时序(1->0->1)不符合。* 2、因此,需要结合OV7740的具体复位时序设置寄存器* @return 无*/
static void cmos_ov7740_reset(void)
{*cigctrl |= (1 << 30); /**< CamRest */mdelay(30);*cigctrl &= ~(1 << 30);mdelay(30);*cigctrl |= (1 << 30);mdelay(30);
}/** @brief 通过IIC总线初始化摄像头模块* @return 无*/
static void cmos_ov7740_init(void)
{int i;unsigned int mid;/* 读ID */mid = i2c_smbus_read_byte_data(s_cmos_ov7740_client, 0x0a) << 8;mid |= i2c_smbus_read_byte_data(s_cmos_ov7740_client, 0x0b);printk("cmos_ov7740 id :0x%x\n", mid);/* 写数据进行初始化 */for (i = 0; i < OV7740_INIT_REGS_SIZE; i++) {i2c_smbus_write_byte_data(s_cmos_ov7740_client, ov7740_setting_30fps_VGA_640_480[i].regaddr, ov7740_setting_30fps_VGA_640_480[i].value);mdelay(2);}
}/** @brief 摄像头编码通道中断函数(驱动无使用编码通道,不设置)* @return IRQ_HANDLED*/
static irqreturn_t cmos_ov7740_camif_irq_c(int irq, void *dev_id)
{return IRQ_HANDLED;
}/** @brief 摄像预览通道中断函数* @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);/*!* 唤醒休眠的等待队列*/s_ev_cam = 1;wake_up_interruptible(&cam_wait_queue);return IRQ_HANDLED;
}/** @brief 在IIC总线设备驱动中找到对应的设备文件后就调用probe函数* @return 0:成功 其他值:失败*/
static int __devinit cmos_ov7740_probe(struct i2c_client *client,const struct i2c_device_id *id)
{int ret;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 硬件相关操作 *//*!* 映射相关的寄存器*/cmos_ov7740_reg_map();/*!* 设置相应的GPIO用于CAMIF*/cmos_ov7740_gpio_cfg();/*!* 复位一下CAMIF的控制器*/cmos_ov7740_camif_reset();/*!* 设置、使能时钟(使能HCLK,使能并设置CAMCLK = 24MHz)*/cmos_ov7740_clk_cfg();/*!* 复位一下摄像头模块*/cmos_ov7740_reset();/*!* 通过IIC总线初始化摄像头模块*/s_cmos_ov7740_client = client;cmos_ov7740_init();/*!* 注册两次中断(编码通道与预览通道):摄像头每采集一帧的数据会触发中断*/if (request_irq(IRQ_S3C2440_CAM_C, cmos_ov7740_camif_irq_c, IRQF_DISABLED, "CAM_C", NULL)){printk("%s request_irq failed\n", __func__);return -1;}if (request_irq(IRQ_S3C2440_CAM_P, cmos_ov7740_camif_irq_p, IRQF_DISABLED, "CAM_P", NULL)){printk("%s request_irq failed\n", __func__);return -1;} /*! * 注册结构体,-1:自动分配次设备号*/ret = video_register_device(&s_cmos_ov7740_vdev, VFL_TYPE_GRABBER, -1);if (ret) {printk("Unable to register video device (error=%i).\n", ret);return ret;}return 0;
}
3.2 相关的ioctl操作函数
由于CMOS摄像头读写数据采用的是read/write方式,与USB摄像头读写数据方式不同,所以涉及到的ioctl
操作如下:
// 表示它是一个摄像头设备.vidioc_querycap = cmos_ov7740_querycap,/* 用于列举、获得、测试、设置摄像头的数据的格式 */.vidioc_enum_fmt_vid_cap = cmos_ov7740_enum_fmt_vid_cap,.vidioc_g_fmt_vid_cap = cmos_ov7740_g_fmt_vid_cap,.vidioc_try_fmt_vid_cap = cmos_ov7740_try_fmt_vid_cap,.vidioc_s_fmt_vid_cap = cmos_ov7740_s_fmt_vid_cap,/* 缓冲区操作: 申请 */.vidioc_reqbufs = cmos_ov7740_reqbufs,/* 启动/停止 */.vidioc_streamon = cmos_ov7740_streamon,.vidioc_streamoff = cmos_ov7740_streamoff,
对于CMOS摄像头其大部分的ioctl操作不复杂,重点在缓冲区申请cmos_ov7740_reqbufs
与设备启动cmos_ov7740_streamon
,涉及到大量的寄存器操作(可以看二、S3C摄像头接口寄存器介绍)与格式计算。
- 对于缓冲区申请
cmos_ov7740_reqbufs
由于其所需的buffer大小都大于128KB,所以采用__get_free_pages()
分配内存,此时得到的地址为虚拟地址,由于需要在分配的内存中读写数据,所以调用__virt_to_phys()
得到该虚拟地址对应的物理地址。
/*!* 描述所分配用于CAMIF的缓冲区*/
typedef struct camif_buffer {unsigned int order; /**< 缓冲区大小 */unsigned long virt_base; /**< 缓冲区虚拟基地址 */unsigned long phy_base; /**< 缓冲区物理基地址 */
}CAMIF_BUFFER_T;/*!* 所需要分配的4个缓冲区的描述数据*/
static CAMIF_BUFFER_T img_buff[] = {{.order = 0,.virt_base = (unsigned long)NULL,.phy_base = (unsigned long)NULL,},{.order = 0,.virt_base = (unsigned long)NULL,.phy_base = (unsigned long)NULL,},{.order = 0,.virt_base = (unsigned long)NULL,.phy_base = (unsigned long)NULL,},{.order = 0,.virt_base = (unsigned long)NULL,.phy_base = (unsigned long)NULL,},
};/*!* @brief Step7:为该设备申请若干个缓冲区,由于每个buffer大小 > 128Kb,* 因此调用__get_free_pages获得虚拟地址,随后得到物理地址并存储到预览模式的寄存器* @return 0:成功,-ENOMEM:失败*/
static int cmos_ov7740_reqbufs(struct file *file,void *priv, struct v4l2_requestbuffers *p)
{int i;unsigned int order;order = get_order(s_buf_size);for (i = 0; i < 4; i++) {img_buff[i].order = order;img_buff[i].virt_base = __get_free_pages(GFP_KERNEL | ___GFP_DMA, img_buff[i].order);if (img_buff[i].virt_base == (unsigned long)NULL)goto error;img_buff[i].phy_base = __virt_to_phys(img_buff[i].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;return 0;error:for (i -= 1; i >= 0; i--) {free_pages(img_buff[i].virt_base, order);img_buff[i].phy_base = (unsigned long)NULL; }return -ENOMEM;
}
- 设备启动
cmos_ov7740_streamon
这个函数中,涉及到大量的寄存器操作,大致设置步骤如下:
①、CISRCFMT:设置传输方式与源数据的分辨率、颜色格式
②、CIWDOFST:设置窗口功能与裁剪大小
③、CIGCTRL:设置信号输入源,时钟、帧同步信号与行同步信号的极性
④、CIPRCTRL:由于使用到DMA传输,需设置其的主突发长度与剩余突发长度
⑤、CIPRSCPRERATIO、CIPRSCPREDST:设置预览缩放的变化系数、水平比、垂直比、目标宽度、目标高度
⑥、CIPRSCCTRL:设置预览主缩放的水平比、预览主缩放的垂直比、图像输出格式、预览缩放开始是否开始
⑦、CIPRTRGFMT:设置目标图片的水平像素大小、垂直像素大小、是否旋转(在设置格式的函数cmos_ov7740_s_fmt_vid_cap
进行设置)
⑧、CIPRTAREA:设置预览通道的目标区域
⑨、CIIMGCPT:使能摄像头控制器、使能编码通道、使能预览通道
/*!* @brief 计算预览模式下DMA处理数据的主突发长度和剩余突发长度* 出于速度的考虑,只考虑burst lengths : 4, 8, 16.* @return 无*/
static void CalculateBurstSize(unsigned int bytesperline, unsigned int *main_burst, unsigned int *remained_burst)
{unsigned int tmp;tmp = (bytesperline / 4) % 16;switch(tmp) {case 0:*main_burst = 16;*remained_burst = 16;break;case 4:*main_burst = 16;*remained_burst = 4;break;case 8:*main_burst = 16;*remained_burst = 8;break;default:tmp = (bytesperline / 4) % 8;switch(tmp) {case 0:*main_burst = 8;*remained_burst = 8;break;case 4:*main_burst = 8;*remained_burst = 4;break;default:*main_burst = 4;tmp = (bytesperline / 4) % 4;*remained_burst = ((tmp) ? tmp : 4);break;} break;}}/*!* @brief 获得摄像头接口的缩放系数* @return 无*/
static void camif_get_scaler_factor(unsigned int src, unsigned int tar, unsigned int *ratio, unsigned int *shift)
{if (src >= (64 * tar)) { return ; /* Out Of Horizontal Scale Range */ }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; }
}/*!* @brief 计算预览模式下缩放信息* @return 无*/
static void cmos_ov7740_calculate_scaler_info(void)
{unsigned int sx, sy;unsigned int tx, ty;sx = s_SRC_Width;sy = s_SRC_Height;tx = s_TargetHsize_Pr;ty = s_TargetVsize_Pr;printk("%s SRC_in(%d, %d),Target_out(%d, %d)\n", __func__, sx, sy, tx, ty);camif_get_scaler_factor(sx, tx, &s_sc.PreHorRatio, &s_sc.H_Shift);camif_get_scaler_factor(sy, ty, &s_sc.PreVerRatio, &s_sc.V_Shift);s_sc.PreDst_Width = sx / s_sc.PreHorRatio;s_sc.PreDst_Height = sy / s_sc.PreVerRatio;s_sc.MainHorRatio = (sx << 8) / (tx << s_sc.H_Shift); s_sc.MainVerRatio = (sy << 8) / (ty << s_sc.V_Shift);s_sc.SHfactor = 10 - (s_sc.H_Shift + s_sc.V_Shift);s_sc.ScaleUpDown = (tx >= sx) ? 1 : 0;
}/*!* @brief Step8:启动数据传输* @return 0:成功*/
static int cmos_ov7740_streamon(struct file *file, void *priv, enum v4l2_buf_type t)
{unsigned int main_burst;unsigned int remained_burst;/*!* CISRCFMT:* bit[31] -- 选择传输方式为BT601(1)或者BT656(0)* bit[30] -- 设置偏移值(0 = +0 (正常情况下) - for YCbCr)* bit[29] -- 保留位,必须设置为0* bit[28:16] -- 设置源图片的水平像素值(640)* bit[15:14] -- 设置源图片的颜色顺序(0x0c(OV7740寄存器) --> 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);s_SRC_Width = CAM_SRC_HSIZE - WinHorOfst * 2;s_SRC_Height = CAM_SRC_VSIZE - WinVerOfst * 2;/*!* CIGCTRL:* bit[31] -- 软件复位CAMIF控制器* bit[30] -- 用于复位外部摄像头模块* bit[29] -- 保留位,必须设置为1* bit[28:27] -- 用于选择信号源(00 = 输入源来自摄像头模块,其他均为测试使用)* bit[26] -- 设置像素时钟的极性(猜0)** 极性的确定需要对比芯片手册与具体器件的时序,一致则不需反转0,否则反转1* bit[25] -- 设置VSYNC(帧同步信号)的极性(0)* bit[24] -- 设置HREF(行同步信号)的极性(0)*/*cigctrl |= (1 << 29) | (0 << 27) | (0 << 26) | (0 << 25) | (0 << 24);/*!* CIPRCTRL:* 对于DMA通信,若需要传的信息为48KB,但一次性传输不了,则拆分为如下:* 48KB = 16KB + 16KB + 16KB + 0KB,16KB为主突发长度,0KB为剩余突发长度* bit[23:19] -- 主突发长度(Main_burst)* bit[18:14] -- 剩余突发长度(Remained_burst)* bit[2] -- 是否使能LastIRQ功能(采集一帧数据后触发的中断,不使能)*/CalculateBurstSize(s_bytesperline, &main_burst, &remained_burst);*ciprctrl = (main_burst << 19) | (remained_burst << 14) | (1 << 2);/*!* CIPRSCPRERATIO: 预览预缩放比例控制* bit[31:28] -- 预览缩放的变化系数(SHfactor_Pr)* bit[22:16] -- 预览缩放的水平比(PreHorRatio_Pr)* bit[6:0] -- 预览缩放的垂直比(PreVerRatio_Pr)* * CIPRSCPREDST: 预览预缩放目标格式* bit[27:16] -- 预览缩放的目标宽度(PreDstWidth_Pr)* bit[11:0] -- 预览缩放的目标高度(PreDstHeight_Pr)* * CIPRSCCTRL: 预览控制的主要标量* bit[29:28] -- 告诉摄像头控制器(图片是缩小、放大)(ScaleUpDown_Pr)* bit[24:16] -- 预览主缩放的水平比(MainHorRatio_Pr)* bit[8:0] -- 预览主缩放的垂直比(MainVerRatio_Pr)* * bit[31] -- 必须固定设置为1* bit[30] -- 设置图像输出格式是RGB16、RGB24* bit[15] -- 预览缩放开始*/cmos_ov7740_calculate_scaler_info();*ciprscpreratio = (s_sc.SHfactor << 28) | (s_sc.PreHorRatio << 16) |\(s_sc.PreVerRatio << 0);*ciprscpredst = (s_sc.PreDst_Width << 16) | (s_sc.PreDst_Height << 0);*ciprscctrl |= (1 << 31) | (s_sc.ScaleUpDown << 28) |\(s_sc.MainHorRatio << 16) | (s_sc.MainVerRatio << 0);/*!* CIPRTAREA: * 表示预览通道的目标区域(缩放后目标图片的大小)*/*ciprtarea = s_TargetHsize_Pr * s_TargetVsize_Pr;/*!* CIIMGCPT: 图像捕获使能控制* bit[31] -- 用来使能摄像头控制器* bit[30] -- 使能编码通道* bit[29] -- 使能预览通道*/*ciimgcpt = (1 << 31) | (1 << 29);*ciprscctrl |= (1 << 15);return 0;
}
3.3 应用程序读出数据函数
使用的CMOS摄像头,其对数据的读写与USB摄像头不一样,调用的是v4l2_file_operations.read()
函数:
- 先获取应用程序所要读取数据的大小 与 驱动程序分配内存的大小二者的最小值;
- 让程序进入(可中断)休眠,待预览通道中断函数发生时,才唤醒程序
- 唤醒后把大小为 1、中获取的最小值的数据copy到用户空间
- 清除标志位
/** @brief 摄像预览通道中断函数* @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);/*!* 唤醒休眠的等待队列*/s_ev_cam = 1;wake_up_interruptible(&cam_wait_queue);return IRQ_HANDLED;
}/*!* @brief 应用程序读出数据函数* @return 成功:返回实际读取到数据大小,失败:-EFAULT*/
static ssize_t cmos_ov7740_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{int i;size_t end;end = min_t(size_t, s_buf_size ,count);/*!* 一开始程序休眠(可中断)*/wait_event_interruptible(cam_wait_queue, s_ev_cam);/*!* 唤醒后把数据copy到用户空间*/for (i = 0; i < 4; i++) {if (copy_to_user(buf, (void *)img_buff[i].virt_base, end))return -EFAULT;}s_ev_cam = 0; /**< 清中断 */return end;
}
4、完整的cmos_ov7740_drv.c程序
/******************************************************************************** Copyleft (c) 2021 Kcode** @file cmos_ov7740_drv.c* @brief cmos_0v7740摄像头的驱动文件* @author K* @version 0.0.1* @date 2021-07-26* @license MulanPSL-1.0** 文件修改历史:* <时间> | <版本> | <作者> | <描述>* 2021-07-31 | v0.0.1 | Kcode | cmos_0v7740摄像头的驱动文件* -----------------------------------------------------------------------------******************************************************************************/#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <linux/mm.h>
#include <asm/atomic.h>
#include <asm/unaligned.h>#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf-core.h>#include <linux/clk.h>
#include <asm/io.h>#define CAM_SRC_HSIZE (640) /**< 摄像头水平分辨率 */
#define CAM_SRC_VSIZE (480) /**< 摄像头垂直分辨率 *//* 摄像头颜色顺序 */
#define CAM_ORDER_YCbYCr (0)
#define CAM_ORDER_YCrYCb (1)
#define CAM_ORDER_CbYCrY (2)
#define CAM_ORDER_CrYCbY (3)#define WinHorOfst (0) /**< 水平方向裁剪大小 */
#define WinVerOfst (0) /**< 垂直方向裁剪大小 *//*!* 描述所分配用于CAMIF的缓冲区*/
typedef struct camif_buffer {unsigned int order; /**< 缓冲区大小 */unsigned long virt_base; /**< 缓冲区虚拟基地址 */unsigned long phy_base; /**< 缓冲区物理基地址 */
}CAMIF_BUFFER_T;/*!* 所需要分配的4个缓冲区的描述数据*/
static CAMIF_BUFFER_T img_buff[] = {{.order = 0,.virt_base = (unsigned long)NULL,.phy_base = (unsigned long)NULL,},{.order = 0,.virt_base = (unsigned long)NULL,.phy_base = (unsigned long)NULL,},{.order = 0,.virt_base = (unsigned long)NULL,.phy_base = (unsigned long)NULL,},{.order = 0,.virt_base = (unsigned long)NULL,.phy_base = (unsigned long)NULL,},
};/*!* 描述cmos_ov7740的I2C相关设置*/
typedef struct cmos_ov7740_i2c_value {unsigned char regaddr; /**< 寄存器地址 */unsigned char value; /**< 寄存器的值 */
}OV7740_I2C_T;/*!* 根据原厂提供的I2C初始化数组进行设置*/
OV7740_I2C_T ov7740_setting_30fps_VGA_640_480[] =
{{0x12, 0x80},{0x47, 0x02},{0x17, 0x27},{0x04, 0x40},{0x1B, 0x81},{0x29, 0x17},{0x5F, 0x03},{0x3A, 0x09},{0x33, 0x44},{0x68, 0x1A},{0x14, 0x38},{0x5F, 0x04},{0x64, 0x00},{0x67, 0x90},{0x27, 0x80},{0x45, 0x41},{0x4B, 0x40},{0x36, 0x2f},{0x11, 0x01},{0x36, 0x3f},{0x0c, 0x12},{0x12, 0x00},{0x17, 0x25},{0x18, 0xa0},{0x1a, 0xf0},{0x31, 0xa0},{0x32, 0xf0},{0x85, 0x08},{0x86, 0x02},{0x87, 0x01},{0xd5, 0x10},{0x0d, 0x34},{0x19, 0x03},{0x2b, 0xf8},{0x2c, 0x01},{0x53, 0x00},{0x89, 0x30},{0x8d, 0x30},{0x8f, 0x85},{0x93, 0x30},{0x95, 0x85},{0x99, 0x30},{0x9b, 0x85},{0xac, 0x6E},{0xbe, 0xff},{0xbf, 0x00},{0x38, 0x14},{0xe9, 0x00},{0x3D, 0x08},{0x3E, 0x80},{0x3F, 0x40},{0x40, 0x7F},{0x41, 0x6A},{0x42, 0x29},{0x49, 0x64},{0x4A, 0xA1},{0x4E, 0x13},{0x4D, 0x50},{0x44, 0x58},{0x4C, 0x1A},{0x4E, 0x14},{0x38, 0x11},{0x84, 0x70}
};#define OV7740_INIT_REGS_SIZE \(sizeof(ov7740_setting_30fps_VGA_640_480) / \sizeof(ov7740_setting_30fps_VGA_640_480[0]))/*!* 描述所支持颜色格式的结构体*/
typedef struct cmos_ov7740_fmt {char *name; /**< 格式名字 */u32 fourcc; /**< 格式id */int depth; /**< 颜色深度 */
}CMOS_OV7740_FMT;/*!* cmos_ov7740设备所支持的颜色格式*/
static CMOS_OV7740_FMT s_cmos_ov7740_formats[] = {{.name = "RGB565",.fourcc = V4L2_PIX_FMT_RGB565,.depth = 16,},{.name = "PACKED_RGB_888",.fourcc = V4L2_PIX_FMT_RGB24,.depth = 24,},
};/*!* 描述cmos_ov7740摄像头的压缩信息*/
typedef struct cmos_ov7740_scaler {unsigned int PreHorRatio; /**< 预览缩放的水平比 */unsigned int PreVerRatio; /**< 预览缩放的垂直比 */unsigned int H_Shift; /**< 水平变比 */unsigned int V_Shift; /**< 垂直变比 */unsigned int PreDst_Width; /**< 预览目标宽度 */unsigned int PreDst_Height; /**< 预览目标高度 */unsigned int MainHorRatio; /**< 主缩放水平比 */unsigned int MainVerRatio; /**< 主缩放垂直比 */unsigned int SHfactor; /**< 缩放变比 */unsigned int ScaleUpDown; /**< 放大/缩小 */
}CMOS_OV7740_SCALER;static CMOS_OV7740_SCALER s_sc;/* CMOS摄像头管脚相关的寄存器 */
static unsigned long *gpjcon;
static unsigned long *gpjdata;
static unsigned long *gpjup;/* CMOS摄像头接口相关的寄存器 */
static unsigned long *cisrcfmt;
static unsigned long *cigctrl;
static unsigned long *ciwdofst;/* 预览通道相关 */
static unsigned long *ciprclrsa1;
static unsigned long *ciprclrsa2;
static unsigned long *ciprclrsa3;
static unsigned long *ciprclrsa4;
static unsigned long *ciprtrgfmt;
static unsigned long *ciprctrl;
static unsigned long *ciprscpreratio;
static unsigned long *ciprscpredst;
static unsigned long *ciprscctrl;
static unsigned long *ciprtarea;
static unsigned long *ciimgcpt;/* 中断相关寄存器 */
static unsigned long *srcpnd;
static unsigned long *intpnd;
static unsigned long *subsrcpnd;static unsigned int s_SRC_Width; /**< (裁剪后)数据源的宽度 */
static unsigned int s_SRC_Height; /**< (裁剪后)数据源的高度 */
static unsigned int s_TargetHsize_Pr; /**< 目标图片的垂直分辨率 */
static unsigned int s_TargetVsize_Pr; /**< 目标图片的水平分辨率 */
static unsigned int s_bytesperline; /**< 每行数据的字节数 */static unsigned long s_buf_size; /**< 分配缓冲区的大小 */static DECLARE_WAIT_QUEUE_HEAD(cam_wait_queue); /**< 摄像头等待队列头部 */static volatile int s_ev_cam = 0; /**< 中断标志:0-无,1-中断 *//* 用来挂接与适配器匹配成功的从设备i2c_client的一个链表头 */
static struct i2c_client *s_cmos_ov7740_client;/*!* @brief Step1:打开cmos_ov7740_fops设备文件*/
static int cmos_ov7740_open(struct file *file)
{return 0;
}/*!* @brief Step2:查询cmos摄像头设备能力* 参考:uvc_v4l2_do_ioctl()*/
static int cmos_ov7740_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;/*!* V4L2_CAP_VIDEO_CAPTURE - 设备为视频捕捉设备* V4L2_CAP_READWRITE - 读写方式处理视频数据*/cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE;return 0;
}/*!* @brief Step3:列举cmos摄像头设备所支持的格式format* 参考:uvc_fmt()*/
static int cmos_ov7740_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{struct cmos_ov7740_fmt *fmt;if (f->index >= ARRAY_SIZE(s_cmos_ov7740_formats))return -EINVAL;fmt = &s_cmos_ov7740_formats[f->index];strlcpy(f->description, fmt->name, sizeof(f->description));f->pixelformat = fmt->fourcc;return 0;
}/*!* @brief Step4:返回当前所使用的格式*/
static int cmos_ov7740_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{return 0;
}/*!* @brief Step5:测试驱动程序是否支持某种格式* 参考:uvc_v4l2_try_format()/myvivi_vidioc_try_fmt_vid_cap()* @return 0:为摄像头且RGB565/24,-EINVAL不支持*/
static int cmos_ov7740_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_RGB24) && \(f->fmt.pix.pixelformat != V4L2_PIX_FMT_RGB565))return -EINVAL;return 0;
}/*!* @brief Step6:设置所支持的格式* @return 0:成功,其他值:失败*/
static int cmos_ov7740_s_fmt_vid_cap(struct file *file,void *priv, struct v4l2_format *f)
{int ret;int bpp;int rgb_fmt;/*!* 测试是否支持该格式*/ret = cmos_ov7740_try_fmt_vid_cap(file, NULL, f); if (ret < 0)return ret;/*!* 格式大小根据应用程序传下来的参数*/s_TargetHsize_Pr = f->fmt.pix.width;s_TargetVsize_Pr = f->fmt.pix.height;rgb_fmt = ((f->fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) ? 1 : 0);*ciprscctrl &= ~(1 << 30);*ciprscctrl |= (rgb_fmt << 30);bpp = ((f->fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) ? 32 : 16);f->fmt.pix.bytesperline = (f->fmt.pix.width * bpp) >> 3;f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;s_bytesperline = f->fmt.pix.bytesperline;s_buf_size = f->fmt.pix.sizeimage;/*!* CIPRTRGFMT:* bit[28:16] -- 表示目标图片(最终存放在缓存中)的水平像素大小(TargetHsize_Pr)* bit[15:14] -- 是否旋转,我们这个驱动就不选择了* bit[12:0] -- 表示目标图片(最终存放在缓存中)的垂直像素大小(TargetVsize_Pr)*/*ciprtrgfmt = (s_TargetHsize_Pr << 16) | (0x0 << 14) | (s_TargetVsize_Pr << 0);return 0;
}/*!* @brief Step7:为该设备申请若干个缓冲区,由于每个buffer大小 > 128Kb,* 因此调用__get_free_pages获得虚拟地址,随后得到物理地址并存储到预览模式的寄存器* @return 0:成功,-ENOMEM:失败*/
static int cmos_ov7740_reqbufs(struct file *file,void *priv, struct v4l2_requestbuffers *p)
{int i;unsigned int order;order = get_order(s_buf_size);for (i = 0; i < 4; i++) {img_buff[i].order = order;img_buff[i].virt_base = __get_free_pages(GFP_KERNEL | ___GFP_DMA, img_buff[i].order);if (img_buff[i].virt_base == (unsigned long)NULL)goto error;img_buff[i].phy_base = __virt_to_phys(img_buff[i].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;return 0;error:for (i -= 1; i >= 0; i--) {free_pages(img_buff[i].virt_base, order);img_buff[i].phy_base = (unsigned long)NULL; }return -ENOMEM;
}/*!* @brief 计算预览模式下DMA处理数据的主突发长度和剩余突发长度* 出于速度的考虑,只考虑burst lengths : 4, 8, 16.* @return 无*/
static void CalculateBurstSize(unsigned int bytesperline, unsigned int *main_burst, unsigned int *remained_burst)
{unsigned int tmp;tmp = (bytesperline / 4) % 16;switch(tmp) {case 0:*main_burst = 16;*remained_burst = 16;break;case 4:*main_burst = 16;*remained_burst = 4;break;case 8:*main_burst = 16;*remained_burst = 8;break;default:tmp = (bytesperline / 4) % 8;switch(tmp) {case 0:*main_burst = 8;*remained_burst = 8;break;case 4:*main_burst = 8;*remained_burst = 4;break;default:*main_burst = 4;tmp = (bytesperline / 4) % 4;*remained_burst = ((tmp) ? tmp : 4);break;} break;}}/*!* @brief 获得摄像头接口的缩放系数* @return 无*/
static void camif_get_scaler_factor(unsigned int src, unsigned int tar, unsigned int *ratio, unsigned int *shift)
{if (src >= (64 * tar)) { return ; /* Out Of Horizontal Scale Range */ }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; }
}/*!* @brief 计算预览模式下缩放信息* @return 无*/
static void cmos_ov7740_calculate_scaler_info(void)
{unsigned int sx, sy;unsigned int tx, ty;sx = s_SRC_Width;sy = s_SRC_Height;tx = s_TargetHsize_Pr;ty = s_TargetVsize_Pr;printk("%s SRC_in(%d, %d),Target_out(%d, %d)\n", __func__, sx, sy, tx, ty);camif_get_scaler_factor(sx, tx, &s_sc.PreHorRatio, &s_sc.H_Shift);camif_get_scaler_factor(sy, ty, &s_sc.PreVerRatio, &s_sc.V_Shift);s_sc.PreDst_Width = sx / s_sc.PreHorRatio;s_sc.PreDst_Height = sy / s_sc.PreVerRatio;s_sc.MainHorRatio = (sx << 8) / (tx << s_sc.H_Shift); s_sc.MainVerRatio = (sy << 8) / (ty << s_sc.V_Shift);s_sc.SHfactor = 10 - (s_sc.H_Shift + s_sc.V_Shift);s_sc.ScaleUpDown = (tx >= sx) ? 1 : 0;
}/*!* @brief Step8:启动数据传输* @return 0:成功*/
static int cmos_ov7740_streamon(struct file *file, void *priv, enum v4l2_buf_type t)
{unsigned int main_burst;unsigned int remained_burst;/*!* CISRCFMT:* bit[31] -- 选择传输方式为BT601(1)或者BT656(0)* bit[30] -- 设置偏移值(0 = +0 (正常情况下) - for YCbCr)* bit[29] -- 保留位,必须设置为0* bit[28:16] -- 设置源图片的水平像素值(640)* bit[15:14] -- 设置源图片的颜色顺序(0x0c(OV7740寄存器) --> 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);s_SRC_Width = CAM_SRC_HSIZE - WinHorOfst * 2;s_SRC_Height = CAM_SRC_VSIZE - WinVerOfst * 2;/*!* CIGCTRL:* bit[31] -- 软件复位CAMIF控制器* bit[30] -- 用于复位外部摄像头模块* bit[29] -- 保留位,必须设置为1* bit[28:27] -- 用于选择信号源(00 = 输入源来自摄像头模块,其他均为测试使用)* bit[26] -- 设置像素时钟的极性(猜0)** 极性的确定需要对比芯片手册与具体器件的时序,一致则不需反转0,否则反转1* bit[25] -- 设置VSYNC(帧同步信号)的极性(0)* bit[24] -- 设置HREF(行同步信号)的极性(0)*/*cigctrl |= (1 << 29) | (0 << 27) | (0 << 26) | (0 << 25) | (0 << 24);/*!* CIPRCTRL:* 对于DMA通信,若需要传的信息为48KB,但一次性传输不了,则拆分为如下:* 48KB = 16KB + 16KB + 16KB + 0KB,16KB为主突发长度,0KB为剩余突发长度* bit[23:19] -- 主突发长度(Main_burst)* bit[18:14] -- 剩余突发长度(Remained_burst)* bit[2] -- 是否使能LastIRQ功能(采集一帧数据后触发的中断,不使能)*/CalculateBurstSize(s_bytesperline, &main_burst, &remained_burst);*ciprctrl = (main_burst << 19) | (remained_burst << 14) | (1 << 2);/*!* CIPRSCPRERATIO: 预览预缩放比例控制* bit[31:28] -- 预览缩放的变化系数(SHfactor_Pr)* bit[22:16] -- 预览缩放的水平比(PreHorRatio_Pr)* bit[6:0] -- 预览缩放的垂直比(PreVerRatio_Pr)* * CIPRSCPREDST: 预览预缩放目标格式* bit[27:16] -- 预览缩放的目标宽度(PreDstWidth_Pr)* bit[11:0] -- 预览缩放的目标高度(PreDstHeight_Pr)* * CIPRSCCTRL: 预览控制的主要标量* bit[29:28] -- 告诉摄像头控制器(图片是缩小、放大)(ScaleUpDown_Pr)* bit[24:16] -- 预览主缩放的水平比(MainHorRatio_Pr)* bit[8:0] -- 预览主缩放的垂直比(MainVerRatio_Pr)* * bit[31] -- 必须固定设置为1* bit[30] -- 设置图像输出格式是RGB16、RGB24* bit[15] -- 预览缩放开始*/cmos_ov7740_calculate_scaler_info();*ciprscpreratio = (s_sc.SHfactor << 28) | (s_sc.PreHorRatio << 16) |\(s_sc.PreVerRatio << 0);*ciprscpredst = (s_sc.PreDst_Width << 16) | (s_sc.PreDst_Height << 0);*ciprscctrl |= (1 << 31) | (s_sc.ScaleUpDown << 28) |\(s_sc.MainHorRatio << 16) | (s_sc.MainVerRatio << 0);/*!* CIPRTAREA: * 表示预览通道的目标区域(缩放后目标图片的大小)*/*ciprtarea = s_TargetHsize_Pr * s_TargetVsize_Pr;/*!* CIIMGCPT: 图像捕获使能控制* bit[31] -- 用来使能摄像头控制器* bit[30] -- 使能编码通道* bit[29] -- 使能预览通道*/*ciimgcpt = (1 << 31) | (1 << 29);*ciprscctrl |= (1 << 15);return 0;
}/*!* @brief Step0:关闭设备* 参考:uvc_video_enable()-->uvc_commit_video()/uvc_init_video()* @return 0:成功*/
static int cmos_ov7740_streamoff(struct file *file, void *priv, enum v4l2_buf_type t)
{*ciprscctrl &= ~(1 << 15);*ciimgcpt &= ~((1 << 31) | (1 << 29));return 0;
}/*!* 分配、设置v4l2_ioctl_ops结构体*/
static const struct v4l2_ioctl_ops s_cmos_ov7740_ioctl_ops = {// 表示它是一个摄像头设备.vidioc_querycap = cmos_ov7740_querycap,/* 用于列举、获得、测试、设置摄像头的数据的格式 */.vidioc_enum_fmt_vid_cap = cmos_ov7740_enum_fmt_vid_cap,.vidioc_g_fmt_vid_cap = cmos_ov7740_g_fmt_vid_cap,.vidioc_try_fmt_vid_cap = cmos_ov7740_try_fmt_vid_cap,.vidioc_s_fmt_vid_cap = cmos_ov7740_s_fmt_vid_cap,/* 缓冲区操作: 申请 */.vidioc_reqbufs = cmos_ov7740_reqbufs,/* 使用的是read方式读数据,/查询/放入队列/取出队列 操作不需要*///.vidioc_querybuf = cmos_ov7740_querybuf,//.vidioc_qbuf = cmos_ov7740_qbuf,//.vidioc_dqbuf = cmos_ov7740_dqbuf,/* 查询/获得/设置属性 *///.vidioc_queryctrl = cmos_ov7740_queryctrl,//.vidioc_g_ctrl = cmos_ov7740_g_ctrl,//.vidioc_s_ctrl = cmos_ov7740_s_ctrl,/* 启动/停止 */.vidioc_streamon = cmos_ov7740_streamon,.vidioc_streamoff = cmos_ov7740_streamoff,
};/*!* * @brief 关闭cmos_ov7740_fops设备文件*/
static int cmos_ov7740_close(struct file *file)
{cmos_ov7740_streamoff(NULL, NULL, 0);return 0;
}/*!* @brief 应用程序读出数据函数* @return 成功:返回实际读取到数据大小,失败:-EFAULT*/
static ssize_t cmos_ov7740_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{int i;size_t end;end = min_t(size_t, s_buf_size ,count);/*!* 一开始程序休眠(可中断)*/wait_event_interruptible(cam_wait_queue, s_ev_cam);/*!* 唤醒后把数据copy到用户空间*/for (i = 0; i < 4; i++) {if (copy_to_user(buf, (void *)img_buff[i].virt_base, end))return -EFAULT;}s_ev_cam = 0; /**< 清中断 */return end;
}static const struct v4l2_file_operations s_cmos_ov7740_fops = {.owner = THIS_MODULE,.open = cmos_ov7740_open,.release = cmos_ov7740_close,.unlocked_ioctl = video_ioctl2,.read = cmos_ov7740_read
};/** @brief 必须的函数,否则在加载驱动时会出错* @return 无*/
static void cmos_ov7740_release(struct video_device *vdev)
{int i;unsigned int order;/* 释放缓存 */order = get_order(s_buf_size);for (i = 0; i < 4; i++) {free_pages(img_buff[i].virt_base, order);img_buff[i].phy_base = (unsigned long)NULL; }}/*!* 分配、设置video_device结构体*/
static struct video_device s_cmos_ov7740_vdev = {.name = "cmos_ov7740",.release = cmos_ov7740_release,.fops = &s_cmos_ov7740_fops,.ioctl_ops = &s_cmos_ov7740_ioctl_ops,
};/** @brief 设置相应的GPIO用于CMOS摄像头的CAMIF* @return 无*/
static void cmos_ov7740_gpio_cfg(void)
{*gpjcon = 0x2aaaaaa; /**< 查看手册可知所有控制位设置为10 */*gpjdata = 0;*gpjup = 0; /**< 使能上拉电阻 */
}/** @brief 映射摄像头相关的寄存器* @return 无*/
static void cmos_ov7740_reg_map(void)
{/* CAMERA GPIO */gpjcon = ioremap(0x560000d0, 4);gpjdata = ioremap(0x560000d4, 4);gpjup = ioremap(0x560000d8, 4);/* CAMERA IF */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);}/** @brief 取消映射摄像头相关的寄存器* @return 无*/
static void cmos_ov7740_reg_unmap(void)
{/* CAMERA GPIO */iounmap(gpjcon);iounmap(gpjdata);iounmap(gpjup);/* CAMERA IF */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);
}/** @brief 复位一下CAMIF的控制器* @return 无*/
static void cmos_ov7740_camif_reset(void)
{/* 传输方式为BT601 */*cisrcfmt |= (1 << 31);;/* 复位 */*cigctrl |= (1 << 31);mdelay(10);/* 清零:正常工作 */*cigctrl &= ~(1 << 31);mdelay(10);
}/** @brief 设置、使能时钟(使能HCLK,使能并设置CAMCLK = 24MHz)* @return 无*/
static void cmos_ov7740_clk_cfg(void)
{struct clk *camif_clk;struct clk *camif_upll_clk;/* 获取时钟"camif" */camif_clk = clk_get(NULL, "camif");if (!camif_clk || IS_ERR(camif_clk)) {printk(KERN_ERR"failed to get CAMIF clock source\n");return ;}/* 使能时钟 */clk_enable(camif_clk);/* 获取时钟"camif-upll" */camif_upll_clk = clk_get(NULL, "camif-upll");if (!camif_upll_clk || IS_ERR(camif_upll_clk)) {printk(KERN_ERR"failed to get CAMCLK clock source\n");return ;}/* 设置时钟CAMCLK = 24MHz */clk_set_rate(camif_upll_clk, 24000000);mdelay(50);
}/** @brief 复位摄像头,复位时序:1->0->1* @note 1、S3C2440提供的复位时序(CAMIF)为:0->1->0(0:正常工作的电平,1:复位电平)* 实验证明,该复位时序与使用的OV7740需要的复位时序(1->0->1)不符合。* 2、因此,需要结合OV7740的具体复位时序设置寄存器* @return 无*/
static void cmos_ov7740_reset(void)
{*cigctrl |= (1 << 30); /**< CamRest */mdelay(30);*cigctrl &= ~(1 << 30);mdelay(30);*cigctrl |= (1 << 30);mdelay(30);
}/** @brief 通过IIC总线初始化摄像头模块* @return 无*/
static void cmos_ov7740_init(void)
{int i;unsigned int mid;/* 读ID */mid = i2c_smbus_read_byte_data(s_cmos_ov7740_client, 0x0a) << 8;mid |= i2c_smbus_read_byte_data(s_cmos_ov7740_client, 0x0b);printk("cmos_ov7740 id :0x%x\n", mid);/* 写数据进行初始化 */for (i = 0; i < OV7740_INIT_REGS_SIZE; i++) {i2c_smbus_write_byte_data(s_cmos_ov7740_client, ov7740_setting_30fps_VGA_640_480[i].regaddr, ov7740_setting_30fps_VGA_640_480[i].value);mdelay(2);}
}/** @brief 摄像头编码通道中断函数(驱动无使用编码通道,不设置)* @return IRQ_HANDLED*/
static irqreturn_t cmos_ov7740_camif_irq_c(int irq, void *dev_id)
{return IRQ_HANDLED;
}/** @brief 摄像预览通道中断函数* @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);/*!* 唤醒休眠的等待队列*/s_ev_cam = 1;wake_up_interruptible(&cam_wait_queue);return IRQ_HANDLED;
}/** @brief 在IIC总线设备驱动中找到对应的设备文件后就调用probe函数* @return 0:成功 其他值:失败*/
static int __devinit cmos_ov7740_probe(struct i2c_client *client,const struct i2c_device_id *id)
{int ret;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 硬件相关操作 *//*!* 映射相关的寄存器*/cmos_ov7740_reg_map();/*!* 设置相应的GPIO用于CAMIF*/cmos_ov7740_gpio_cfg();/*!* 复位一下CAMIF的控制器*/cmos_ov7740_camif_reset();/*!* 设置、使能时钟(使能HCLK,使能并设置CAMCLK = 24MHz)*/cmos_ov7740_clk_cfg();/*!* 复位一下摄像头模块*/cmos_ov7740_reset();/*!* 通过IIC总线初始化摄像头模块*/s_cmos_ov7740_client = client;cmos_ov7740_init();/*!* 注册两次中断(编码通道与预览通道):摄像头每采集一帧的数据会触发中断*/if (request_irq(IRQ_S3C2440_CAM_C, cmos_ov7740_camif_irq_c, IRQF_DISABLED, "CAM_C", NULL)){printk("%s request_irq failed\n", __func__);return -1;}if (request_irq(IRQ_S3C2440_CAM_P, cmos_ov7740_camif_irq_p, IRQF_DISABLED, "CAM_P", NULL)){printk("%s request_irq failed\n", __func__);return -1;} /*! * 注册结构体,-1:自动分配次设备号*/ret = video_register_device(&s_cmos_ov7740_vdev, VFL_TYPE_GRABBER, -1);if (ret) {printk("Unable to register video device (error=%i).\n", ret);return ret;}return 0;
}/** @brief 移除函数* @return 0:成功 -1:失败*/
static int __devexit cmos_ov7740_remove(struct i2c_client *client)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);cmos_ov7740_reg_unmap();free_irq(IRQ_S3C2440_CAM_C, NULL);free_irq(IRQ_S3C2440_CAM_P, NULL);video_unregister_device(&s_cmos_ov7740_vdev);return 0;
}/*!* 支持设备的名字*/
static const struct i2c_device_id s_cmos_ov7740_id_table[] = {{ "cmos_ov7740", 0 },{}
};/*!* 设置i2c设备驱动结构体*/
static struct i2c_driver s_cmos_ov7740_drv = {.driver = {.name = "cmos_ov7740",.owner = THIS_MODULE,},.probe = cmos_ov7740_probe,.remove = __devexit_p(cmos_ov7740_remove),.id_table = s_cmos_ov7740_id_table,
};/** @brief cmos_ov7740_dev初始化函数(入口函数)* @return 0:成功 -1:失败*/
static int cmos_ov7740_drv_init(void)
{i2c_add_driver(&s_cmos_ov7740_drv);return 0;
}/** @brief cmos_ov7740_dev退出函数(出口函数)* @return 无*/
static void cmos_ov7740_drv_exit(void)
{i2c_del_driver(&s_cmos_ov7740_drv);
}module_init(cmos_ov7740_drv_init);
module_exit(cmos_ov7740_drv_exit);
MODULE_LICENSE("GPL");