基於2.6.35內核的OV9650攝像頭驅動分析

基於2.6.35內核的OV9650攝像頭驅動分析
驅動分析:
打開ov9650驅動首先找到驅動入口函數
static int __init s5pc100_camera_init(void)
在這個函數中間做只有一句話
platform_driver_register(&s5pc100_camera_driver);  這個就是平臺驅動註冊,所以在驅動註冊之前我們需要構建s5pc100_camera_driver這個結構體,並且在內核中間我們需要添加平臺資源信息,在這裏平臺資源的信息中間的name這個成員必須跟s5pc100_camera_driver這個結構體中間的成員name一致,這個在平臺驅動註冊的時候內核遍歷內核的時候才能找到我們的加進去的平臺資源配對成功,在platform_driver_register函數註冊成功的時候,內核就會調用5pc100_camera_driver結構體中間的probe成員執行,我們先來看看5pc100_camera_driver這個結構體
struct platform_driver s5pc100_camera_driver = {
        .probe =s5pc100_camera_probe,
        .remove =__devexit_p(s5pc100_camera_remove),
        .driver = {
               .name = "s5pc100-camif",
        },
};
當驅動加載platform_driver_register註冊成功的時候內核就會調用probe成員,驅動卸載的時候就會調用remove成員,我們先來看看驅動註冊的時候做了什麼事情,來看看這個probe函數s5pc100_camera_probe
camera_gpio_cfg();    這個是camera接口的io樓設置看看這個函數的內容
static void camera_gpio_cfg(void)
{
               s3c_gpio_cfgpin(S5PC100_GPE0(0), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(1), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(2), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(3), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(4), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(5), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(6), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE0(7), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(0), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(1), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(2), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(3), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(4), S3C_GPIO_SFN(2));
               s3c_gpio_cfgpin(S5PC100_GPE1(5), S3C_GPIO_SFN(2));
               //s3c_gpio_setpull(cam->base_addr + S5PC100_GPB(2),S3C_GPIO_PULL_UP);
}
我們這裏根據數據手冊就可以發現這裏就是設置io的功能爲camera接口如下圖




camera_dev = kzalloc(sizeof(struct s5pc100_camera_device),GFP_KERNEL); 這裏是爲camera_dev這個結構體申請空間,在來看看這個結構體中間有哪些成員
struct s5pc100_camera_device {
        void __iomem        *reg_base;  //這個是配置寄存器經過映射後基地址,其實就是s5pc100 camera接口控制寄存器的首地址經過映射後的地址,這個地方應該存的是這個東東,後面可以驗證
        int                        irq;//camera所屬中斷的中斷號
        struct clk               *fimc_clk;    //fimc時鐘結構體指針
        struct clk               *cam_clk;    //cam_clk時鐘結構體指針
        struct device        *dev;         //dev設備結構體
        struct resource*addr_res;     //設備資源中的地址資源指針
        struct resource*irq_res;       //設備資源中的中斷資源結構體指針
        struct resource*addr_req;     //設備資源中地址內存資源結構體指針
        structs5pc100_camera_buffer *frame;     //緩衝區結構體指針
        struct list_headinqueue, outqueue;   //雙向鏈表首指針跟尾指針
        int state;
        int nbuffers;
        int stream;
        struct video_device*v4ldev;   // V4L2驅動註冊的時候需要的核心數據結構
        struct mutexopen_mutex, fileop_mutex;     //定義兩個互斥鎖
        spinlock_tqueue_lock;          //定義一個自旋鎖
        wait_queue_head_t wait_open,wait_frame, wait_stream;    定義三個等待隊列頭指針
        int input;          //輸入的圖像格式選擇
        int format;       //現在的視頻格式
        int srcHsize; / //原圖像尺寸
        int srcVsize;
        int wndVsize;       /窗口頭像大小
        int targetHsize;
        int targetVsize;     //目標輸出視頻大小
        int cmdcode;     //命令代碼
};
camera_dev->dev =&cam->dev;  //初始化camera_dev->dev這個成員
camera_dev->addr_res =platform_get_resource(cam, IORESOURCE_MEM, 0);//初始化camera_dev->addr_res這個成員,將平臺資源中的地址資源首地址取過來
camera_dev->irq_res  =platform_get_resource(cam, IORESOURCE_IRQ, 0);//初始化camera_dev->irq_res這個成員,其實就是將平臺資源中的irq資源取過來
iosize =resource_size(camera_dev->addr_res);  其實這個函數是計算這個地址資源的大小,以方便後面的物理地址轉換爲虛擬地址
camera_dev->reg_base =ioremap(camera_dev->addr_res->start, iosize);//初始化camera_dev->reg_base這個成員,跟上面說那個結構體哪裏一樣,就是地址資源經過映射後得到虛擬地址的首地址存儲在這裏
camera_dev->irq =camera_dev->irq_res->start;//初始化camera_dev->irq這個成員,其實就是中斷資源的第一個
camera_dev->fimc_clk =clk_get(camera_dev->dev, "fimc");//初始化camera_dev->fimc_clk這個成員,其實就是通過clk_get函數獲取fimc時鐘並賦值給過去
clk_enable(camera_dev->fimc_clk);//使能fimc時鐘
camera_dev->cam_clk =clk_get(camera_dev->dev, "div_cam");//初始化camera_dev->cam_clk這個成員,通過clk_get獲取時鐘cam_clk時鐘
clk_set_rate(camera_dev->cam_clk,24000000);//設置cam_clk的時鐘爲24Mhz


