Linux I2C总线设备驱动模型分析(ov7740)
1. 框架
1.1 硬件协议简介
1.2 驱动框架
1.3 bus-drv-dev模型及写程序
a. 设备的4种构建方法
a.1 定义一个i2c_board_info, 里面有:名字, 设备地址
然后i2c_register_board_info(busnum, ...) (把它们放入__i2c_board_list链表)
list_add_tail(&devinfo->list, &__i2c_board_list);
链表何时使用:
i2c_register_adapter > i2c_scan_static_board_info > i2c_new_device
使用限制:必须在 i2c_register_adapter 之前 i2c_register_board_info
所以:不适合我们动态加载insmod
a.2 直接i2c_new_device, i2c_new_probed_device
a.2.1 i2c_new_device : 认为设备肯定存在
a.2.2 i2c_new_probed_device :对于"已经识别出来的设备"(probed_device),才会创建("new")
i2c_new_probed_device
probe(adap, addr_list[i]) /* 确定设备是否真实存在 */
info->addr = addr_list[i];
i2c_new_device(adap, info);
a.3 从用户空间创建设备
创建设备
echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device
导致i2c_new_device被调用
删除设备
echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device
导致i2c_unregister_device
a.4 前面的3种方法都要事先确定适配器(I2C总线,I2C控制器)
如果我事先并不知道这个I2C设备在哪个适配器上,怎么办?去class表示的所有的适配器上查找
有上一些I2C设备的地址是一样,怎么继续分配它是哪一款?用detect函数
static struct i2c_driver at24cxx_driver = {
.class = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备 */
.driver = {
.name = "100ask",
.owner = THIS_MODULE,
},
.probe = at24cxx_probe,
.remove = __devexit_p(at24cxx_remove),
.id_table = at24cxx_id_table,
.detect = at24cxx_detect, /* 用这个函数来检测设备确实存在 */
.address_list = addr_list, /* 这些设备的地址 */
};
去"class表示的这一类"I2C适配器,用"detect函数"来确定能否找到"address_list里的设备",
如果能找到就调用i2c_new_device来注册i2c_client, 这会和i2c_driver的id_table比较,
如果匹配,调用probe
i2c_add_driver
i2c_register_driver
a. at24cxx_driver放入i2c_bus_type的drv链表
并且从dev链表里取出能匹配的i2c_client并调用probe
driver_register
b. 对于每一个适配器,调用__process_new_driver
对于每一个适配器,调用它的函数确定address_list里的设备是否存在
如果存在,再调用detect进一步确定、设置,然后i2c_new_device
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
__process_new_driver
i2c_do_add_adapter
/* Detect supported devices on that bus, and instantiate them */
i2c_detect(adap, driver);
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
err = i2c_detect_address(temp_client, driver);
/* 判断这个设备是否存在:简单的发出S信号确定有ACK */
if (!i2c_default_probe(adapter, addr))
return 0;
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
// 设置info.type
err = driver->detect(temp_client, &info);
i2c_new_device.
下面示例一份cmos摄像头ov7740的i2c驱动:
//设备注册部分:cmos_ov7740_dev.c
#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 = {
I2C_BOARD_INFO("cmos_ov7740", 0x21), //0x21:i2c设备地址
};
static struct i2c_client *cmos_ov7740_client;
static int cmos_ov7740_dev_init(void)
{
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(0); //获得当前单板的适配器号
cmos_ov7740_client = i2c_new_device(i2c_adap, &cmos_ov7740_info); //生成一个i2c设备
i2c_put_adapter(i2c_adap); //挂到到适配器0之下
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");
//驱动注册部分:cmos_ov7740_drv.c
#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)
{
/* 清中断 */
*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;
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;
}
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)
{
*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)
{
*CIPRSCCTRL |= (1<<30);
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;
}
static int cmos_ov7740_vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
unsigned int order;
order = get_order(buf_size);
img_buff[0].order = order;
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;
}
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 = 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;
}
static void CalculateBurstSize(unsigned int hSize, unsigned int *mainBusrtSize, unsigned int *remainedBustSize)
{
unsigned int tmp;
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;
default:
tmp = (hSize/4)%8;
switch(tmp)
{
case 0:
*mainBusrtSize = 8;
*remainedBustSize = 8;
break;
case 4:
*mainBusrtSize = 8;
*remainedBustSize = 4;
break;
default:
*mainBusrtSize = 4;
tmp = (hSize/4)%4;
*remainedBustSize = (tmp)?tmp:4;
break;
}
break;
}
}
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;}
}
static void cmos_ov7740_calculate_scaler_info(void)
{
unsigned int sx, sy, tx, ty;
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)
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);
SRC_Width = CAM_SRC_HSIZE - 2*WinHorOfst;
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]: 预览缩放开始
*/
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;
}
static const struct v4l2_file_operations cmos_ov7740_fops = {
.owner = THIS_MODULE,
.open = cmos_ov7740_open,
.release = cmos_ov7740_close,
.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结构体 */
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;
*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的时钟源 */
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_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;
/* 读 */
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 注册中断 */
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__);
/* 2.2.注册 */
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 = {
.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");
再附一段测试程序,对i2c设备的寄存器进行读写测试:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* i2c_test r addr
* i2c_test w addr val
*/
void print_usage(char *file)
{
printf("%s r addr\n", file);
printf("%s w addr val\n", file);
}
int main(int argc, char **argv)
{
int fd;
unsigned char buf[2];
if ((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/at24cxx", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/at24cxx\n");
return -1;
}
if (strcmp(argv[1], "r") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);
read(fd, buf, 1);
printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
}
else if (strcmp(argv[1], "w") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);
buf[1] = strtoul(argv[3], NULL, 0);
write(fd, buf, 2);
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}