USB攝像頭驅動--CMOS攝像頭

目的:自然景觀->攝像頭模塊->接口->S3C2440的攝像頭控制器->LCD

1.CMOS攝像頭基礎

本次使用的白問網提供的ov7740攝像頭模組,基礎機構如下:在這裏插入圖片描述

1.1攝像頭參數

OV7740_CSP_DS_1.51.pdf—ov7740的datasheet中的參數可知:
在這裏插入圖片描述
支持輸出格式:RAW RGB與YUV格式

  • RAW RGB與RGB的區別是什麼?
    答:所謂的RAW RGB就是隻有紅綠藍三種顏色的數據。而RGB數據,它不僅只表示紅綠藍
    三種顏色,而且還能表示由紅綠藍組合成的任何一種顏色。
  • RGB、YUV又分別是什麼?
    答:RGB、YUV(Y(亮度信號)、U(R-Y的色差信號)、V(B-Y的色差信號)組成)是兩種完全不同的顏色空間,它們之間可以相互轉換。轉換公式如下:
 Y = 0.299R + 0.587G + 0.114B
 U = -0.147R - 0.289G + 0.436B
 V = 0.615R - 0.515G - 0.100B
 R = Y + 1.14V
 G = Y - 0.39U - 0.58V
 B = Y + 2.03U

輸出分辨率爲:VGA(640480)、QVGA(240320)、CIF(352288)、更小的任意大小
在這裏插入圖片描述
有效感光陣列的大小:656
488 = 320128(30W)指感光區域內單像素點的數量,像素越多,拍攝畫面幅面就越大,可拍攝的畫面的細節就越多;

鏡頭的大小(lens size):1/5寸(指感光區域對角線距離,尺寸越大,材料成本越高);

像素點顆粒的大小(pixel size): 4.2um * 4.2um(指單個感光元件的長寬尺寸,也稱單像素的開口尺寸,開口尺寸越大,單位時間內進入的光量就越大,芯片整體性能就相對較高,最終拍攝畫面的整體畫質相對較優秀)

  • 單像素尺寸是圖像傳感器一個相當關鍵的參數,也就是都號稱1200萬像素的相機和手機,相機的效果遠遠好於手機的原因。手機由於體積限制,儘管像素很多,但每個像素都很小,拍攝瞬間進光量也小,成像質量就自然差一些了。
  • 以上三個參數,都是用來描述感光陣列,即使同爲30W像素的攝像頭,如果它的鏡頭尺寸大小越小,那麼對應的像素點顆粒的大小就越小,從而感光性就越差,進而拍攝的效果就越差。

輸入時鐘頻率(input clock frequency): 6~27MHz

  • 即0V7740攝像頭模組的工作頻率範圍。0V7740攝像頭模組的工作時鐘範圍,這個將由SOC提供給CMOS

掃描模式(scan mode): 連續掃描§

  • 掃描方式一般分爲”逐行掃描”§和”隔行掃描”(I)兩種。
    逐行掃描:每一幀圖像由電子束順序地一行接着一行連續掃描而成;
    隔行掃描:把每一幀圖像通過兩場掃描完成則是隔行掃描,兩場掃描中,第一場(奇數場)只掃描奇數行,依次掃描1、3、5…行,而第二場(偶數場)只掃描偶數行,依次掃描2、4、6…行。
    隔行掃描技術在傳送信號帶寬不夠的情況下起了很大作用,逐行掃描和隔行掃描的顯示效果主要區別在穩定性上面,隔行掃描的行間閃爍比較明顯,逐行掃描克服了隔行掃描的缺點,畫面平滑自然無閃爍。

1.2 功能處理