camera_reset();  //camera復位函數,來看看camera接口初始化做了什麼事情
writel(readl(camera_dev->reg_base +S5PC100_CISRCFMT) | 1<< 31, camera_dev->reg_base + S5PC100_ CISRCFMT);
這裏將CISRCFMTn這個寄存器的31位置1,那麼就是選擇ITU-R BT.601 YCbCr 8-bit mode enable
writel(readl(camera_dev->reg_base +S5PC100_CIGCTRL) | 1<< 31, camera_dev->reg_base + S5PC100_CIGCTRL);
這裏是將CIGCTRLn的第三十一位置1,該位爲camera的軟復位位,這裏寫1,產生復位
mdelay(10);//延時等待復位
writel(readl(camera_dev->reg_base +S5PC100_CIGCTRL) & ~(1<< 31), camera_dev->reg_base +S5PC100_CIGCTRL);
這裏是將CIGCTRLn的第三十一位置0,取消復位;
到這裏這個函數就完了,我們接着往下看
sensor_reset();    //外設復位
writel(readl(camera_dev->reg_base +S5PC100_CIGCTRL) | 1<< 30, camera_dev->reg_base + S5PC100_CIGCTRL);
這裏是將CIGCTRLn的第三十位置1,該位爲外設電源,這裏寫1,外設斷電覆位
mdelay(10);  延時等待復位
writel(readl(camera_dev->reg_base +S5PC100_CIGCTRL) & ~(1<< 30), camera_dev->reg_base +S5PC100_CIGCTRL);重新給外設上電
到這裏這個函數結束
mdelay(10);    延時10ms等待
for(i = 0; i <ARRAY_SIZE(s5pc100_sensor_table); i++)
        {
               s5pc100_sensor_table[i].init();
        }
