第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动

视频监控—从零写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)

第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动_第1张图片

1.2 窗口选择寄存器(CIWDOFST)

第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动_第2张图片

1.3 全局控制寄存器(CIGCTRL)

第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动_第3张图片

2、 CMOS摄像头预览通道相关的寄存器

2.1 RGB1起始地址寄存器(CIPRCLRSA1)

在这里插入图片描述

2.2 RGB2起始地址寄存器(CIPRCLRSA2)

在这里插入图片描述

2.3 RGB3起始地址寄存器(CIPRCLRSA3)

第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动_第4张图片

2.4 RGB4起始地址寄存(CIPRCLRSA4)

第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动_第5张图片

2.5 目标预览格式寄存器(CIPRTRGFMT)

第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动_第6张图片

2.6 预览DMA控制寄存器(CIPRCTRL)

第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动_第7张图片

  • 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.7 视频监控—从零写CMOS摄像头驱动_第8张图片

2.8 预览前计数器控制寄存器2(CIPRSCPREDST)

第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动_第9张图片

2.9 预览主计数器控制寄存器(CIPRSCCTRL)

第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动_第10张图片

2.10 预览DMA目标面积寄存器(CIPRTAREA)

在这里插入图片描述

2.11 预览状态寄存器(CIIMGCPT)

第三阶段应用层——2.7 视频监控—从零写CMOS摄像头驱动_第11张图片

三、程序编写

1、cmos_ov7740_drv.c框架

对于CMOS摄像头,在前面的博文【2.6 视频监控—CMOS摄像头的硬件原理】已经介绍了,CMOS摄像头模块也是一个I2C设备,需要编写符合IIC设备的架构的驱动,从而实现初始化和灵活的控制

对于一个I2C的总线驱动,需要xxx_dev.c设备文件与xxx_drv.c设备驱动文件,二者可根据结构体中的变量.name来进行匹配,当一致时,会调用.probe函数,在这个函数里面可以实现我们具体要做的事情。

  1. 编写一个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);
}
  1. i2c_driver结构体的.probe函数中,注册一个video_device结构体
  2. 对于video_device结构体,需要分配、设置、注册这个结构体:
    3.1 在video_device结构体.fopsv4l2_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,
};
  1. 最终框架
    经过对函数的补充定义,得到以下框架:
/*******************************************************************************
 * 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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*!
 * @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 
#include 
#include 
#include 
#include 
#include 
#include 

/*!
 * 板载的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函数
主要步骤:

  1. 映射相关的寄存器
  2. 设置相应的GPIO用于CAMIF
  3. 复位一下CAMIF的控制器
  4. 设置、使能时钟(使能HCLK,使能并设置CAMCLK = 24MHz)
  5. 复位一下摄像头模块,原因:IIC能够正常操作CMOS摄像头模块内部的寄存器的前提如下
    提供符合它需求的系统时钟(CAMCLK)
    需要给它一个复位信号
  6. 通过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摄像头接口寄存器介绍)与格式计算。

  1. 对于缓冲区申请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;
}
  1. 设备启动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. 先获取应用程序所要读取数据的大小 与 驱动程序分配内存的大小二者的最小值
  2. 程序进入(可中断)休眠,待预览通道中断函数发生时,才唤醒程序
  3. 唤醒后把大小为 1、中获取的最小值的数据copy到用户空间
  4. 清除标志位
/*
 * @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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#include 
#include 


#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");

你可能感兴趣的:(第三阶段应用层,嵌入式,linux,驱动程序)