在這裏插入圖片描述
從圖上可以看出處理流程分爲三個部分:

  • image sensor core(ISC)----pdf第四章
    圖像翻轉、增益大小調整、黑電平校準、飽和度的控制、OTP存儲器

  • image sensor processor(ISP)—pdf第五章
    提供測試功能、鏡頭補償功能、自動白平衡、顏色空間的轉換功能(RAW RGB->RGB、RGB->YUV)、窗口功能(自動裁剪圖片)、縮小放大功能;

  • image output interface(ISI)—pdf第六章
    功能:RAW RGB/YUV(圖片數據格式)、VGA/QVGA、BT601/BT656
    BT601有獨立的行同步信號線、幀同步信號線,而BT656是將這兩種信號內嵌到數據中;

2.硬件原理圖

2.1ov7740對外的接口如下:

在這裏插入圖片描述
這是ov7740暴露出來的接口,可以看到CAMERA相關的數據外,還有兩根IIC的數據線和時鐘線。
CAMRST – 復位CMOS攝像頭模塊
CAMCLK – 攝像頭模塊工作的系統時鐘(24MHz)
CAM_HREF – 行同步信號
CAM_VSYNC – 幀同步信號
CAM_PCLK – 像素時鐘
CAMDATA0~7-- 數據線

  • IIC數據線和時鐘線的作用是什麼?
    答:s3c2440通過IIC總線,操作OV7740的寄存器來進行設置的,控制ov7740輸出正確的格式。
    ①:初始化:對攝像頭模塊進行相應的初始化操作,讓攝像頭模塊能夠正常的輸出攝像頭數據;
    ②:控制: 設置亮度、旋轉、縮放等操作;

2.2 對應s3c2440接口

CAM數據類:
在這裏插入圖片描述
在這裏插入圖片描述
CAM控制類:
在這裏插入圖片描述
攝像頭工作流程:
首先SOC輸出CAMCLKOUT給攝像頭提供時鐘,然後控制CAMRST復位攝像頭,再通過I2C初始化、設置攝像頭,最後在HREF、VSYNC和PCLK的控制下,通過D0~D7這八根數據線將數據發給SOC。

3 CAMERA 控制器

攝像頭的採集的數據CPU一般不直接處理,主控芯片裏面集成了Camera控制器FIMC(FullyInteractive Mobile Camera)來處理。
攝像頭先把圖像數據傳給Camera控制器,經過控制器處理(裁剪拉昇後直接預覽或者編碼)之後交給CPU處理。
實際上攝像頭工作需要的時鐘(MCLK)也是FIMC給它提供的。

攝像頭的驅動主要分爲兩部分:
一部分由模組廠家或者sensor廠家提供的初始化代碼,通常是一段數組,通過I2C 總線來控制,用於設置sensor 寄存器,使用時一般不需要修改,如需調整,也由模組廠家完成;
另外一部分是應用處理器端的代碼,這部分需要各個平臺自行開發,在Exynos4412中就是CAMIF和FIMC。

CMOS攝像頭模塊,實質上是一個I2C設備。通過I2C設置、控制攝像頭,SOC的攝像頭控制器(CAMIF和FIMC)負責數據的處理。
因此後面CMOS驅動的核心就是:設置OV7740控制器,使其按想要的方式輸出數據以及傳輸數據。設置camera interface控制器,使其能夠接收ov7740傳進來的數據並處理

3. 1用戶手冊設計的寄存器

如電路圖所示,設計GPE控制器,GPJ控制器的設置。
因此打開用戶手冊,尋找相應的寄存器設置。517頁進行camera interface的設置
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
這是所有涉及的寄存器

  • 問:爲什麼原理圖中涉及IIC(GPE管腳)而程序中並沒有涉及?
    答:原因是程序並不是我們手動的輸入IIC管腳的輸入輸出數據,而是調用i2c_smbus_read_byte_data()內核所提供的的函數進行讀寫。(所以在實驗步驟時會提前先加載IIC的驅動,以便數據的讀寫)

4. 程序編寫

4.1 分配設置一個結構體

驅動的第一步:先寫驅動的入口、出口函數,並用相關的宏定義入口、出口函數。

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

第二步:分配設置一個IIC結構體、並在入口函數註冊這個結構體