這裏是循環調用s5pc100_sensor_table[i].init();這個函數表裏面成員的init函數來初始化,用法跟彙編裏面查表類似來看看這個循環裏面做了什麼事情,首先來看看這個函數表裏面的函數
struct s5pc100_sensor s5pc100_sensor_table[] = 
{
        {
               .init = ov9650_init,
               .cleanup = ov9650_exit,
        },
};
這裏一看,變知道i < ARRAY_SIZE(s5pc100_sensor_table);這個應該等於1,因爲裏面只有一個成員,所以,這個循環就執行了一個函數ov9650_init,在來看看這個函數做了一些什麼事情
int ov9650_init(void)
{
        returni2c_add_driver(&ov9650_driver);
}
這個看看其實這裏就是一個i2c設備的驅動註冊程序,等於說,我們在這個ov9650這個cmos攝像頭驅動裏面包含了一個i2c設備的驅動,這個是爲什麼,其實我們這裏去看看攝像頭的硬件連接,其實我們這裏就不難知道,其實ov9650跟我們a8板相連的時候使用到了i2c1接口,通過閱讀ov9650的手冊也知道,其實ov9650在初始化的時候需要設置內部寄存器,然後根據ov9650數據手冊裏面提供設置內部寄存器的時序圖發現,其實這個時序是一個弱化的i2c協議,所以我們這裏直接利用i2c控制器來連接,可以直接用這個接口來對其進行初始化。我們接下來再來看看這個i2c1設備驅動的註冊過程吧,通過這個驅動程序,我們可以瞭解到,對ov9650初始化需要做些什麼事情。
首先i2c_add_driver(&ov9650_driver);利用這個函數來註冊驅動,在利用這個函數註冊驅動註冊之前,我們需要先去構建ov9650_driver這個結構體
static struct i2c_driver ov9650_driver = {
        .driver = {
               .name        = "ov9650",
               .owner        = THIS_MODULE,
        },
        .probe               = ov9650_probe,
        .remove               = __devexit_p(ov9650_remove),
        .id_table       = ov9650_id,
};
這裏需要注意的是,.name這個必須跟我們在i2c1資源哪裏的name的名字一致,否則驅動註冊不成功,當內核配對成功的時候,那麼就會調用這個函數裏面的.probe這個函數,那麼我們來看看這個函數ov9650_probe裏面做了什麼事情
reg = 0x0A;
        ret = ov9650_reg_read(ov9650_client,reg, &PLDH);
首先這裏是調用ov9650_reg_read函數來讀取ov9650這個內部的0A單元中間的內容,那麼這個單元的內容是什麼,我們去看看ov9650手冊便可以知道


這裏一看很顯然這裏是產品編號,並且這個寄存器只讀。MSB代表產品編號高八位
reg = 0x0B;
        ret =ov9650_reg_read(ov9650_client, reg, &PLDL);
這裏跟上面差不多,讀取ov9650內部0X0B單元裏面的內容


這裏一看還是產品編號,只讀,LSB代表是產品編號的第八位
if(((PLDH << 8)| PLDL) ==OV9650_PRODUCT_ID)
               printk("found sensor: product id = 0x%02x%02x!\n", PLDH,PLDL);
        else
               return -ENODEV;
