根据硬件OV7740,编写一个CMOS摄像头驱动程序:
注:此驱动程序是用在学习的时候编写的,不适合企业的应用。
下面是寄存器的中文介绍,如果英文阅读能力较强的就可以直接看手册了。
对于CMOS摄像头,在前面的博文【2.6 视频监控—CMOS摄像头的硬件原理】已经介绍了,CMOS摄像头模块也是一个I2C设备,需要编写符合IIC设备的架构的驱动,从而实现初始化和灵活的控制。
对于一个I2C的总线驱动,需要xxx_dev.c
设备文件与xxx_drv.c
设备驱动文件,二者可根据结构体中的变量.name
来进行匹配,当一致时,会调用.probe
函数,在这个函数里面可以实现我们具体要做的事情。
i2c_driver
结构体:/*!
* 设置i2c设备驱动结构体
*/
static struct i2c_driver s_cmos_ov7740_drv = {
.driver = {
.name = "cmos_0v7740",
.owner = THIS_MODULE,
},
.probe = cmos_ov7740_probe,
.remove = __devexit_p(cmos_ov7740_remove),
.id_table = s_cmos_ov7740_id_table,
};
/*
* @brief cmos_ov7740_dev初始化函数(入口函数)
* @return 0:成功 -1:失败
*/
static int cmos_ov7740_drv_init(void)
{
i2c_add_driver(&s_cmos_ov7740_drv);
return 0;
}
/*
* @brief cmos_ov7740_dev退出函数(出口函数)
* @return 无
*/
static void cmos_ov7740_drv_exit(void)
{
video_unregister_device(&s_cmos_ov7740_vdev);
i2c_del_driver(&s_cmos_ov7740_drv);
}
i2c_driver
结构体的.probe
函数中,注册一个video_device
结构体video_device
结构体,需要分配、设置、注册这个结构体:video_device结构体
中的.fops
,在v4l2_file_operations
结构体中包含了对设备实际操作open、close、read
;video_device结构体
中的.unlocked_ioctl
,保存的是与该设备ioctl
操作相关的结构体v4l2_ioctl_ops
。/*!
* 分配、设置v4l2_ioctl_ops结构体
*/
static const struct v4l2_ioctl_ops s_cmos_ov7740_ioctl_ops = {
// 表示它是一个摄像头设备
.vidioc_querycap = cmos_ov7740_querycap,
/* 用于列举、获得、测试、设置摄像头的数据的格式 */
.vidioc_enum_fmt_vid_cap = cmos_ov7740_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = cmos_ov7740_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = cmos_ov7740_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = cmos_ov7740_s_fmt_vid_cap,
/* 缓冲区操作: 申请 */
.vidioc_reqbufs = cmos_ov7740_reqbufs,
/* 使用的是read方式读数据,/查询/放入队列/取出队列 操作不需要*/
//.vidioc_querybuf = cmos_ov7740_querybuf,
//.vidioc_qbuf = cmos_ov7740_qbuf,
//.vidioc_dqbuf = cmos_ov7740_dqbuf,
/* 查询/获得/设置属性 */
//.vidioc_queryctrl = cmos_ov7740_queryctrl,
//.vidioc_g_ctrl = cmos_ov7740_g_ctrl,
//.vidioc_s_ctrl = cmos_ov7740_s_ctrl,
/* 启动/停止 */
.vidioc_streamon = cmos_ov7740_streamon,
.vidioc_streamoff = cmos_ov7740_streamoff,
};
/*!
*
* @brief 关闭cmos_ov7740_fops设备文件
*/
static int cmos_ov7740_close(struct file *file)
{
cmos_ov7740_streamoff(NULL, NULL, 0);
return 0;
}
/*!
* @brief 应用程序读出数据函数
*/
static ssize_t cmos_ov7740_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
return 0;
}
static const struct v4l2_file_operations s_cmos_ov7740_fops = {
.owner = THIS_MODULE,
.open = cmos_ov7740_open,
.release = cmos_ov7740_close,
.unlocked_ioctl = video_ioctl2,
.read = cmos_ov7740_read
};
/*
* @brief 必须的函数,否则在加载驱动时会出错
* @return 无
*/
static void cmos_ov7740_release(struct video_device *vdev)
{}
/*!
* 分配、设置video_device结构体
*/
static struct video_device s_cmos_ov7740_vdev = {
.name = "cmos_ov7740",
.release = cmos_ov7740_release,
.fops = &s_cmos_ov7740_fops,
.ioctl_ops = &s_cmos_ov7740_ioctl_ops,
};
/*******************************************************************************
* Copyleft (c) 2021 Kcode
*
* @file cmos_ov7740_drv.c
* @brief cmos_0v7740摄像头的驱动文件
* @author K
* @version 0.0.1
* @date 2021-07-26
* @license MulanPSL-1.0
*
* 文件修改历史:
* <时间> | <版本> | <作者> | <描述>
* 2021-07-29 | v0.0.1 | Kcode | cmos_0v7740摄像头的驱动文件
* -----------------------------------------------------------------------------
******************************************************************************/
#include
#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");
/*******************************************************************************
* 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");
下面会挑选一些比较有代表性的编写问题来介绍代码
摄像头的初始化的实现是在cmos_ov7740_probe
函数实现,即cmos_oc7740_dev.c
设备文件与cmos_oc7740_drv.c
设备驱动文件,二者匹配一致时,所调用的.probe
函数
主要步骤:
代码实现如下:
/*
* @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;
}
由于CMOS摄像头读写数据采用的是read/write方式,与USB摄像头读写数据方式不同,所以涉及到的ioctl
操作如下:
// 表示它是一个摄像头设备
.vidioc_querycap = cmos_ov7740_querycap,
/* 用于列举、获得、测试、设置摄像头的数据的格式 */
.vidioc_enum_fmt_vid_cap = cmos_ov7740_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = cmos_ov7740_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = cmos_ov7740_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = cmos_ov7740_s_fmt_vid_cap,
/* 缓冲区操作: 申请 */
.vidioc_reqbufs = cmos_ov7740_reqbufs,
/* 启动/停止 */
.vidioc_streamon = cmos_ov7740_streamon,
.vidioc_streamoff = cmos_ov7740_streamoff,
对于CMOS摄像头其大部分的ioctl操作不复杂,重点在缓冲区申请cmos_ov7740_reqbufs
与设备启动cmos_ov7740_streamon
,涉及到大量的寄存器操作(可以看二、S3C摄像头接口寄存器介绍)与格式计算。
cmos_ov7740_reqbufs
__get_free_pages()
分配内存,此时得到的地址为虚拟地址,由于需要在分配的内存中读写数据,所以调用__virt_to_phys()
得到该虚拟地址对应的物理地址。/*!
* 描述所分配用于CAMIF的缓冲区
*/
typedef struct camif_buffer {
unsigned int order; /**< 缓冲区大小 */
unsigned long virt_base; /**< 缓冲区虚拟基地址 */
unsigned long phy_base; /**< 缓冲区物理基地址 */
}CAMIF_BUFFER_T;
/*!
* 所需要分配的4个缓冲区的描述数据
*/
static CAMIF_BUFFER_T img_buff[] = {
{
.order = 0,
.virt_base = (unsigned long)NULL,
.phy_base = (unsigned long)NULL,
},
{
.order = 0,
.virt_base = (unsigned long)NULL,
.phy_base = (unsigned long)NULL,
},
{
.order = 0,
.virt_base = (unsigned long)NULL,
.phy_base = (unsigned long)NULL,
},
{
.order = 0,
.virt_base = (unsigned long)NULL,
.phy_base = (unsigned long)NULL,
},
};
/*!
* @brief Step7:为该设备申请若干个缓冲区,由于每个buffer大小 > 128Kb,
* 因此调用__get_free_pages获得虚拟地址,随后得到物理地址并存储到预览模式的寄存器
* @return 0:成功,-ENOMEM:失败
*/
static int cmos_ov7740_reqbufs(struct file *file,
void *priv, struct v4l2_requestbuffers *p)
{
int i;
unsigned int order;
order = get_order(s_buf_size);
for (i = 0; i < 4; i++) {
img_buff[i].order = order;
img_buff[i].virt_base = __get_free_pages(GFP_KERNEL | ___GFP_DMA, img_buff[i].order);
if (img_buff[i].virt_base == (unsigned long)NULL)
goto error;
img_buff[i].phy_base = __virt_to_phys(img_buff[i].virt_base);
}
*ciprclrsa1 = img_buff[0].phy_base;
*ciprclrsa2 = img_buff[1].phy_base;
*ciprclrsa3 = img_buff[2].phy_base;
*ciprclrsa4 = img_buff[3].phy_base;
return 0;
error:
for (i -= 1; i >= 0; i--) {
free_pages(img_buff[i].virt_base, order);
img_buff[i].phy_base = (unsigned long)NULL;
}
return -ENOMEM;
}
cmos_ov7740_streamon
cmos_ov7740_s_fmt_vid_cap
进行设置)/*!
* @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;
}
使用的CMOS摄像头,其对数据的读写与USB摄像头不一样,调用的是v4l2_file_operations.read()
函数:
/*
* @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;
}
/*******************************************************************************
* 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");