这次弄一个复杂一点的驱动测试,暂且认为是LED驱动吧!应用程序借用驱动程序控制4个LED灯亮灭。
对于驱动程序的编写,首先是构建框架,打通LED与应用程序之间的关系。本文在《字符设备驱动之"Hello, World!"》的基础上捣鼓,框架已经有了。然后便是完善对硬件的操作。
具体下文细细道来。
此LED驱动程序首先能够实现的功能是:调用应用程序app_test1,能够实现LED灯亮灭控制。譬如在终端输入“app_test1 on”,灯亮;输入“app_test1 off”,灯灭。
对于完善硬件的操作,首先阅读原理图和S3C2440 datasheet,然后编写代码。
通过原理图,可以知道开发板四个控制LED的IO口,如下:
然后阅读S3C2440 datasheet,相关GPIO控制寄存器信息如下:
更多信息参考datasheet,接下来就是编写代码了。
1.首先添加两个指向GPBCON、GPBDAT控制寄存器的全局变量,如下:
显然需要将控制寄存器的地址映射到这两个变量上。
2. 在入口函数(test1_init)加上两行代码,如下:
内核在初始化阶段完成了物理地址到虚拟地址的映射工作,但我们只能知道的GPBCON的物理地址,所以需要实现物理地址到虚拟地址的映射,ioremap就是专门干这事儿的函数,它的具体解析此处不详细阐述。
ps:gpbcon和gpbdat都是定义为long类型的指针变量,即它们都是指向32bit数据的地址指针,“gpbdat = gpbcon + 1;”中的1的单位是long数据类型的长度(4字节),而不是字节。
3.入口完成映射,同样,需要在出口解除映射,在出口函数(test1_exit)加上一行代码,如下:
对IO口写操作有两个步骤要做:一是设置IO口状态,二是赋值。前者操作GPBCON寄存器,后者操作GPBDAT寄存器。前者在驱动的open函数里实现,后者在驱动的write函数里实现。
4.在open函数(test1_open)添加两行代码如下:
ps:这两行操作GPIO的代码比较常见,相较于直接赋值,这样做的好处是不影响其他不相干的IO状态,本文需要操作的IO口是GPB5、GPB6、GPB7、GPB8,首先把它们的相关位(每个IO口对应2bit)给清零,然后对相关位赋值01,使其处于输出状态。
5.在write函数(test1_write)添加代码如下:
对于test1_write函数,应用程序在执行系统调用write的时候调用它,系统调用write有传递参数,buf就是传递的一个参数,关于其他参数的意义,此处略过。值得注意的是,获取系统调用write的参数不知直接用“val = buf;”来实现,因为它们不是同一个函数,中间必有蹊跷,值得深挖。
ps:对于GPBCON和GPBDAT的赋值,当然有其他的安排方式,譬如都在write函数里实现,但似乎按本文安排更显逻辑性。
6.测试程序
ps:对于这个应用程序,当执行语句是“./app_test1”时会进入“if(argc != 2)...”语句,因为此时参数只有一个app_test1也算一个参数!