這裏就是判斷我們讀出來的產品ID編號是否與廠商給出的ID號一致
ov9650_init_regs();   //這裏就是應該是這個I2C初始化的重點,初始化ov9650內部寄存器
我們去看看這裏做了那些事情
static void ov9650_init_regs(void)
{
        int i;
        
        for (i=0;i<ARRAY_SIZE(regs); i++)
        {
               if (regs[i].subaddr == 0xff) 
               {
                       mdelay(regs[i].value);
                       continue;
               }
               ov9650_reg_write(ov9650_client, regs[i].subaddr, regs[i].value);
        }
}
跟上面那個初始化是一樣的,這裏也是用一個循環給ov9650內部的寄存器設置,是怎樣設置的捏!我們仔細分析一下這個代碼,得知當regs[i].subaddr==0xff的時候,那麼我們就在這裏做延時,延時的時長爲regs[i].value毫秒,然後結束本次循環,然後接着下次循環,如果regs[i].subaddr!=0xff的時候,我們就將regs[i].subaddr這個值作爲ov9650內部的地址寫入regs[i].value這個值,那麼其實這個函數就是對ov9650內部的寄存器進行一個初始化,我們來看看這個表
static struct ov9650_reg
{
        unsigned charsubaddr;
        unsigned char value;
}regs[] = {
        /* OV9650intialization parameter table for VGA application */
        {0x12, 0x80},       // Camera Soft reset. Self cleared after reset.
        {CHIP_DELAY, 10},
        {0x11,0x80},{0x6a,0x3e},{0x3b,0x09},{0x13,0xe0},{0x01,0x80},{0x02,0x80},{0x00,0x00},{0x10,0x00},
       {0x13,0xe5},{0x39,0x43},{0x38,0x12},{0x37,0x00},{0x35,0x91},{0x0e,0xa0},{0x1e,0x04},{0xA8,0x80},
       {0x12,0x40},{0x04,0x00},{0x0c,0x04},{0x0d,0x80},{0x18,0xc6},{0x17,0x26},{0x32,0xad},{0x03,0x00},
       {0x1a,0x3d},{0x19,0x01},{0x3f,0xa6},{0x14,0x2e},{0x15,0x10},{0x41,0x02},{0x42,0x08},{0x1b,0x00},
       {0x16,0x06},{0x33,0xe2},{0x34,0xbf},{0x96,0x04},{0x3a,0x00},{0x8e,0x00},{0x3c,0x77},{0x8B,0x06},
       {0x94,0x88},{0x95,0x88},{0x40,0xc1},{0x29,0x3f},{0x0f,0x42},{0x3d,0x92},{0x69,0x40},{0x5C,0xb9},
       {0x5D,0x96},{0x5E,0x10},{0x59,0xc0},{0x5A,0xaf},{0x5B,0x55},{0x43,0xf0},{0x44,0x10},{0x45,0x68},
       {0x46,0x96},{0x47,0x60},{0x48,0x80},{0x5F,0xe0},{0x60,0x8c},{0x61,0x20},{0xa5,0xd9},{0xa4,0x74},
       {0x8d,0x02},{0x13,0xe7},{0x4f,0x3a},{0x50,0x3d},{0x51,0x03},{0x52,0x12},{0x53,0x26},{0x54,0x38},
       {0x55,0x40},{0x56,0x40},{0x57,0x40},{0x58,0x0d},{0x8C,0x23},{0x3E,0x02},{0xa9,0xb8},{0xaa,0x92},
       {0xab,0x0a},{0x8f,0xdf},{0x90,0x00},{0x91,0x00},{0x9f,0x00},{0xa0,0x00},{0x3A,0x01},{0x24,0x70},
       {0x25,0x64},{0x26,0xc3},{0x2a,0x00},{0x2b,0x00},{0x6c,0x40},{0x6d,0x30},{0x6e,0x4b},{0x6f,0x60},
        {0x70,0x70},{0x71,0x70},{0x72,0x70},{0x73,0x70},{0x74,0x60},{0x75,0x60},{0x76,0x50},{0x77,0x48},
       {0x78,0x3a},{0x79,0x2e},{0x7a,0x28},{0x7b,0x22},{0x7c,0x04},{0x7d,0x07},{0x7e,0x10},{0x7f,0x28},
       {0x80,0x36},{0x81,0x44},{0x82,0x52},{0x83,0x60},{0x84,0x6c},{0x85,0x78},{0x86,0x8c},{0x87,0x9e},
       {0x88,0xbb},{0x89,0xd2},{0x8a,0xe6},
};
哇!咋一看!這些數據,由於這些數據較多,所以這裏就利用這個循環來進行對這些ov9650內部的寄存器進行一個初始化,初始化哪些寄存器,有什麼樣的功能我們可以通過閱讀ov9650的數據手冊可以知道。通過查閱ov9650的第十章interface這張裏面的寄存器表就知道。這裏先提一下,首先對ov9650進行軟復位,然後延時10ms,然後就開始對ov9650內部的寄存器進行設置
到這裏我們I2C1的驅動註冊完成,那麼也就是對ov9650內部寄存器的初始化完成。我們在回去接着看看程序註冊的時候還做了什麼
camera_dev->v4ldev = video_device_alloc();向系統申請分配一個表示Radio設備的structvideo_device結構體變量爲後面的V4L2驅動的註冊做準備,v4l2這個東西就是給視頻驅動提供了統一的接口,使得應用程序可以使用統一的API 函數操作不同的視頻設備,極大地簡化了視頻系統的開發和維護。
camera_init();       //這個函數其實是對上面講到的那個結構體camera_dev中的幾個成員分別初始化,初始化等待隊列,初始化互斥鎖,初始化自旋鎖,下面貼出代碼
void camera_init(void)
{
       mutex_init(&camera_dev->open_mutex);
       mutex_init(&camera_dev->fileop_mutex);   初始化互斥鎖
       spin_lock_init(&camera_dev->queue_lock);  初始化自旋鎖
       init_waitqueue_head(&camera_dev->wait_frame);
       init_waitqueue_head(&camera_dev->wait_stream);  初始化等待隊列
       init_waitqueue_head(&camera_dev->wait_open);
}


