设备文件
#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>
/* 第一个参数名字用于匹配,第二个参数是摄像头地址 */
static struct i2c_board_info cmos_ov7740_info = {
/* 由芯片手册可知,写 -- 0x42(01000010),读 -- 0x43(01000011)
* 将读地址或者写地址,向右移动一位即可。
*/
I2C_BOARD_INFO("cmos_ov7740", 0x21),
};
static struct i2c_client *cmos_ov7740_client;
static int cmos_ov7740_dev_init(void)
{
struct i2c_adapter *i2c_adap;
/* 获得适配器,由2440原理图可知,2440只有一个iic控制器,所以iic控制器为适配器0,
* 也就是0号iic总线上
*/
i2c_adap = i2c_get_adapter(0);
cmos_ov7740_client = i2c_new_device(i2c_adap, &cmos_ov7740_info);
/* 适配器使用完后要释放他 */
i2c_put_adapter(i2c_adap);
return 0;
}
static void cmos_ov7740_dev_exit(void)
{
i2c_unregister_device(cmos_ov7740_client);
}
module_init(cmos_ov7740_dev_init);
module_exit(cmos_ov7740_dev_exit);
MODULE_LICENSE("GPL");
驱动文件
/* 该驱动函数是基于linux3.4.2
* 该驱动程序是参考自己写的uvc驱动
*
*/
#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 OV7740_INIT_REGS_SIZE (sizeof(ov7740_setting_30fps_VGA_640_480)/sizeof(ov7740_setting_30fps_VGA_640_480[0]))
#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)
struct cmos_ov7740_scaler {
unsigned int PreHorRatio;//水平变比
unsigned int PreVerRatio;//垂直变比
unsigned int H_Shift;//水平比
unsigned int V_Shift;//垂直比
unsigned int PreDstWidth;//目标宽度
unsigned int PreDstHeight;//目标高度
unsigned int MainHorRatio;//预览主缩放的水平比
unsigned int MainVerRatio;//预览主缩放的垂直比
unsigned int SHfactor;//缩放变比
unsigned int ScaleUpDown;//放大缩小标志
};
static struct cmos_ov7740_scaler sc;
typedef struct cmos_ov7740_i2c_value {
unsigned char regaddr;
unsigned char value;
}ov7740_t;
/* init: 640x480,30fps的,YUV422输出格式 */
ov7740_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}
};
struct cmos_ov7740_fmt {
char *name;
u32 fourcc; /* v4l2 format id */
int depth;
};
/* 这两个格式仅仅是预览通道支持的格式,由控制器的芯片手册可知 */
static struct cmos_ov7740_fmt formats[] = {
{
.name = "RGB565",
.fourcc = V4L2_PIX_FMT_RGB565,
.depth = 16,
},
{
.name = "PACKED_RGB_888",
.fourcc = V4L2_PIX_FMT_RGB24,
.depth = 24,
},
};
struct camif_buffer
{
unsigned int order;
unsigned long virt_base;
unsigned long phy_base;
};
struct camif_buffer 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
}
};
static struct i2c_client *cmos_ov7740_client;
// CAMIF GPIO
static unsigned long *GPJCON;
static unsigned long *GPJDAT;
static unsigned long *GPJUP;
// CAMIF
static unsigned long *CISRCFMT;
static unsigned long *CIWDOFST;
static unsigned long *CIGCTRL;
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;
// IRQ
static unsigned long *SRCPND;
static unsigned long *INTPND;
static unsigned long *SUBSRCPND;
static unsigned int SRC_Width, SRC_Height;
static unsigned int TargetHsize_Pr, TargetVsize_Pr;
static unsigned long buf_size;
static unsigned int bytesperline;
static DECLARE_WAIT_QUEUE_HEAD(cam_wait_queue);
/* 中断标志 */
static volatile int ev_cam = 0;
static irqreturn_t cmos_ov7740_camif_irq_c(int irq, void *dev_id)
{
return IRQ_HANDLED;
}
static irqreturn_t cmos_ov7740_camif_irq_p(int irq, void *dev_id)
{
/* 清中断,因为不是使用上升沿或者下降沿触发,所以我们要清楚中断标志位
* 确保下次能继续产生中断。由中断控制器的手册394页可知,向相应的寄存器写入1
* 就是清除相应的中断标识,写入0表示保持原来的标志不变。
*/
*SRCPND = 1<<6;
*INTPND = 1<<6;
*SUBSRCPND = 1<<12;
ev_cam = 1;
wake_up_interruptible(&cam_wait_queue);
return IRQ_HANDLED;
}
/* A2 参考 uvc_v4l2_do_ioctl */
static int cmos_ov7740_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
memset(cap, 0, sizeof *cap);
strcpy(cap->driver, "cmos_ov7740");
strcpy(cap->card, "cmos_ov7740");
cap->version = 2;
/* V4L2_CAP_READWRITE表示获取摄像头数据使用过读写操作,不是通过流操作
* 所谓流操作就是通过qbuf,dqbuf,mmap,poll等函数来获得数据的
*/
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE;
return 0;
}
/* A3 列举支持哪种格式
* 参考: uvc_fmts 数组
*/
static int cmos_ov7740_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
struct cmos_ov7740_fmt *fmt;
if (f->index >= ARRAY_SIZE(formats))
return -EINVAL;
fmt = &formats[f->index];
strlcpy(f->description, fmt->name, sizeof(f->description));
f->pixelformat = fmt->fourcc;
return 0;
}
/* A4 返回当前所使用的格式 */
static int cmos_ov7740_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
return 0;
}
/* A5 测试驱动程序是否支持某种格式, 强制设置该格式
* 参考: uvc_v4l2_try_format
* myvivi_vidioc_try_fmt_vid_cap
*/
static int cmos_ov7740_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
/* 判断是否为摄像头设备 */
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
{
return -EINVAL;
}
/* 在coms中的CICOSCCTRL寄存器中可知,在预览通道只支持RGB16/RGB24 */
if ((f->fmt.pix.pixelformat != V4L2_PIX_FMT_RGB565) && (f->fmt.pix.pixelformat != V4L2_PIX_FMT_RGB24))
return -EINVAL;
return 0;
}
/* A6 参考 myvivi_vidioc_s_fmt_vid_cap
* 设置摄像头数据格式
*/
static int cmos_ov7740_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
int ret = cmos_ov7740_vidioc_try_fmt_vid_cap(file, NULL, f);
if (ret < 0)
return ret;
/* 目标图像的水平像素大小 */
TargetHsize_Pr = f->fmt.pix.width;
/* 目标图像的垂直像素大小 */
TargetVsize_Pr = f->fmt.pix.height;
if(f->fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565)
{
/* bit[30]: 设置图像输出格式是RGB16、RGB24 */
*CIPRSCCTRL &= ~(1<<30);
f->fmt.pix.bytesperline = (f->fmt.pix.width * 16) >> 3;
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
buf_size = f->fmt.pix.sizeimage;
bytesperline = f->fmt.pix.bytesperline;
}
else if(f->fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24)
{
/* bit[30]: 设置图像输出格式是RGB16、RGB24 */
*CIPRSCCTRL |= (1<<30);
/* 对于RGB24格式数据来说,每个像素用32位来表示,在s3c2440用户手册的515页可知 */
f->fmt.pix.bytesperline = (f->fmt.pix.width * 32) >> 3;
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
buf_size = f->fmt.pix.sizeimage;
bytesperline = f->fmt.pix.bytesperline;
}
/*
CIPRTRGFMT:
bit[28:16] -- 表示目标图片的水平像素大小(TargetHsize_Pr)
bit[15:14] -- 是否旋转,我们这个驱动就不选择了
bit[12:0] -- 表示目标图片的垂直像素大小(TargetVsize_Pr)
*/
*CIPRTRGFMT = (TargetHsize_Pr<<16)|(0x0<<14)|(TargetVsize_Pr<<0);
return 0;
}
/* 分配四个缓存,因为有四个CIPRCLRSA1寄存器用于存放视频的缓存寄存器 */
static int cmos_ov7740_vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
unsigned int order;
/* 参考其他驱动如何使用__get_free_pages函数,就会涉及到get_order
* 表示大小
*/
order = get_order(buf_size);
img_buff[0].order = order;
/* 使用__get_free_pages函数最大可以分配4M的连续物理内存,不适用kmalloc函数是因为
* 其最大只能分配128K的连续物理内存。
*/
img_buff[0].virt_base = __get_free_pages(GFP_KERNEL|__GFP_DMA, img_buff[0].order);
if(img_buff[0].virt_base == (unsigned long)NULL)
{
printk("error0\n");
goto error0;
}
/* 将虚拟地址转换为物理地址。因为CIPRCLRSA1寄存器就是要赋值为物理地址 */
img_buff[0].phy_base = __virt_to_phys(img_buff[0].virt_base);
img_buff[1].order = order;
img_buff[1].virt_base = __get_free_pages(GFP_KERNEL|__GFP_DMA, img_buff[1].order);
if(img_buff[1].virt_base == (unsigned long)NULL)
{
printk("error1\n");
goto error1;
}
img_buff[1].phy_base = __virt_to_phys(img_buff[1].virt_base);
img_buff[2].order = order;
img_buff[2].virt_base = __get_free_pages(GFP_KERNEL|__GFP_DMA, img_buff[2].order);
if(img_buff[2].virt_base == (unsigned long)NULL)
{
printk("error2\n");
goto error2;
}
img_buff[2].phy_base = __virt_to_phys(img_buff[2].virt_base);
img_buff[3].order = order;
img_buff[3].virt_base = __get_free_pages(GFP_KERNEL|__GFP_DMA, img_buff[3].order);
if(img_buff[3].virt_base == (unsigned long)NULL)
{
printk("error3\n");
goto error3;
}
img_buff[3].phy_base = __virt_to_phys(img_buff[3].virt_base);
/* CIPRCLRSA1寄存器需要赋值为物理地址
* 这个地址其实就是给dma用的,当摄像头采集完数据之后将数据放到CIPRCLRSA1
* 寄存器指定的地址,然后在开始采集下一帧数据时触发一个中断。这个过程有点类似dma
* 的操作,只不过dma拷贝完指定长度的数据到指定的地址后会触发一个中断。
*/
*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;
error3:
free_pages(img_buff[2].virt_base, order);
img_buff[2].phy_base = (unsigned long)NULL;
error2:
free_pages(img_buff[1].virt_base, order);
img_buff[1].phy_base = (unsigned long)NULL;
error1:
free_pages(img_buff[0].virt_base, order);
img_buff[0].phy_base = (unsigned long)NULL;
error0:
return -ENOMEM;
}
/* 突发长度的计算在2440用户手册的532页 */
static void CalculateBurstSize(unsigned int hSize, unsigned int *mainBusrtSize, unsigned int *remainedBustSize)
{
unsigned int tmp;
/* 由芯片手册可知,所有的突发长度只能为2,4,8,16,所以我们就从最快的16开始测试
* 由芯片手册可知,每一行的数据必须字为单位。
*/
tmp = (hSize/4)%16;
switch(tmp)
{
case 0:
*mainBusrtSize = 16;
*remainedBustSize = 16;
break;
case 4:
*mainBusrtSize = 16;
*remainedBustSize = 4;
break;
case 8:
*mainBusrtSize = 16;
*remainedBustSize = 8;
break;
/* 主突发长度不能为16,所以开始尝试主突发长度为8 */
default:
tmp = (hSize/4)%8;
switch(tmp)
{
case 0:
*mainBusrtSize = 8;
*remainedBustSize = 8;
break;
case 4:
*mainBusrtSize = 8;
*remainedBustSize = 4;
break;
/* 主突发长度不能为8,开始尝试主突发长度为4 */
default:
*mainBusrtSize = 4;
tmp = (hSize/4)%4;
*remainedBustSize = (tmp)?tmp:4;
break;
}
break;
}
}
/* 照抄jz2440用户手册的527页代码 */
static void camif_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift)
{
if(src >= 64*tar) {return;}
else if(src >= 32*tar) {*ratio = 32; *shift = 5;}
else if(src >= 16*tar) {*ratio = 16; *shift = 4;}
else if(src >= 8*tar) {*ratio = 8; *shift = 3;}
else if(src >= 4*tar) {*ratio = 4; *shift = 2;}
else if(src >= 2*tar) {*ratio = 2; *shift = 1;}
else {*ratio = 1; *shift = 0;}
}
/* 计算公式在2440用户手册的527页 */
static void cmos_ov7740_calculate_scaler_info(void)
{
unsigned int sx, sy, tx, ty;
/* 这里的源宽度和源高度是经过窗口剪切的图像数据
* 这里的目标宽度和高度是应用程序在cmos_ov7740_vidioc_s_fmt_vid_cap函数
* 设置数据格式时传入的,表示应用程序想要获得的图像分辨率
*/
sx = SRC_Width;
sy = SRC_Height;
tx = TargetHsize_Pr;
ty = TargetVsize_Pr;
printk("%s: SRC_in(%d, %d), Target_out(%d, %d)\n", __func__, sx, sy, tx, ty);
camif_get_scaler_factor(sx, tx, &sc.PreHorRatio, &sc.H_Shift);
camif_get_scaler_factor(sy, ty, &sc.PreVerRatio, &sc.V_Shift);
sc.PreDstWidth = sx / sc.PreHorRatio;
sc.PreDstHeight = sy / sc.PreVerRatio;
sc.MainHorRatio = (sx << 8) / (tx << sc.H_Shift);
sc.MainVerRatio = (sy << 8) / (ty << sc.V_Shift);
sc.SHfactor = 10 - (sc.H_Shift + sc.V_Shift);
sc.ScaleUpDown = (tx>=sx)?1:0;
}
/* A11 启动传输
* 参考: uvc_video_enable(video, 1):
* uvc_commit_video
* uvc_init_video
*/
static int cmos_ov7740_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
unsigned int Main_burst, Remained_burst;
/*
CISRCFMT:
bit[31] -- 选择传输方式为BT601或者BT656
bit[30] -- 设置偏移值(0 = +0 (正常情况下) - for YCbCr)
bit[29] -- 保留位,必须设置为0
bit[28:16] -- 设置源图片的水平像素值(640)
bit[15:14] -- 设置源图片的颜色顺序(0x0c --> 0x2),由摄像头地址为0xc的寄存器
可知,输出的顺序为UYVYUYVY------>CbYCrYCbYCrY,
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);
/* 裁剪后的图像宽度 = 原始图像宽度 - 2*水平窗口宽度 */
SRC_Width = CAM_SRC_HSIZE - 2*WinHorOfst;
/* 裁剪后的图像高度 = 原始图像高度 - 2*水平窗口高度 */
SRC_Height = CAM_SRC_VSIZE - 2*WinVerOfst;
/*
CIGCTRL:
bit[31] -- 软件复位CAMIF控制器
bit[30] -- 用于复位外部摄像头模块
bit[29] -- 保留位,必须设置为1
bit[28:27] -- 用于选择信号源(00 = 输入源来自摄像头模块)
bit[26] -- 设置像素时钟的极性(猜0)
bit[25] -- 设置VSYNC的极性(0)
bit[24] -- 设置HREF的极性(0),行同步信号
*/
*CIGCTRL |= (1<<29)|(0<<27)|(0<<26)|(0<<25)|(0<<24);
/*
CIPRCTRL:
bit[23:19] -- 主突发长度(Main_burst)
bit[18:14] -- 剩余突发长度(Remained_burst)
bit[2] -- 是否使能LastIRQ功能(不使能)
*/
CalculateBurstSize(bytesperline, &Main_burst, &Remained_burst);
*CIPRCTRL = (Main_burst<<19)|(Remained_burst<<14)|(0<<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]: 预览缩放开始
//bit[30]在cmos_ov7740_vidioc_s_fmt_vid_cap函数中设置
*/
cmos_ov7740_calculate_scaler_info();
*CIPRSCPRERATIO = (sc.SHfactor<<28)|(sc.PreHorRatio<<16)|(sc.PreVerRatio<<0);
*CIPRSCPREDST = (sc.PreDstWidth<<16)|(sc.PreDstHeight<<0);
*CIPRSCCTRL |= (1<<31)|(sc.ScaleUpDown<<28)|(sc.MainHorRatio<<16)|(sc.MainVerRatio<<0);
/*
CIPRTAREA:
表示预览通道的目标区域
*/
*CIPRTAREA = TargetHsize_Pr * TargetVsize_Pr;
/*
CIIMGCPT:
bit[31]: 用来使能摄像头控制器
bit[30]: 使能编码通道
bit[29]: 使能预览通道
*/
*CIIMGCPT = (1<<31)|(1<<29);
*CIPRSCCTRL |= (1<<15);
return 0;
}
/* A17 停止
* 参考 : uvc_video_enable(video, 0)
*/
static int cmos_ov7740_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type t)
{
*CIPRSCCTRL &= ~(1<<15);
*CIIMGCPT &= ~((1<<31)|(1<<29));
return 0;
}
static const struct v4l2_ioctl_ops cmos_ov7740_ioctl_ops = {
// 表示它是一个摄像头设备
.vidioc_querycap = cmos_ov7740_vidioc_querycap,
/* 用于列举、获得、测试、设置摄像头的数据的格式 */
.vidioc_enum_fmt_vid_cap = cmos_ov7740_vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = cmos_ov7740_vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = cmos_ov7740_vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = cmos_ov7740_vidioc_s_fmt_vid_cap,
/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
.vidioc_reqbufs = cmos_ov7740_vidioc_reqbufs,
/* 说明: 因为我们是通过读的方式来获得摄像头数据,因此查询/放入队列/取出队列这些操作函数将不在需要 */
#if 0
.vidioc_querybuf = myuvc_vidioc_querybuf,
.vidioc_qbuf = myuvc_vidioc_qbuf,
.vidioc_dqbuf = myuvc_vidioc_dqbuf,
#endif
// 启动/停止
.vidioc_streamon = cmos_ov7740_vidioc_streamon,
.vidioc_streamoff = cmos_ov7740_vidioc_streamoff,
};
/* A1 */
static int cmos_ov7740_open(struct file *file)
{
return 0;
}
/* A18 关闭 */
static int cmos_ov7740_close(struct file *file)
{
return 0;
}
/* 应用程序通过读的方式读取摄像头的数据 */
static ssize_t cmos_ov7740_read(struct file *filep, char __user *buf, size_t count, loff_t *pos)
{
size_t end;
int i;
/* 取一帧数据的大小和用户想要读取数据的大小的最小值 */
end = min_t(size_t, buf_size, count);
wait_event_interruptible(cam_wait_queue, ev_cam);
for(i=0; i<4; i++)
{
if(copy_to_user(buf, (void *)img_buff[i].virt_base, end))
return -EFAULT;
}
ev_cam = 0;
return end;
}
/* 应用程序使用read函数来获取摄像头数据,所以不再需要mmap,poll函数。 */
static const struct v4l2_file_operations cmos_ov7740_fops = {
.owner = THIS_MODULE,
.open = cmos_ov7740_open,
.release = cmos_ov7740_close,
/* video_ioctl2是内核提供的函数,只是起到中转的作用,最后会调用video_device中的ioctl_ops */
.unlocked_ioctl = video_ioctl2,
.read = cmos_ov7740_read,
};
/*
注意:
该函数是必须的,否则在insmod的时候,会出错
*/
static void cmos_ov7740_release(struct video_device *vdev)
{
unsigned int order;
order = get_order(buf_size);
free_pages(img_buff[0].virt_base, order);
img_buff[0].phy_base = (unsigned long)NULL;
free_pages(img_buff[1].virt_base, order);
img_buff[1].phy_base = (unsigned long)NULL;
free_pages(img_buff[2].virt_base, order);
img_buff[2].phy_base = (unsigned long)NULL;
free_pages(img_buff[3].virt_base, order);
img_buff[3].phy_base = (unsigned long)NULL;
}
/* 2.1. 分配、设置一个video_device结构体
* 这里采用的是静态分配结构体,使用video_device_alloc()是动态分配摄像头结构体
*/
static struct video_device cmos_ov7740_vdev = {
.fops = &cmos_ov7740_fops,
.ioctl_ops = &cmos_ov7740_ioctl_ops,
.release = cmos_ov7740_release,
.name = "cmos_ov7740",
};
static void cmos_ov7740_gpio_cfg(void)
{
/* 设置相应的GPIO用于CAMIF */
*GPJCON = 0x2aaaaaa;
/* 将数据位清0 */
*GPJDAT = 0;
/* 为了让信号稳定,使能上拉电阻 */
*GPJUP = 0;
}
static void cmos_ov7740_camif_reset(void)
{
/* 传输方式为BT601 */
*CISRCFMT |= (1<<31);
/* 复位CAMIF控制器 */
*CIGCTRL |= (1<<31);
/* 稍微延迟一下 */
mdelay(10);
/* 使摄像头恢复正常状态 */
*CIGCTRL &= ~(1<<31);
mdelay(10);
}
static void cmos_ov7740_clk_cfg(void)
{
struct clk *camif_clk;
struct clk *camif_upll_clk;
/* 使能CAMIF的时钟源(摄像头控制器)
* clk_get是三星公司为自己的soc写好的代码,我们可以直接使用,但是我们也可以自己设置
* 相应的寄存器来设置时钟。在source insight中搜索”camif”,找到s3c2440相关的文件,
* 查看其实能函数是如何配置其寄存器的
*/
camif_clk = clk_get(NULL, "camif");
if(!camif_clk || IS_ERR(camif_clk))
{
printk(KERN_INFO "failed to get CAMIF clock source\n");
}
clk_enable(camif_clk);
/* 使能并设置CAMCLK = 24MHz,设置外部摄像头的时钟
* camif-upll是来自usb总线的时钟,
* 由芯片手册可知外部摄像头时钟也是是通过soc内部的时钟树来配置,由芯片手册可知外部摄像头
* 时钟来自usb总线的时钟,这个时钟也是配置soc中的时钟树来设置频率的。也是三星公司写好的。
*/
camif_upll_clk = clk_get(NULL, "camif-upll");
clk_set_rate(camif_upll_clk, 24000000);
mdelay(100);
}
/*
注意:
1.S3C2440提供的复位时序(CAMRST)为:0->1->0(0:表示正常工作的电平、1:表示复位电平)
但是,实验证明,该复位时序与我们的OV7740需要的复位时序(1->0->1)不符合。
2.因此,我们就应该结合OV7740的具体复位时序,来设置相应的寄存器。
*/
static void cmos_ov7740_reset(void)
{
*CIGCTRL |= (1<<30);
mdelay(30);
*CIGCTRL &= ~(1<<30);
mdelay(30);
*CIGCTRL |= (1<<30);
mdelay(30);
}
static void cmos_ov7740_init(void)
{
unsigned int mid;
int i;
/* 读
* 0xa是摄像头模块的厂家id的高8位,0xb是厂家id的低8位
* i2c_smbus_read_byte_data函数是内核提供的函数,也可是使用i2c_transfer函数
*/
mid = i2c_smbus_read_byte_data(cmos_ov7740_client, 0x0a)<<8;
mid |= i2c_smbus_read_byte_data(cmos_ov7740_client, 0x0b);
printk("manufacture ID = 0x%4x\n", mid);
/* 写
* 初始化摄像头模块
*/
for(i = 0; i < OV7740_INIT_REGS_SIZE; i++)
{
i2c_smbus_write_byte_data(cmos_ov7740_client, ov7740_setting_30fps_VGA_640_480[i].regaddr, ov7740_setting_30fps_VGA_640_480[i].value);
mdelay(2);
}
}
static int __devinit cmos_ov7740_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 2.3 硬件相关 */
/* 2.3.1 映射相应的寄存器 */
GPJCON = ioremap(0x560000d0, 4);
GPJDAT = ioremap(0x560000d4, 4);
/* 上拉电阻相关的寄存器 */
GPJUP = ioremap(0x560000d8, 4);
/* 以下是摄像头控制器的寄存器 */
CISRCFMT = ioremap(0x4F000000, 4);
CIWDOFST = ioremap(0x4F000004, 4);
CIGCTRL = ioremap(0x4F000008, 4);
CIPRCLRSA1 = ioremap(0x4F00006C, 4);
CIPRCLRSA2 = ioremap(0x4F000070, 4);
CIPRCLRSA3 = ioremap(0x4F000074, 4);
CIPRCLRSA4 = ioremap(0x4F000078, 4);
CIPRTRGFMT = ioremap(0x4F00007C, 4);
CIPRCTRL = ioremap(0x4F000080, 4);
CIPRSCPRERATIO = ioremap(0x4F000084, 4);
CIPRSCPREDST = ioremap(0x4F000088, 4);
CIPRSCCTRL = ioremap(0x4F00008C, 4);
CIPRTAREA = ioremap(0x4F000090, 4);
CIIMGCPT = ioremap(0x4F0000A0, 4);
SRCPND = ioremap(0X4A000000, 4);
INTPND = ioremap(0X4A000010, 4);
SUBSRCPND = ioremap(0X4A000018, 4);
/* 2.3.2 设置相应的GPIO用于CAMIF */
cmos_ov7740_gpio_cfg();
/* 2.3.3 复位一下CAMIF控制器
* 接下来要操作摄像头控制器,因此要先复位一下控制器
*/
cmos_ov7740_camif_reset();
/* 2.3.4 设置、使能时钟(使能HCLK用于摄像头控制器、使能并设置CAMCLK = 24MHz用于外部摄像头) */
cmos_ov7740_clk_cfg();
/* 2.3.5 复位一下摄像头模块 */
cmos_ov7740_reset();
/* 2.3.6 通过IIC总线,初始化摄像头模块 */
cmos_ov7740_client = client;
cmos_ov7740_init();
/* 2.3.7 注册中断
* IRQF_DISABLED - keep irqs disabled when calling the action handler
*/
if (request_irq(IRQ_S3C2440_CAM_C, cmos_ov7740_camif_irq_c, IRQF_DISABLED , "CAM_C", NULL))
printk("%s:request_irq failed\n", __func__);
if (request_irq(IRQ_S3C2440_CAM_P, cmos_ov7740_camif_irq_p, IRQF_DISABLED , "CAM_P", NULL))
printk("%s:request_irq failed\n", __func__);
/* 3. 注册 ,以下信息是通过追踪源代码得到的结论
* 参数-1表示分配第一个可用的号
* which device node number (0 == /dev/video0, 1 == /dev/video1, ...
* -1 == first free)
*/
if(video_register_device(&cmos_ov7740_vdev, VFL_TYPE_GRABBER, -1))
{
printk("unable to register video device\n");
}
return 0;
}
static int __devexit cmos_ov7740_remove(struct i2c_client *client)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
iounmap(GPJCON);
iounmap(GPJDAT);
iounmap(GPJUP);
iounmap(CISRCFMT);
iounmap(CIWDOFST);
iounmap(CIGCTRL);
iounmap(CIPRCLRSA1);
iounmap(CIPRCLRSA2);
iounmap(CIPRCLRSA3);
iounmap(CIPRCLRSA4);
iounmap(CIPRTRGFMT);
iounmap(CIPRCTRL);
iounmap(CIPRSCPRERATIO);
iounmap(CIPRSCPREDST);
iounmap(CIPRSCCTRL);
iounmap(CIPRTAREA);
iounmap(CIIMGCPT);
iounmap(SRCPND);
iounmap(INTPND);
iounmap(SUBSRCPND);
free_irq(IRQ_S3C2440_CAM_C, NULL);
free_irq(IRQ_S3C2440_CAM_P, NULL);
video_unregister_device(&cmos_ov7740_vdev);
return 0;
}
static const struct i2c_device_id cmos_ov7740_id_table[] = {
{ "cmos_ov7740", 0 },
{}
};
/* 1.1. 分配、设置一个i2c_driver */
static struct i2c_driver cmos_ov7740_driver = {
.driver = {
/* 用于和iic设备进行匹配,必须和iic_dev中的名字一样,否则匹配不成功 */
.name = "cmos_ov7740",
.owner = THIS_MODULE,
},
.probe = cmos_ov7740_probe,
.remove = __devexit_p(cmos_ov7740_remove),
.id_table = cmos_ov7740_id_table,
};
static int cmos_ov7740_drv_init(void)
{
/* 1.2.注册 */
i2c_add_driver(&cmos_ov7740_driver);
return 0;
}
static void cmos_ov7740_drv_exit(void)
{
i2c_del_driver(&cmos_ov7740_driver);
}
module_init(cmos_ov7740_drv_init);
module_exit(cmos_ov7740_drv_exit);
MODULE_LICENSE("GPL");