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

news/2024/11/22 21:51:42/

视频监控—从零写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函数,在这个函数里面可以实现我们具体要做的事情。

  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 <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函数
主要步骤:

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

http://www.ppmy.cn/news/539073.html

相关文章

呱呱财经视频社区V5.6无插件官方版

名称&#xff1a;呱呱财经视频社区V5.6无插件官方版 版本&#xff1a;5.6 软件大小&#xff1a;19.80MB 软件语言&#xff1a;中文简体 软件授权&#xff1a;免费版 应用平台&#xff1a;WinXP/Win7/WinAll 呱呱财经是中国最大的股民视频社区,免费学习炒股技巧,分享高手赚钱心…

USB3.0高清视频和音频采集处理芯片——MS2131

MS2131是一款USB3.0高清视频和音频采集处理芯片&#xff0c;内部集成USB3.0 Device控制器、数据收发模块、音视频处理模块。MS2131可以通过USB3.0接口将HDMI输入的音视频信号传送到PC、智能手机、平板电脑上预览或采集。MS2131支持HDMI环出功能&#xff0c;支持USB host录制的同…

树莓派3B+开发板,搭建ffmpeg+nginx+usb摄像头+摄像头自带麦克风简单实现声音视频监控

方案一&#xff1a; 一、树莓派安装ffmpeg sudo apt-get install ffmpeg 二、安装nginx,nginx-rtmp sudo apt-get install nginx sudo apt-get install libnginx-mod-rtmp 三、编辑nginx.conf文件配置支持rtmp sudo nano /etc/nginx/nginx.conf 在http模块上添加以下配置&#…

【树莓派不吃灰】基础篇⑲ 搭建usb摄像头MJPG-streamer图片流监控,支持远程视频监控访问

目录 1. 前言2. 识别摄像头3. MJPG-streamer方案3.1 什么是 MJPG&#xff1f;3.2 MJPG 的优点&#xff1f;3.2 MJPG 的缺点&#xff1f; 4. 搭建usb摄像头监控4.1 开启树莓派摄像头开关4.2 查看设备文件4.3 安装必要的库4.4 下载 mjpg-streamer 安装文件4.5 切换到 /mjpg-strea…

音视频开发系列(7):完成本地摄像头直播推流

今天把读取本地摄像头将视频流推流到nginx服务器的直播代码学习完了&#xff0c;这里对代码的流程做一下记录&#xff0c;以便以后进行复习。 这边用到了opencv和ffmpeg的开源库(PS&#xff1a;在前面有进行分享)&#xff0c;配置环境在之前也有进行分享。 第一步&#xff1a…

树莓派+官方摄像头模块+VLC串流实时输出网络视频流

sudo apt-get update sudo apt-get install vlc sudo raspivid -o - -t 0 -w 640 -h 360 -fps 25|cvlc -vvv stream:///dev/stdin --sout #standard{accesshttp,muxts,dst:8090} :demuxh264 在电脑端&#xff0c;无论是Windows&#xff0c;Linux还是OSX&#xff0c;或者安卓机器…

10 种分布式系统必备模式

在当今的技术领域中&#xff0c;分布式系统已成为许多大型应用程序和平台的核心。构建高性能、可伸缩和可靠的分布式系统是一个复杂的挑战&#xff0c;需要合理的架构设计和模式选择。本文将介绍10个必备的分布式系统模式&#xff0c;帮助您更好地理解和应用这些模式以提升系统…

公众号自动回复消息添加跳转小程序链接

业务说明&#xff1a; 是要实现在小程序授权公众号收发消息功能 解决方案&#xff1a; 在用户关注后自动回复消息&#xff08;因为要实现授权前提是用户必须先关注了公众&#xff09;&#xff0c;在消息中添加可跳转小程序的链接&#xff0c;就可以将小程序的参数传到授权页面进…