Bcm2079x型号NFC开发版,与主机有5根pin脚,分别是两根IIC通信线、中断脚、使能脚、唤醒脚。
驱动需要完成IIC注册,中断的初始化,pin脚初始化。
2 驱动初始化
2.1 初始化流程图
2.2 代码分析
在驱动代码中,需要静态初始化数据结构,代码如下:
staticconst unsigned short normal_i2c[] = {0x77,I2C_CLIENT_END}; staticstruct i2c_driver bcm2079x_driver = { }; |
完成probe、remove方法的映射,已经IIC地址的映射,bcm2079x的IIC地址是0x77。
内核加载驱动模块的时候,调用bcm2079x_dev_init()
#definePIO_BASE_ADDRESS #definePIO_RANGE_SIZE static int__init bcm2079x_dev_init(void) { exit_ioremap_failed: } |
调用ioremap()将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问,PIO_BASE_ADDRESS是要映射的物理地址,PIO_RANGE_SIZE是要映射的长度。
映射detect方法,在模块加载时,进行硬件检测,判断硬件是否存在,满足条件之后,才会注册i2c设备相关信息,创建i2c-client,并注册i2c driver,执行probe操作;看看该方法吧:
intbcm2079x_detect(struct i2c_client *client, struct i2c_board_info*info) { } |
满足条件之后,就调用bcm2079x_driver.probe()方法了,映射到bcm2079x_probe()中,在这里面完成初始化工作。代码如下:
static intbcm2079x_probe(struct i2c_client *client, { // platform_data->en_gpio =gpio_request_ex("bcm2079x_para", "bcm2079x_en_port");//获取使能引脚的句柄,继续山寨 // // // // } |
在我们的平台上,pgio和中断的使用都不标准,非常苦恼,这就是山寨的公司!!
在上面代码中,调用gpio_request_ex()获取引脚的句柄,其实是去解析一个配置文件里面的信息,在配置文件里面定义有IO,这是我们公司自己的一套,山寨!也学习一下吧!得到IO句柄之后,就可以对其进行初始化了,上面就完成了3个IO口的初始化,分别是中断、使能和唤醒。
为bcm2079x_dev数据结构分配内存空间,并初始化一些变量,其定义如下:
structbcm2079x_dev { }; |
接着,要向内核注册该设备的驱动,调用misc_register(&bcm2079x_dev->bcm2079x_device)进行注册。前面也定义了主设备号、设备名称和file_operations结构体。
调用request_irq()完成中断的申请,这里的中断号是28,并注册了中断响应函数bcm2079x_dev_irq_handler,当有中断发生的时候调用它。在这里的初始化工作中,我们暂时不对中断使能。
到此,初始化工作就完成了,等待着用户空间的调用。
3 用户空间调用
3.1 流程图
3.2代码分析
3.2.1 打开设备
用户空间通过系统调用open,打开驱动对应的设备节点,那么,将调用到驱动程序中的bcm2079x_dev_open()方法,代码如下:
static intbcm2079x_dev_open(struct inode *inode, struct file*filp) { } |
在open方法中,主要是完成两件事情,第一是获取在probe()方法中分配内存空间的bcm2079x_dev的指针,并赋值给filp->private_data,第二是对中断的使能。
先看看container_of()方法,作用是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。在这里已知的filp->private_data是指向bcm2079x_dev结构体的域成员变量bcm2079x_device的指针。调用container_of()后得到bcm2079x_dev的指针,然后再赋值给filp->private_data.
接下来就对中断使能了,代码如下:
staticvoid bcm2079x_enable_irq(struct bcm2079x_dev*bcm2079x_dev) { } |
3.2.2 ioctl使能唤醒
用户空间open()驱动节点之后,就获得了该驱动节点的文件描述符,然后就可以对其进行使能唤醒了。通过系统调用ioctl(),调用到驱动程序的bcm2079x_dev_unlocked_ioctl()方法,代码如下:
staticlong bcm2079x_dev_unlocked_ioctl(struct file *filp, { } |
gpio_write_one_pin_value()方法又看到了(山寨…),调用它往IO口写高低电平,上面代码分别是给使能和唤醒口写高电平,完成使能唤醒。
3.2.3 等待中断
在用户空间的设计中,需要开一个新的线程循环读取数据,当没有数据的时候,线程将进入休眠,等待中断唤醒线程。系统调用的poll()方法在线程中调用,当没有数据的时候,将在poll()方法中休眠。对应的驱动程序方法是bcm2079x_dev_poll():
staticunsigned int bcm2079x_dev_poll(struct file *filp, poll_table*wait) { } |
在用户空间线程循环中,当没有数据的时候,将在bcm2079x_dev_poll()调用中等待中断的唤醒,而在bcm2079x_dev_poll()方法中,将在中poll_wait()等待。poll_wait()函数,它的原型:
void poll_wait(struct file *filp,wait_queue_head_t *queue, poll_table *wait); |
它的作用就是把当前进程添加到wait参数指定的等待列表(poll_table)中。等待唤醒。
3.2.4 响应中断,唤醒等待线程
我们在申请中断的时候,注册了一个响应中断的方法,申请中断的代码如下:
request_irq(client->irq, bcm2079x_dev_irq_handler, |
该方法原形如下:
int |
参数irq表示所要申请的硬件中断号。handler为向系统登记的中断处理子程序,中
断产生时由系统来调用,调用时所带参数irq为中断号,dev_id为申请时告诉系统的设备
标识。device为设备名。flag是申请时的选项,它决定中断处理程序的一些特性,其中最重要的是中断处理程序是快速处理程序(flag里设置了SA_INTERRUPT)还是慢速处理程(不设SA
IRQ,这些处理程序之间以dev_id来区分。如果中断由某个处理程序独占,则dev_id可以
为NULL。request_irq返回0表示成功,返回-INVAL表示irq>15或handler==NULL,返回-
EBUSY表示中断已经被占用且不能共享。
bcm2079x_dev_irq_handler方法为向系统登记的中断处理子程序,其代码如下:
staticirqreturn_t bcm2079x_dev_irq_handler(int irq, void*dev_id) { } |
在前面我们介绍3.2.3的时候,说到poll()方法中的阻塞方法poll_wait(filp,&bcm2079x_dev->read_wq,wait);其一直在等待bcm2079x_dev->read_wq信号,而在handler方法中,调用了wake_up(),就的跳出poll_wait()的等待,这样poll()方法就有了返回值,用户空间我认为有数据可以读了,于是就系统调用read()方法读取数据。
3.2.5 读数据
用户空间调用read()方法,读取设备节点的数据,最终调用到驱动程序的bcm2079x_dev_read()方法,代码如下:
staticssize_t bcm2079x_dev_read(struct file *filp, char __user*buf, { } |
看起来简单吧!
参考:
container_of:http://www.cnblogs.com/sdphome/archive/2011/09/14/2176624.html
poll_wart():http://blog.chinaunix.net/uid-26851094-id-3175940.html