strcpy(camera_dev->v4ldev->name,"cam->base_addr + S5PC100 Soc Camera"); 這個是對camera_dev這個結構體中間的v4ldev這個結構體的成員name初始化,爲後面v4l2註冊做準備
camera_dev->v4ldev->fops =&s5pc100_camera_fops;// 給camera_dev->v4ldev->fops這個成員初始化,其實是對v4l2構建操作方法,我們來看看s5pc100_camera_fops這個操作方法實現了那些功能
static struct v4l2_file_operationss5pc100_camera_fops = {
        .owner        = THIS_MODULE, 
        .open       = s5pc100_camera_open,        
        .release =s5pc100_camera_release,
        .ioctl        = s5pc100_camera_ioctl,
        .mmap       = s5pc100_camera_mmap,
};
分別實現open ,ioctl,release,還有mmap這四個函數在分析的最後分析這幾個操作方法的實現
camera_dev->v4ldev->release =video_device_release;//初始化camera_dev->v4ldev->release這個成員
video_set_drvdata(camera_dev->v4ldev,camera_dev);//這個函數其實是在初始化camera_dev->v4ldev –>dev->p->driver_data這個成員,也就是將camera_dev結構體指針傳到camera_dev->v4ldev –>dev->p->driver_data這地方去
ret =video_register_device(camera_dev->v4ldev, VFL_TYPE_GRABBER,video_nr);//這裏調用video_register_device這個函數來註冊一個v4l2驅動
if((ret = request_irq(camera_dev->irq,cam_isr, IRQF_DISABLED | IRQF_TRIGGER_RISING , "camera", NULL)) <0)
        {
               printk("request irq %d failed\n", camera_dev->irq);
        }
這個地方是爲camera這個接口註冊中斷函數;
其實在這個中斷函數中間什麼事情也沒有做就是一個打印信息,在這裏可以不用關心,貼出源碼如下:
irqreturn_t cam_isr(int irq, void *devid)
{
        printk("incam_isr\n");
        //wake_up_interruptible(&camera_dev->wait_frame);
        return IRQ_HANDLED;
}
init_image_buffer(camera_dev);//初始化視頻緩衝區,在這裏我們去看看這個函數實現怎樣的功能
cam->frame = img_buff;   //初始化cam->frame這個成員
size = MAX_WIDTH * MAX_HEIGHT * formats[3].depth/ 8;//計算緩衝區的大小,在這裏formats[3].其實是用的RGB-32 (B-G-R)的模式,這樣現實一個640*480大小的圖片,緩衝區的大小爲640*480*32/8個字節,這裏除八是因爲32是位數所以要除8轉換爲字節數
order = get_order(size);//get_order 函數可以用來從一個整數參數 size(必須是 2 的冪) 中提取 order
img_buff[0].order = order;
       img_buff[0].virt_base = __get_free_pages(GFP_KERNEL|GFP_DMA, img_buff[0].order);
        img_buff[0].img_size= size;
初始化img_buff[0].中的成員變量,__get_free_pages這個函數是通過之前獲取到的order來分配內存,並將分配到的內存首地址給img_buff[0].virt_base
img_buff[0].phy_base = img_buff[0].virt_base -PAGE_OFFSET + PHYS_OFFSET;
這裏是計算DMA地址,這裏是怎樣計算的捏!!其實這裏就是一個dma的虛擬地址,在驅動中間我們需要用其真是的物理地址,所以我們這個時候的算法就是:虛擬地址-虛擬地址偏移=地址偏移量;地址偏移量+物理地址偏移=我們要求的物理地址了