/* 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 const struct i2c_device_id cmos_ov7740_id_table[] = {
	{ "cmos_ov7740", 0 },
	{}
};

i2c_device_id :包含兩部分內容,名字和是否有私有數據。

struct i2c_device_id {
	char name[I2C_NAME_SIZE];
	kernel_ulong_t driver_data;	/* Data private to the driver */
};

4.2在probe函數中註冊一個實際操作對象的結構體–video device

在內核比對id之後會自動調用probe()函數:

在probe函數中進行實際的操作,進行video設備驅動的分配設置與註冊

/* 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",
};

填充其中的fops結構體、release函數、ops函數集

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,
};
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;	
}
/* 應用程序通過讀的方式讀取攝像頭的數據 */
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_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,   
};

最後在probe函數中註冊這個video device結構體

/* 2.2.註冊 */
    if(video_register_device(&cmos_ov7740_vdev, VFL_TYPE_GRABBER, -1))
	{
    		printk("unable to register video device\n");
    }

4.3填充probe函數與remove函數

cmos_ov7740_remove:remove函數如往常一樣進行內存的釋放,映射地址的關閉、中斷的釋放、以及video_unregister_device()

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;
}

cmos_ov7740_probe:probe函數中需要進行所有涉及的寄存器的映射以及所有操作流程的安排。

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 復位一下攝像頭模塊   原因:IIC能夠正常操作CMOS模塊的內部的寄存器的前提是:
	 *提供符合它需求的系統時鐘(CAMCLK)
	 *需要給它一個復位信號 */
	/*通過操作CAMIF控制器中相應的寄存器,讓CAMRST發出復位信號,從而復位攝像頭接口,具體操作見驅動源碼*/
	cmos_ov7740_reset();

	/*2.3.6 通過IIC總線,初始化攝像頭模塊*/
	cmos_ov7740_client = client;
	cmos_ov7740_init();

	/*2.3.7 註冊中斷*/
	/*編碼中斷  CAM_C編碼通道  cmos_ov7740_camif_irq_c中斷函數*/
	if (request_irq(IRQ_S3C2440_CAM_C, cmos_ov7740_camif_irq_c, IRQF_DISABLED , "CAM_C", NULL))
		printk("%s:request_irq failed\n", __func__);

	/*預覽中斷  CAM_P預覽通道  cmos_ov7740_camif_irq_p中斷函數*/
	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;
}

4.3.1設置相應的 GPIO用於CAMIF

將GPJCON ,GPJDAT ,GPJUP (必須上拉,所有IIC設備必須有上拉電阻的存在)使能
在這裏插入圖片描述

/*設置相應的 GPIO用於CAMIF*/
static void cmos_ov7740_gpio_cfg(void)
{
	*GPJCON = 0x2aaaaaa;
	*GPJDAT = 0;

	/*使能上拉電阻*/
	*GPJUP  = 0; 
}

4.3.2復位一下CAMIF控制器

CIGCTRL :Global control register 控制軟件復位
在這裏插入圖片描述

static void cmos_ov7740_camif_reset(void)
{
	/*傳輸方式爲bt601*/
	*CISRCFMT |= (1<<31);
	/*復位camif控制器*/
	*CIGCTRL |= (1<<31);
	mdelay(10);

	*CIGCTRL &= ~(1<<31);
	mdelay(10);

}

4.3.3設置使能時鐘(使能HCLK,使能並設置CAMCLK = 24Mhz)

這是所有需要時鐘的通過函數,首先獲得一個時鐘,然後使能這個時鐘,最後再獲得另外一個時鐘,設置這個時鐘的頻率,從而設置時鐘頻率

static void cmos_ov7740_clk_cfg(void)
{
	struct clk *camif_clk;
	struct clk *camif_upll_clk;
	/*使能CAMIF時鐘源(HCLK)像素時鐘--由ov7740提供的*/
	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(CAMCLKOUT)由usb pll(96Mhz)分頻而得,設置給ov7740*/
	camif_upll_clk = clk_get(NULL,"camif-upll");
	if(!camif_upll_clk || IS_ERR(camif_upll_clk))
	{
		printk(KERN_INFO"failed to get CAMIF_UPLL clock source!\n");
	}
	clk_set_rate(camif_upll_clk, 24000000);
	mdelay(100);
}

