学习了裸机OV9650P通道LCD直接显示程序,作为这点基础开始分析OV9650linux设备驱动程序。昨天看了点这个驱动程序,让我很郁闷的是写这个程序的人是有毛病还是怎么回事,简简单单的IO口功能引脚的定义,整出了一个套一个的定义,看的我晕乎乎的,也可能是哥根本没有入门,看别人牛逼的程序就看迷糊了。鉴于上面这种情况,我在程序跟踪时只能带一下了。

我们跟踪程序按照常规方法,跟着驱动的编写脉络去读程序。

1、在程序中找到程序入口函数——加载和卸载module_initmodule_exit

 

通过这个入口函数我们找到了函数加载和卸载函数的定义,首先我们分析加载函数(由于函数的嵌套性,我们这里用序号来体现嵌套)

2、camif_init跟踪

①定义Camera Interface的硬件管脚功能(即设置GPJ为功能引脚)

②初始化Camera的虚拟内存地址

 request_mem_region() -- 将起始地址为[start, start+n-1]的资源插入根资源iomem_resource中。参数startI/O内存资源的起始物理地址(CPURAM物理地址空间中的物理地址),参数n指定I/O内存资源的大小。
#define request_mem_region(start, n, name) /
        __request_region(&iomem_resource, (start), (n), (name))
 注调用request_mem_region()不是必须的,但是建议使用。该函数的任务是检查申请的资源是否可用,如果可用则申请成功,并标志为已经使用,其他驱动想再申请该资源时就会失败。
③映射为虚拟内存

ioremap_nocache - 把内存映射到CPU空间  

void __iomem * ioremap_nocache (unsigned long phys_addr, unsigned long size);   

phys_addr 要映射的物理地址  

size 要映射资源的大小  

ioremap_nocache进行一系列平台相关的操作使得CPU可以通过readb/readw/readl/writeb/writew/writelIO函数进行访问。

返回的地址不保证可以作为虚拟地址直接访问。 

[译者按:在译者的使用过程种并没有出现不能作为虚拟地址直接访问的情况,可能是某些平台下的不可以吧。译者的使用平台是x86ixp425]

这个版本的ioremap确保这些内存在CPU是不可缓冲的,如同PCI总线上现存的缓冲规则一样。注:此时在很多总线上仍有其他的缓冲和缓存。在某些特殊的驱动中,作者应当在PCI写的时候进行读取。
这对于一些控制寄存器在这种不希望复合写或者缓冲读的区域内时是非常有用的
返回的映射地址必须使用iounmap来释放。

④初始化Camera的时钟(这里通过一个例子说明函数)

如何获取FCLK, HLCK 和 PCLK的时针频率呢?

可先通过clk_get获取一个clk结构体

/* clk_get获取一个名为id的时针
 * 输入参数dev:   可以为NULL
 * 输入参数id:    时针名称,fclkhclkpclk
 * 返回值:        返回该时钟的clk结构体
 */

struct clk *clk_get(struct device *dev, const char *id)
struct clk {
    struct list_head list;
    struct module *owner;
    struct clk *parent;
    const char *name; /* 该时针名称 */
    int id;
    int usage;
    unsigned long rate; /* 时钟频率 */
    unsigned long ctrlbit;
    int (*enable)(struct clk *, int enable);
    int (*set_rate)(struct clk *c, unsigned long rate);
    unsigned long (*get_rate)(struct clk *c);
    unsigned long (*round_rate)(struct clk *c, unsigned long rate);
    int (*set_parent)(struct clk *c, struct clk *parent);
};

再将clk_get返回的clk结构体传递给clk_get_rate,获取该时钟的频率

unsigned long clk_get_rate(struct clk *clk)

一个例子:

这里出现了另一个时针uclk,专门给usb供给时针信号。uclk是外部时针源,由s3c2410芯片的gph8/uclk管脚引入,给uart提供外部时针信号,以获取更精确地时针频率。

⑤初始化计数器和它的互斥

用互斥锁可以使线程顺序执行。互斥锁通常只允许一个线程执行一个关键部分的
代码,来同步线程。互斥锁也可以用来保护单线程代码。
⑥初始化输入的源图象

⑦初始化Camera 接口的状态

enum

{

CAMIF_STATE_FREE = 0, // not openned

CAMIF_STATE_READY = 1, // openned, but standby

CAMIF_STATE_PREVIEWING = 2, // in previewing

CAMIF_STATE_CODECING = 3 // in capturing

};

⑧初始化命令代码,和初始化等待队列头

enum

{

CAMIF_CMD_NONE = 0,

CAMIF_CMD_SFMT = 1<<0, // source image format changed.

CAMIF_CMD_WND = 1<<1, // window offset changed.

CAMIF_CMD_ZOOM = 1<<2, // zoom picture in/out

CAMIF_CMD_TFMT = 1<<3, // target image format changed.

CAMIF_CMD_P2C = 1<<4, // need camif switches from p-path to c-path

CAMIF_CMD_C2P = 1<<5, // neet camif switches from c-path to p-path

CAMIF_CMD_STOP = 1<<16 // stop capture

};