到這裏init_image_buffer函數已經看完了 
init_camif_config(camera_dev);   初始化camera接口寄存器的一些配置,進去看看這個函數是怎樣設置的
cam->format = 3;        設置數據格式可以從前面size = MAX_WIDTH * MAX_HEIGHT * formats[3].depth這個地方看到


        cam->srcHsize =640;        // 設置源數據的水平像素   CISRCFMTn寄存器的SrcHsize_CAM
        cam->srcVsize =480;        //設置源數據的垂直像素   CISRCFMTn寄存器的SrcVsize_CAM
        cam->wndHsize =640;
        cam->wndVsize =480; //lht


        cam->targetHsize= cam->wndHsize;// 目標圖像的水平像素
        cam->targetVsize= cam->wndVsize; // 目標圖像的垂直像素


update_camera_config(cam,(u32)-1);  //根據cam中的一些信息配置camera的配置寄存器這個函數的內容爲update_camera_regs(cam);   再去看看這個函數
update_source_fmt_regs(cam);
update_target_fmt_regs(cam);
先來看看update_source_fmt_regs(cam);這個函數
fg = (1<<31)                                      // ITU-R BT.601 YCbCr 8-bit mode
                              |(0<<30)                               // CB,Cr value offset cntrolfor YCbCr
                              |(cam->srcHsize<<16)        // target image width
                              |(0<<14)                               // input order is YCbYCr
                              |(cam->srcVsize<<0);        // source image height
        writel(cfg,cam->reg_base + S5PC100_CISRCFMT);


        cfg = (1<<15)
                       |(1<<14)
                       |(1<<30)
                       |(1<<29);
        writel(cfg,cam->reg_base + S5PC100_CIWDOFST);


        cfg = (1<<26)
                       |(1<<29)
                       |(1<<16)
                       |(1<<7)
                       |(0<<0);
        writel(cfg,cam->reg_base + S5PC100_CIGCTRL);
        writel(0,cam->reg_base + S5PC100_CIWDOFST2);
現在我將這個程序直接整理出來看看
CISRCFMTn=(1<<31)|(0<<30)|(640<<16)|(0<<14)|(480<<0)=
CIWDOFSTn=(1<<15)|(1<<14)|(1<<30)|(1<<29)
CIGCTRLn=(1<<29)|(1<<26)|(1<<7)|(0<<0)
這個就是這個函數所實現的功能  可以查閱s5pc100芯片手冊查看
再來看看update_target_fmt_regs(cam);這個函數
writel(img_buff[0].phy_base, cam->reg_base +S5PC100_CIOYSA1);
               cfg = (2 << 29) | (cam->targetHsize << 16) |(cam->targetVsize << 0)|(3<<14);
               writel(cfg, cam->reg_base + S5PC100_CITRGFMT);
               calculate_prescaler_ratio_shift(cam->srcHsize, cam->targetHsize,&prescaler_h_ratio, &h_shift);
               calculate_prescaler_ratio_shift(cam->srcVsize, cam->targetVsize,&prescaler_v_ratio, &v_shift);
               main_h_ratio = (cam->srcHsize << 8) / (cam->targetHsize<< h_shift);
               main_v_ratio = (cam->srcVsize << 8) / (cam->targetVsize<< v_shift);
               cfg = ((10 - (h_shift + v_shift)) << 28) | (prescaler_h_ratio<< 16) | (prescaler_v_ratio << 0);   
               writel(cfg, cam->reg_base + S5PC100_CISCPRERATIO);
               cfg = (cam->targetHsize << 16) | (cam->targetVsize <<0);
               writel(cfg, cam->reg_base + S5PC100_CISCPREDST);
               cfg = (main_h_ratio << 16) | (main_v_ratio << 0);
               writel(cfg, cam->reg_base + S5PC100_CISCCTRL);
               cfg = cam->targetVsize * cam->targetHsize;
               writel(cfg, cam->reg_base + S5PC100_CITAREA);


               cfg = (cam->targetVsize << 16) | (cam->targetHsize <<0);
               writel(cfg, cam->reg_base + S5PC100_ORGOSIZE);