在這裏插入圖片描述
注:時鐘信息在2手冊255頁從LOCKTIME開始。
在這裏插入圖片描述

4.3.4復位一下攝像頭模塊(OV7740復位一下)

cmos_ov7740_reset:

/*2.3.5 復位一下攝像頭模塊   原因:IIC能夠正常操作CMOS模塊的內部的寄存器的前提是:
	 *提供符合它需求的系統時鐘(CAMCLKOUT)
	 *需要給它一個復位信號 */
	/*通過操作CAMIF控制器中相應的寄存器,讓CAMRST發出復位信號,從而復位攝像頭接口,具體操作見驅動源碼*/
static void cmos_ov7740_reset(void)
{
	*CIGCTRL |= (1<<30);
	mdelay(30);
	*CIGCTRL &= ~(1<<30);
	mdelay(30);
	*CIGCTRL |= (1<<30);
	mdelay(30);
}

在這裏插入圖片描述

4.3.5初始化攝像頭模塊

	cmos_ov7740_client = client;
	cmos_ov7740_init();
static void cmos_ov7740_init(void)
{
	unsigned int mid;
	int i;

	/* 讀 */
	/*讀廠家ID*/
	/*MSB高8位*/
	mid = i2c_smbus_read_byte_data(cmos_ov7740_client, 0x0a)<<8;
	/*LSB低8位*/
	mid |= i2c_smbus_read_byte_data(cmos_ov7740_client, 0x0b);
	printk("manufacture ID = 0x%4x\n", mid);

	/* 寫 :將攝像頭初始化輸出格式爲640x480,30fps的,YUV422輸出格式*/
	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);
	}
}

4.3.6中斷的註冊

每次攝像頭採集完一幀數據時,在下一幀開始前會產生中斷

	/*2.3.7 註冊中斷*/
	/*編碼中斷  CAM_C編碼通道  cmos_ov7740_camif_irq_c中斷函數*/
	if (request_irq(IRQ_S3C2440_CAM_C, cmos_ov7740_camif_irq_c, IRQF_DISABLED , "CAM_C", NULL))
		printk("%s:request_irq failed\n", __func__);

	/*預覽中斷  CAM_P預覽通道  cmos_ov7740_camif_irq_p中斷函數*/
	if (request_irq(IRQ_S3C2440_CAM_P, cmos_ov7740_camif_irq_p, IRQF_DISABLED , "CAM_P", NULL))
		printk("%s:request_irq failed\n", __func__);
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;
}

在這裏插入圖片描述

4.4啓動攝像頭