⑨注册到视频设备层

杂项设备(misc device

杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。在 Linux 内核的include/linux目录下有Miscdevice.h文件,要把自己定义的misc device从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号10 ,一起归于misc device,其实misc_register就是用主标号10调用register_chrdev()的。 

也就是说,misc设备其实也就是特殊的字符设备,可自动生成设备节点。 

字符设备(char device) 

使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时,如果有多个设备使用该函数注册驱动程序,LED_MAJOR不能相同,否则几个设备都无法注册(我已验证)。如果模块使用该方式注册并且 LED_MAJOR0(自动分配主设备号 ),使用insmod命令加载模块时会在终端显示分配的主设备号和次设备号,在/dev目录下建立该节点,比如设备leds,如果加载该模块时分配的主设备号和次设备号为2530,则建立节点:mknod leds c 253 0。使用register_chrdev (LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时都要手动建立节点 ,否则在应用程序无法打开该设备。

这里有一个结构很关键,也是我们程序脉络的一个跟踪点if (misc_register(&misc) < 0)中的misc,我们先把整个加载程序跟踪完,在回过来跟踪这个入口点。

⑩初始化sccb传输协议,与IIC协议相似,并初始化外部摄相设备

首先将GPE14GPE15设置成输入输出功能,将两个引脚都置成高电平,延迟

重起外部摄相硬件设备

初始化外部硬件设备:

打开电源(GPG11设置为输出模式,并给其数据为0

检测设备的厂商ID

显示设备的产品ID

配置OV9650的内部各个寄存器,在配制寄存起开始时down(®s_mutex);在配置结束时up(®s_mutex);

GPG4引脚数据设置为1(这个设置不知道为什么,是开启LCD电源?我把这个语句注释掉了,再编译驱动下载到开发板,用camera_test测试了一下,发现一切正常)

其他的一些出错处理,主要是对前面那些出现错误后的反操作(比如申请内存,如果出错,在出错处理中就要释放内存)。

2、misc的跟踪

  

 

 

 

 

 

 

 

 

这里#define MISC_DYNAMIC_MINOR 255

#define CARD_NAME  "camera"

关键点是camif_fops,这个结构是字符设备驱动程序的核心,当应用程序操作设备文件时所调用的openreadwrite等函数,最终会调用这个结构体中指定的对应函数

2、file_operations结构体camif_fops

 

由这个结构体我们知道,程序定义了三个接口函数open,release,read。因此接下来我们要具体的分跟踪这三个函数,这也是编写字符行设备驱动程序时我们主要完成的工作。

 

 camif_open的实现

①通过has_ov9650来判断是否检测到0v9650设备的厂商ID(这个变量在程序加载时被设置)

②给设备结构体tq2440_camif_dev在内存中分配区域

③将设备结构体中的状态变量设置为CAMIF_STATE_READY

④初始化camera接口,配置相关的寄存器

⑤初始化图像的缓存区(即4DMA通道的缓存地址)

⑥设置请求C通道,P通道的的中断

 

⑦使能camif的时钟,软件重起camif,将设备指针赋值给文件指针的私有数据

⑧最后更新配置(这步在这里好像也是可有可无的)

1init_camif_config

 

①开始图像捕捉,参数表示是捕捉视频流还是仅仅捕捉一张图片

 

②关闭P通道和C通道的中断

③通过一个for循环语句判断数据存放地址的状态,标志为有效,则把数据从内核复制到用户空间,并将标志设置为无效

④使能P通道和C通道的中断

(1)start_capture

 

5、卸载函数camif_cleanup

相对与所有的程序块,这部分应该是最简单的,基本就是释放一些资源,前面的一些反操作

/*

 * camif_cleanup()

 */

static void __exit camif_cleanup(void)

{

struct tq2440_camif_dev *pdev;

// sccb_cleanup();

CFG_READ(SIO_C);

CFG_READ(SIO_D);

pdev = &camera;

misc_deregister(&misc);

clk_put(pdev->clk);

iounmap((void *)camif_base_addr);

release_mem_region((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF);

printk(KERN_ALERT"tq2440_camif: module removed/n");

}

 

6、总结

由上面的程序跟踪我们发现字符设备程序的主体就是file_operation的填充,上面很多内嵌的函数都没有粘贴出来,我不敢说不重要,实际是很多程序嵌来嵌去的很容易把我整蒙,所以只是大致的读了一意思(能明白这块是做什么的以及如果我要修改程序我应该明白修改什么地方就行)。  

                                    

                                   

                                          

Logo

更多推荐