OV9650驱动程序跟踪
(2012-05-31 11:11:48)学习了裸机OV9650的P通道LCD直接显示程序,作为这点基础开始分析OV9650在linux设备驱动程序。
我们跟踪程序按照常规方法,跟着驱动的编写脉络去读程序。
1、在程序中找到程序入口函数——加载和卸载module_init和module_exit
- module_init(camif_init);
- module_exit(camif_cleanup);
通过这个入口函数我们找到了函数加载和卸载函数的定义,首先我们分析加载函数(由于函数的嵌套性,我们这里用序号来体现嵌套)
2、camif_init跟踪
- static
int __init camif_init(void) - {
-
int ret; -
struct tq2440_camif_dev * pdev; -
struct clk * camif_upll_clk; -
-
printk(KERN_ALERT"initializing s3c2440 camera interface....../n"); -
-
pdev = &camera; -
-
-
s3c2410_gpio_cfgpin(S3C2440_GPJ0, S3C2440_GPJ0_CAMDATA0); -
s3c2410_gpio_cfgpin(S3C2440_GPJ1, S3C2440_GPJ1_CAMDATA1); -
s3c2410_gpio_cfgpin(S3C2440_GPJ2, S3C2440_GPJ2_CAMDATA2); -
s3c2410_gpio_cfgpin(S3C2440_GPJ3, S3C2440_GPJ3_CAMDATA3); -
s3c2410_gpio_cfgpin(S3C2440_GPJ4, S3C2440_GPJ4_CAMDATA4); -
s3c2410_gpio_cfgpin(S3C2440_GPJ5, S3C2440_GPJ5_CAMDATA5); -
s3c2410_gpio_cfgpin(S3C2440_GPJ6, S3C2440_GPJ6_CAMDATA6); -
s3c2410_gpio_cfgpin(S3C2440_GPJ7, S3C2440_GPJ7_CAMDATA7); -
s3c2410_gpio_cfgpin(S3C2440_GPJ8, S3C2440_GPJ8_CAMPCLK); -
s3c2410_gpio_cfgpin(S3C2440_GPJ9, S3C2440_GPJ9_CAMVSYNC); -
s3c2410_gpio_cfgpin(S3C2440_GPJ10, S3C2440_GPJ10_CAMHREF); -
s3c2410_gpio_cfgpin(S3C2440_GPJ11, S3C2440_GPJ11_CAMCLKOUT); -
s3c2410_gpio_cfgpin(S3C2440_GPJ12, S3C2440_GPJ12_CAMRESET); -
-
-
if (!request_mem_region((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF, CARD_NAME)) -
{ -
ret = -EBUSY; -
goto error1; -
} -
-
-
camif_base_addr = (unsigned long)ioremap_nocache((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF); -
if (camif_base_addr == (unsigned long)NULL) -
{ -
ret = -EBUSY; -
goto error2; -
} -
-
-
pdev->clk = clk_get(NULL, "camif"); -
if (IS_ERR(pdev->clk)) -
{ -
ret = -ENOENT; -
goto error3; -
} -
clk_enable(pdev->clk); -
-
camif_upll_clk = clk_get(NULL, "camif-upll"); -
clk_set_rate(camif_upll_clk, 24000000); -
mdelay(100); -
-
-
mutex_init(&pdev->rcmutex); -
pdev->rc = 0; -
-
-
pdev->input = 0; -
-
-
pdev->state = CAMIF_STATE_FREE; -
-
-
pdev->cmdcode = CAMIF_CMD_NONE; -
init_waitqueue_head(&pdev->cmdqueue); -
-
-
if (misc_register(&misc) < 0) -
{ -
ret = -EBUSY; -
goto error4; -
} -
printk(KERN_ALERT"s3c2440 camif init done/n"); -
- //
sccb_init(); -
CFG_WRITE(SIO_C); -
CFG_WRITE(SIO_D); -
-
High(SIO_C); -
High(SIO_D); -
WAIT_STABLE(); -
-
hw_reset_camif(); -
has_ov9650 = s3c2440_ov9650_init() >= 0; -
s3c2410_gpio_setpin(S3C2410_GPG4, 1); -
return 0; -
- error4:
-
clk_put(pdev->clk); - error3:
-
iounmap((void *)camif_base_addr); - error2:
-
release_mem_region((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF); -
- error1:
-
return ret; - }
①定义Camera
②初始化Camera的虚拟内存地址
#define
③映射为虚拟内存
ioremap_nocache
void
phys_addr
size
ioremap_nocache进行一系列平台相关的操作使得CPU可以通过readb/readw/readl/writeb/writew/writel等IO函数进行访问。
返回的地址不保证可以作为虚拟地址直接访问。
[译者按:在译者的使用过程种并没有出现不能作为虚拟地址直接访问的情况,可能是某些平台下的不可以吧。译者的使用平台是x86和ixp425]
这个版本的ioremap确保这些内存在CPU是不可缓冲的,如同PCI总线上现存的缓冲规则一样。注:此时在很多总线上仍有其他的缓冲和缓存。在某些特殊的驱动中,作者应当在PCI写的时候进行读取。
这对于一些控制寄存器在这种不希望复合写或者缓冲读的区域内时是非常有用的
返回的映射地址必须使用iounmap来释放。
④初始化Camera的时钟(这里通过一个例子说明函数)
如何获取FCLK,
可先通过clk_get获取一个clk结构体
struct |
再将clk_get返回的clk结构体传递给clk_get_rate,获取该时钟的频率
unsigned |
一个例子:
这里出现了另一个时针uclk,专门给usb供给时针信号。uclk是外部时针源,由s3c2410芯片的gph8/uclk管脚引入,给uart提供外部时针信号,以获取更精确地时针频率。
⑤初始化计数器和它的互斥
用互斥锁可以使线程顺序执行。互斥锁通常只允许一个线程执行一个关键部分的
代码,来同步线程。互斥锁也可以用来保护单线程代码。
⑥初始化输入的源图象
⑦初始化Camera
enum
{
CAMIF_STATE_FREE
CAMIF_STATE_READY
CAMIF_STATE_PREVIEWING
CAMIF_STATE_CODECING
};
⑧初始化命令代码,和初始化等待队列头
enum
{
CAMIF_CMD_NONE =
CAMIF_CMD_SFMT =
CAMIF_CMD_WND =
CAMIF_CMD_ZOOM =
CAMIF_CMD_TFMT =
CAMIF_CMD_P2C =
CAMIF_CMD_C2P =
CAMIF_CMD_STOP =
};
⑨注册到视频设备层
杂项设备(misc
杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。在
也就是说,misc设备其实也就是特殊的字符设备,可自动生成设备节点。
字符设备(char
使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时,如果有多个设备使用该函数注册驱动程序,LED_MAJOR不能相同,否则几个设备都无法注册(我已验证)。如果模块使用该方式注册并且
这里有一个结构很关键,也是我们程序脉络的一个跟踪点if
⑩初始化sccb传输协议,与IIC协议相似,并初始化外部摄相设备
首先将GPE14和GPE15设置成输入输出功能,将两个引脚都置成高电平,延迟
重起外部摄相硬件设备
初始化外部硬件设备:
打开电源(GPG11设置为输出模式,并给其数据为0)
检测设备的厂商ID
显示设备的产品ID
配置OV9650的内部各个寄存器,在配制寄存起开始时down(®s_mutex);在配置结束时up(®s_mutex);
将GPG4引脚数据设置为1(这个设置不知道为什么,是开启LCD电源?我把这个语句注释掉了,再编译驱动下载到开发板,用camera_test测试了一下,发现一切正常)
其他的一些出错处理,主要是对前面那些出现错误后的反操作(比如申请内存,如果出错,在出错处理中就要释放内存)。
2、misc的跟踪
- static
struct miscdevice misc = { -
.minor = MISC_DYNAMIC_MINOR, -
.name = CARD_NAME, -
.fops = &camif_fops, - };
这里#define
#define
关键点是camif_fops,这个结构是字符设备驱动程序的核心,当应用程序操作设备文件时所调用的open,read,write等函数,最终会调用这个结构体中指定的对应函数
2、file_operations结构体camif_fops
- static
struct file_operations camif_fops = - {
-
.owner = THIS_MODULE, -
.open = camif_open, -
.release = camif_release, -
.read = camif_read, - };
由这个结构体我们知道,程序定义了三个接口函数open,release,read。因此接下来我们要具体的分跟踪这三个函数,这也是编写字符行设备驱动程序时我们主要完成的工作。
㈠
- static
int camif_open(struct inode *inode, struct file *file) - {
-
struct tq2440_camif_dev *pdev; -
struct tq2440_camif_fh *fh; -
-
int ret; -
-
if (!has_ov9650) { -
return -ENODEV; -
} -
pdev = &camera; -
-
-
fh = kzalloc(sizeof(*fh),GFP_KERNEL); // alloc memory for filehandle -
if (NULL == fh) -
{ -
return -ENOMEM; -
} -
fh->dev = pdev; -
-
pdev->state = CAMIF_STATE_READY; -
-
init_camif_config(fh); -
-
ret = init_image_buffer(); // init image buffer. -
if (ret < 0) -
{ -
goto error1; -
} -
-
request_irq(IRQ_S3C2440_CAM_C, on_camif_irq_c, IRQF_DISABLED, "CAM_C", pdev); // setup ISRs -
if (ret < 0) -
{ -
goto error2; -
} -
-
request_irq(IRQ_S3C2440_CAM_P, on_camif_irq_p, IRQF_DISABLED, "CAM_P", pdev); -
if (ret < 0) -
{ -
goto error3; -
} -
-
clk_enable(pdev->clk); // and enable camif clock. -
-
soft_reset_camif(); -
-
file->private_data = fh; -
fh->dev = pdev; -
update_camif_config(fh, 0); -
return 0; -
- error3:
-
free_irq(IRQ_S3C2440_CAM_C, pdev); - error2:
-
free_image_buffer(); - error1:
-
kfree(fh); -
return ret; - }
①通过has_ov9650来判断是否检测到0v9650设备的厂商ID(这个变量在程序加载时被设置)
②给设备结构体tq2440_camif_dev在内存中分配区域
③将设备结构体中的状态变量设置为CAMIF_STATE_READY
④初始化camera接口,配置相关的寄存器
⑤初始化图像的缓存区(即4个DMA通道的缓存地址)
⑥设置请求C通道,P通道的的中断
⑦使能camif的时钟,软件重起camif,将设备指针赋值给文件指针的私有数据
⑧最后更新配置(这步在这里好像也是可有可无的)
(1)init_camif_config
- static
void init_camif_config(struct tq2440_camif_fh * fh) - {
-
struct tq2440_camif_dev * pdev; -
-
pdev = fh->dev; -
-
pdev->input = 0; // FIXME, the default input image format, see inputs[] for detail. -
-
-
pdev->srcHsize = 1280; // FIXME, the OV9650's horizontal output pixels. -
pdev->srcVsize = 1024; // FIXME, the OV9650's verical output pixels. -
-
-
pdev->wndHsize = 1280; -
pdev->wndVsize = 1024; -
-
-
pdev->coTargetHsize = pdev->wndHsize; -
pdev->coTargetVsize = pdev->wndVsize; -
-
-
pdev->preTargetHsize = 320; -
pdev->preTargetVsize = 240; -
-
update_camif_config(fh, CAMIF_CMD_STOP); - }
-
- 这里有个函数update_camif_config(fh,CMAIF_CMD_STOP),用于更新camera接口的配置
-
- static
void update_camif_config (struct tq2440_camif_fh * fh, u32 cmdcode) - {
-
struct tq2440_camif_dev * pdev; -
-
pdev = fh->dev; -
-
switch (pdev->state) -
{ -
case CAMIF_STATE_READY: -
update_camif_regs(fh->dev); // config the regs directly. -
break; -
-
case CAMIF_STATE_PREVIEWING: -
-
-
-
disable_irq(IRQ_S3C2440_CAM_P); // disable cam-preview irq. -
-
-
if (cmdcode & CAMIF_CMD_SFMT) -
{ -
// ignore it, nothing to do now. -
} -
-
-
if (cmdcode & CAMIF_CMD_TFMT) -
{ -
-
pdev->cmdcode |= CAMIF_CMD_TFMT; -
} -
-
-
if (cmdcode & CAMIF_CMD_WND) -
{ -
pdev->cmdcode |= CAMIF_CMD_WND; -
} -
-
-
if (cmdcode & CAMIF_CMD_ZOOM) -
{ -
pdev->cmdcode |= CAMIF_CMD_ZOOM; -
} -
-
-
if (cmdcode & CAMIF_CMD_STOP) -
{ -
pdev->cmdcode |= CAMIF_CMD_STOP; -
} -
enable_irq(IRQ_S3C2440_CAM_P); // enable cam-preview irq. -
-
wait_event(pdev->cmdqueue, (pdev->cmdcode==CAMIF_CMD_NONE)); // wait until the ISR completes command. -
break; -
-
case CAMIF_STATE_CODECING: -
-
-
-
disable_irq(IRQ_S3C2440_CAM_C); // disable cam-codec irq. -
-
-
if (cmdcode & CAMIF_CMD_SFMT) -
{ -
// ignore it, nothing to do now. -
} -
-
-
if (cmdcode & CAMIF_CMD_TFMT) -
{ -
-
pdev->cmdcode |= CAMIF_CMD_TFMT; -
} -
-
-
if (cmdcode & CAMIF_CMD_WND) -
{ -
pdev->cmdcode |= CAMIF_CMD_WND; -
} -
-
-
if (cmdcode & CAMIF_CMD_ZOOM) -
{ -
pdev->cmdcode |= CAMIF_CMD_ZOOM; -
} -
-
-
if (cmdcode & CAMIF_CMD_STOP) -
{ -
pdev->cmdcode |= CAMIF_CMD_STOP; -
} -
enable_irq(IRQ_S3C2440_CAM_C); // enable cam-codec irq. -
wait_event(pdev->cmdqueue, (pdev->cmdcode==CAMIF_CMD_NONE)); // wait until the ISR completes command. -
break; -
-
default: -
break; -
} - }
-
-
- update_camif_regs(fh->dev);表示如果为准备状态就直接配置寄存器
-
- static
void __inline__ update_camif_regs(struct tq2440_camif_dev * pdev) - {
-
if (!in_irq()) -
{ -
while(1) // wait until VSYNC is 'L' -
{ -
barrier(); -
if ((ioread32(S3C244X_CICOSTATUS)&(1<<28)) == 0) -
break; -
} -
} -
-
-
update_source_fmt_regs(pdev); -
update_target_wnd_regs(pdev); -
update_target_fmt_regs(pdev); -
update_target_zoom_regs(pdev); - }
-
- 这里最后4个函数是配置camera
interface寄存器的函数,由于寄存器的配置过程跟裸机的配置大同小异,所以这里就不进入其中讲解了,但有一点要注意,如果要修改显示效果,我觉得可以到这里面来配置一下寄存器,修改一下参数 - init_camif_buffer
-
-
- static
int __inline__ init_image_buffer(void) - {
-
int size1, size2; -
unsigned long size; -
unsigned int order; -
-
-
size1 = MAX_C_WIDTH * MAX_C_HEIGHT * 16 / 8; -
-
-
size2 = MAX_P_WIDTH * MAX_P_HEIGHT * 16 / 8; -
-
size = (size1 > size2)?size1:size2; -
-
order = get_order(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) -
{ -
goto error0; -
} -
img_buff[0].phy_base = img_buff[0].virt_base - PAGE_OFFSET + PHYS_OFFSET; // the DMA address. -
-
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) -
{ -
goto error1; -
} -
img_buff[1].phy_base = img_buff[1].virt_base - PAGE_OFFSET + PHYS_OFFSET; // the DMA address. -
-
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) -
{ -
goto error2; -
} -
img_buff[2].phy_base = img_buff[2].virt_base - PAGE_OFFSET + PHYS_OFFSET; // the DMA address. -
-
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) -
{ -
goto error3; -
} -
img_buff[3].phy_base = img_buff[3].virt_base - PAGE_OFFSET + PHYS_OFFSET; // the DMA address. -
-
invalid_image_buffer(); -
-
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; - }
-
- (二)
camif_read的实现 -
- static
ssize_t camif_read(struct file *file, char __user *data, size_t count, loff_t *ppos) - {
-
int i; -
struct tq2440_camif_fh * fh; -
struct tq2440_camif_dev * pdev; -
-
fh = file->private_data; -
pdev = fh->dev; -
-
-
if (start_capture(pdev, 0) != 0) -
{ -
return -ERESTARTSYS; -
} -
-
disable_irq(IRQ_S3C2440_CAM_C); -
disable_irq(IRQ_S3C2440_CAM_P); -
for (i = 0; i < 4; i++) -
{ -
if (img_buff[i].state != CAMIF_BUFF_INVALID) -
{ -
copy_to_user(data, (void *)img_buff[i].virt_base, count); -
img_buff[i].state = CAMIF_BUFF_INVALID; -
} -
} -
enable_irq(IRQ_S3C2440_CAM_P); -
enable_irq(IRQ_S3C2440_CAM_C); -
-
-
return count;
①开始图像捕捉,参数表示是捕捉视频流还是仅仅捕捉一张图片
②关闭P通道和C通道的中断
③通过一个for循环语句判断数据存放地址的状态,标志为有效,则把数据从内核复制到用户空间,并将标志设置为无效
④使能P通道和C通道的中断
(1)start_capture
-
-
- static
int start_capture(struct tq2440_camif_dev * pdev, int stream) - {
-
int ret; -
-
u32 ciwdofst; -
u32 ciprscctrl; -
u32 ciimgcpt; -
-
ciwdofst = ioread32(S3C244X_CIWDOFST); -
ciwdofst |= (1<<30) // Clear the overflow indication flag of input CODEC FIFO Y -
|(1<<15) // Clear the overflow indication flag of input CODEC FIFO Cb -
|(1<<14) // Clear the overflow indication flag of input CODEC FIFO Cr -
|(1<<13) // Clear the overflow indication flag of input PREVIEW FIFO Cb -
|(1<<12); // Clear the overflow indication flag of input PREVIEW FIFO Cr -
iowrite32(ciwdofst, S3C244X_CIWDOFST); -
-
ciprscctrl = ioread32(S3C244X_CIPRSCCTRL); -
ciprscctrl |= 1<<15; // preview scaler start -
iowrite32(ciprscctrl, S3C244X_CIPRSCCTRL); -
-
pdev->state = CAMIF_STATE_PREVIEWING; -
-
ciimgcpt = (1<<31) // camera interface global capture enable -
|(1<<29); // capture enable for preview scaler. -
iowrite32(ciimgcpt, S3C244X_CIIMGCPT); -
-
ret = 0; -
if (stream == 0) -
{ -
pdev->cmdcode = CAMIF_CMD_STOP; -
-
ret = wait_event_interruptible(pdev->cmdqueue, pdev->cmdcode == CAMIF_CMD_NONE); -
} -
return ret; - }
-
- ⑴配置窗口移位寄存器,清空各个FIFO
- ⑵配置预览通道主框控制寄存器,预览模式开始
- ⑶将设备接口体的状态标志标识为CAMIF_STATE_PREVIEWING
- ⑷配置图像捕捉使能寄存器,图像捕捉全局使能,预览通道捕捉使能
- ⑸一个判断语句,如果stream为0,则设备结构体的命令变量变为CAMIF_CMD_STOP,并设置一个等待中断队列
- camif_release的实现
-
- static
int camif_release(struct inode *inode, struct file *file) - {
-
struct tq2440_camif_fh * fh; -
struct tq2440_camif_dev * pdev; -
-
fh = file->private_data; -
pdev = fh->dev; -
-
-
clk_disable(pdev->clk); // stop camif clock -
-
free_irq(IRQ_S3C2440_CAM_P, pdev); // free camif IRQs -
-
free_irq(IRQ_S3C2440_CAM_C, pdev); -
-
free_image_buffer(); // and free image buffer -
-
return 0; - }
-
- ①关闭时钟,释放P通道中断,释放C通道中断,释放4个DMA缓存
5、卸载函数camif_cleanup
相对与所有的程序块,这部分应该是最简单的,基本就是释放一些资源,前面的一些反操作
static
{
struct
// sccb_cleanup();
CFG_READ(SIO_C);
CFG_READ(SIO_D);
pdev
misc_deregister(&misc);
clk_put(pdev->clk);
iounmap((void
release_mem_region((unsigned
printk(KERN_ALERT"tq2440_camif:
}
6、总结
由上面的程序跟踪我们发现字符设备程序的主体就是file_operation的填充,上面很多内嵌的函数都没有粘贴出来,我不敢说不重要,实际是很多程序嵌来嵌去的很容易把我整蒙,所以只是大致的读了一意思(能明白这块是做什么的以及如果我要修改程序我应该明白修改什么地方就行)。