除了關於buf的有關寄存器,其餘寄存器都在這裏設置

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  正常情況下   forYCbCr)
				  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表示不翻轉   1表示翻轉
			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):就是DMA傳輸時的最大完整長度
				bit[18:14]			--剩餘突發長度(remained burst):最後一次需要搬運的字節
				bit[2]				--是否使能LastIRQ功能:一般每一次數據採集前觸發一次中斷,LastIRQ最後一針採集完才觸發中斷

	*/
	CalculateBurstSize(bytesperline,&Main_burst,&Remained_burst);
	*CIPRCTRL = (Main_burst<<19) | (Remained_burst<<14) | (0<<2);

	/*
	*CIPRSCPRERATIO:
				bit[31:28]		--預覽縮放的變化係數(SHfactor)
				bit[22:16]		--預覽縮放的水平比(PreHorRatio)
				bit[6:0]		--預覽縮放的垂直比(PreVerRatio)
	*
	*CIPRSCPREDST:
				bit[27:16]		--預覽縮放的目標寬度(PreDstWidth)
				bit[11:0]		--預覽縮放的目標高度(PreDstHeight)
	*
	*CIPRSCCTRL:
				bit[31]			--固定設置爲1(與縮放無關)
				bit[30]			--設置圖像輸出格式是RGB16(0)或RGB24(1)
				bit[29:28]		--用來告訴攝像頭控制器圖片是縮小還是放大(ScaleUpDown)
				bit[24:16]		--預覽主縮放的水平比(MainHorRatio)
				bit[15]			--預覽縮放開始
				bit[8:0]		--預覽主縮放的垂直比(MainVerRatio)
	*/
	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;
}
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;		/*目的圖片垂直高度*/

	/*__func__表示函數名字*/
	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.MainHorRatio = (sx<<8) / (tx << sc.H_Shift);

	sc.PreDstHeight = sy / sc.PreVerRatio;
	sc.MainVerRatio = (sy<<8) / (ty << sc.V_Shift);

	sc.SHfactor = 10 - (sc.H_Shift + sc.V_Shift);

	sc.ScaleUpDown = (tx >= sx) ? 1 : 0;
}
static void CalculateBurstSize(unsigned int hSize, unsigned int *mainBurstSize,unsigned int *remainBurstSize)
{
	unsigned int tmp;

	tmp = (hSize/4) % 16;		//hSize/4多少個字
	switch (tmp)
	{
		case 0:
			*mainBurstSize = 16;
			*remainBurstSize = 16;
			break;
		case 4:
			*mainBurstSize = 16;
			*remainBurstSize = 4;
			break;
		case 8:
			*mainBurstSize = 16;
			*remainBurstSize = 8;
			break;
		default:
			tmp = (hSize/4) % 8;
			switch (tmp)
			{
				case 0:
					*mainBurstSize = 8;
					*remainBurstSize = 8;
					break;
				case 4:
					*mainBurstSize = 8;
					*remainBurstSize = 4;
					break;
				default:
					*mainBurstSize = 4;
					tmp = (hSize/4) % 4;
					*remainBurstSize = (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;}
}

其餘ioctl函數(參考uvc驅動的ioctl函數)

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((unsigned long)NULL == img_buff[0].virt_base)
	{
		goto erro0;
	}
	img_buff[0].phy_base = __virt_to_phys(img_buff[0].virt_base);

	order = get_order(buf_size);
	img_buff[1].order = order;
	img_buff[1].virt_base = __get_free_pages(GFP_KERNEL | __GFP_DMA, img_buff[1].order);
	if((unsigned long)NULL == img_buff[1].virt_base)
	{
		goto erro1;
	}
	img_buff[1].phy_base = __virt_to_phys(img_buff[0].virt_base);

	order = get_order(buf_size);
	img_buff[2].order = order;
	img_buff[2].virt_base = __get_free_pages(GFP_KERNEL | __GFP_DMA, img_buff[2].order);
	if((unsigned long)NULL == img_buff[2].virt_base)
	{
		goto erro2;
	}
	img_buff[2].phy_base = __virt_to_phys(img_buff[2].virt_base);

	order = get_order(buf_size);
	img_buff[3].order = order;
	img_buff[3].virt_base = __get_free_pages(GFP_KERNEL | __GFP_DMA, img_buff[3].order);
	if((unsigned long)NULL == img_buff[3].virt_base)
	{
		goto erro3;
	}
	img_buff[3].phy_base = __virt_to_phys(img_buff[0].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;

	/*執行到erro3後悔繼續往下執行erro2,erro1,erro0*/
	return 0;
erro3:
	free_pages(img_buff[2].virt_base, order);
	img_buff[2].phy_base = (unsigned long)NULL;
erro2:
	free_pages(img_buff[1].virt_base, order);
	img_buff[1].phy_base = (unsigned long)NULL;
erro1:
	free_pages(img_buff[0].virt_base, order);
	img_buff[0].phy_base = (unsigned long)NULL;
erro0:
	return -ENOMEM;
}

總結

對於CMOS的攝像頭,我們是在開發板設置相應的寄存器,只需要找到芯片手冊,設置對應位即可!

遺留問題:
在這裏插入圖片描述

CAMPCLK(像素時鐘)是由誰產生,它與CAMCLKOUT之間有什麼關係?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章