CIOYSA1=_get_free_pages(GFP_KERNEL|GFP_DMA,img_buff[0].order);申請的首地址
CITRGFMTn=(2<<29)|(640<<16)|(3<<14)|(480<<0)
CISCPRERATIOn=(10<<28)|(1<<16)|(1<<0)
CISCPREDSTn=(640<<16)|(480<<0)
CISCCTRLn=(((640<<8)/(640<<1))<<16)|(((480<<8)/(480<<1))<<0)
CITAREAn=640*480
ORGOSIZEn=(640<<16)|(480<<0)
這個函數就做了這些事情,對照數據手冊可以去查看設置那些功能
到這裏我們ov9650的驅動已經分析玩了
總結:在這個ov9650驅動中間我們使用到了I2C1的驅動,然後使用到camera控制器驅動程序,以及v4l2驅動程序,將這三個驅動融合共同實現對ov9650的控制以及現實,以及給上層提供良好的接口




static struct v4l2_file_operationss5pc100_camera_fops = {
        .owner        = THIS_MODULE, 
        .open       = s5pc100_camera_open,        
        .release =s5pc100_camera_release,
        .ioctl        = s5pc100_camera_ioctl,
        .mmap       = s5pc100_camera_mmap,
};


首先來看open函數
static int s5pc100_camera_open(struct file*filp)
{
        printk("cameraopen\n");


        return 0;
}這個函數其實什麼也沒有做就是打印一條信息
再看看s5pc100_camera_release這個函數
static int s5pc100_camera_release(struct file*filp)
{
        printk("camerarelease\n");
        return 0;
}這個函數跟open都是沒有做什麼事情
接下來我們先來看看。Mmap函數這個函數比較短
static int s5pc100_camera_mmap(struct file*filp, struct vm_area_struct *vma)
{
        unsigned long size =vma->vm_end - vma->vm_start,
                                start = vma->vm_start,
                                 off= vma->vm_pgoff;




        if(!(vma->vm_flags & (VM_WRITE | VM_READ))) {
               return -EACCES;
        }


       if(io_remap_pfn_range(vma, start, off, size, vma->vm_page_prot))
               return -EAGAIN;


        return 0;
}通過io_remap_pfn_range這個函數實現內核空間映射到用戶空間
這樣就實現了mmap函數,再來看看loctl的實現
static long s5pc100_camera_ioctl(struct file*filp,
               unsigned int cmd, unsigned long arg)
{
        structs5pc100_camera_device *cam = video_drvdata(filp);
        int err = 0;


        if (mutex_lock_interruptible(&cam->fileop_mutex))
               return -ERESTARTSYS;




        err =s5pc100_ioctl_v4l2(filp, cmd, (void __user *)arg);


       mutex_unlock(&cam->fileop_mutex);


        return err;
}
從這裏看出這裏轉調s5pc100_ioctl_v4l2這個函數來實現ioctl的那我們再去看看這個函數做了什麼事情
static long s5pc100_ioctl_v4l2(struct file*filp,
               unsigned int cmd, void __user *arg)
{
        structs5pc100_camera_device *cam = video_drvdata(filp);


        switch (cmd) {
        caseVIDIOC_QUERYCAP:
               //printk("VIDIOC_QUERYBUF\n");
               return s5pc100_vidioc_querycap(cam, arg); 
        case VIDIOC_REQBUFS:
               //printk("VIDIOC_REQBUFS\n");
               return s5pc100_vidioc_reqbufs(cam, arg);
        caseVIDIOC_QUERYBUF:
               //printk("VIDIOC_QUERYBUF\n");
               return s5pc100_vidioc_querybuf(cam, arg);
        case VIDIOC_QBUF:
               //printk("VIDIOC_QBUF\n");
               return s5pc100_vidioc_qbuf(cam, arg);
        case VIDIOC_DQBUF:
               //printk("VIDIOC_DQBUF\n");
               return s5pc100_vidioc_dqbuf(cam, filp, arg);
        caseVIDIOC_STREAMON:
               //printk("VIDIOC_STREAMON\n");
               return s5pc100_vidioc_streamon(cam, arg);
        case VIDIOC_STREAMOFF:
               //printk("VIDIOC_STREAMOFF\n");
               return s5pc100_vidioc_streamoff(cam, arg);
        caseVIDIOC_QUERYCTRL:
        case VIDIOC_CROPCAP:
        case VIDIOC_G_PARM:
        case VIDIOC_S_PARM:
        case VIDIOC_S_CTRL:
        case VIDIOC_G_CTRL:
        caseVIDIOC_ENUMINPUT:
        case VIDIOC_G_CROP:
        case VIDIOC_S_CROP:
        caseVIDIOC_ENUM_FMT:
        case VIDIOC_G_FMT:
        case VIDIOC_TRY_FMT:
        case VIDIOC_S_FMT:
        case VIDIOC_ENUM_FRAMESIZES:
        caseVIDIOC_G_JPEGCOMP:
        caseVIDIOC_S_JPEGCOMP:
        case VIDIOC_G_STD:
        case VIDIOC_S_STD:
        caseVIDIOC_QUERYSTD:
        case VIDIOC_ENUMSTD:
        caseVIDIOC_QUERYMENU:
        caseVIDIOC_ENUM_FRAMEINTERVALS:
               return 0;
               return -EINVAL;


        default:
               return -EINVAL;


        }
}
我們將上面實現的函數功能都來看看
case VIDIOC_QUERYCAP:               return s5pc100_vidioc_querycap(cam, arg);
查詢一個驅動的功能        源碼爲下
{
        structv4l2_capability cap = {
               .driver = "s5pc100_camera",
               .capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE,
        };


        strlcpy(cap.card,cam->v4ldev->name, sizeof(cap.card));


        if (copy_to_user(arg,&cap, sizeof(cap)))
               return -EFAULT;


        return 0;
}
其實就睡返回cap這個結構體給用戶空間
case VIDIOC_REQBUFS:             return s5pc100_vidioc_reqbufs(cam,arg);
分配內存&nbsp;     其實這個函數主要執行的功能是調用了s5pc100_request_buffers這個函數,我們來看看這個函數的功能
{
        int i;


        cam->nbuffers =1;
        count = count >cam->nbuffers ? cam->nbuffers : count;


        for (i = 0; i <count; i++) {
               cam->frame[i].buf.index = i;
               cam->frame[i].buf.m.offset = cam->frame[i].phy_base;
               cam->frame[i].buf.length = cam->frame[i].img_size;
               cam->frame[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
               cam->frame[i].buf.sequence = 0;
               cam->frame[i].buf.field = V4L2_FIELD_NONE;
               cam->frame[i].buf.memory = V4L2_MEMORY_MMAP;
               cam->frame[i].buf.flags = 0;
        }


        returncam->nbuffers;
}
注意這個函數中間cam->frame[i].buf.m.offset = cam->frame[i].phy_base;
               cam->frame[i].buf.length = cam->frame[i].img_size;
這兩句話很重要的,cam->frame[i].phy_base這個就是之前算出來的物理地址的首地址,cam->frame[i].img_size;這個就是那個地址的大小,這裏看到返回值是cam->nbuffers是分配的內存數量,這裏程序不管怎樣都值分配一個




s5pc100_vidioc_querybuf(cam, arg);再來看看這個函數做了什麼事情
最主要的功能就是memcpy(&b, &cam->frame[0].buf, sizeof(b));這句話了,將上面那個函數裏面準備的物理地址取出來這樣我們就可以將這個物理地址映射爲虛擬地址來使用


s5pc100_vidioc_streamon   //開始視頻顯示函數
s5pc100_vidioc_streamoff&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//結束視頻顯示函數


上述是我自己研究代碼所得!如有不對希望大家指出來共同學習!謝謝!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章