为什么学习驱动
写驱动是为了不调库,比如树莓派厂家提供了wiringPi库函数,集成了诸多函数,学习驱动就是为了在另一块板子上即使没有wiringPi我们只要拿到了原理图、芯片手册我们就能驱动IO等。
主设备号和次设备号
Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。为了管路这些设备,系统为设备编了号,每个设备又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如硬盘的主设备号是3。
一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和此设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的个设备。例如一个嵌入式系统,有两个LED指示灯,LED指示灯需要独立打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2.这里,次设备号就分别表示两个LED灯。
驱动实现过程
当用户层调用open,write等函数时 (例如:open(“/dev/pin4”, O_RDWR)),系统发生一次软中断,触发sys_call(系统调用处理入口 内核态)(中断号是0x80),接下来根据设备名找到设备号找到虚拟文件系统的sys_open,sys_open会找到引脚四驱动程序里的open函数,我们在引脚四里面肯定是进行寄存器操作。
如何编写驱动(GPIO)(最精简的方式)
1.在驱动中定义出寄存器
volatile unsigned int* GPFSEL0 = NULL; //volatile:防止编译器优化它
volatile unsigned int* GPSET0 = NULL; //先定义为空指针, 在pin4_drv_init();里面再将物
//理地址转化为虚拟地址,IO口寄存器映射成普通内存单元进行访问
volatile unsigned int* GPCLR0 = NULL;
int __init pin4_drv_init(void)
{
int ret;
devno = MKDEV(major,minor); //创建设备号
ret = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo");
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件
GPFSEL0 = volatile(unsigned int*)ioremap(0x3f200000,4);//物理地址转化为虚拟地址,IO口寄存器映射成普通内存单元进行访问
GPSET0 = volatile(unsigned int*)ioremap(0x3f20001C,4);
GPCLR0 = volatile(unsigned int*)ioremap(0x3f200028,4);
//我们在编写驱动程序的时候,IO口空间的起始地址是0x3f000000,加上
//GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的,
//然后在这个基础上进行linux系统的MMU内存虚拟化管理,映射到虚拟地址上。
return 0;
}
2.查看芯片手册,找到GPIO相关的寄存器
我们查手册,找到GPIO所对应的地方
GPIO Function Select Registers (GPFSELn)
(GPIO函数选择寄存器(GPFSELn))
GPIO有41个寄存器。所有的访问都假定是32位的。
功能寄存器:
(还有FSEL30~FSEL53,就不一一截图)
以FSEL9为例描述了要选择的口的功能
FSEL0~FSEL9为一族,FSEL10 ~ FSEL19为一族,以此类推
GPFSEL0-----GPIO Function Select 0 功能选择 输入/输出
GPSET0------ GPIO Pin Output Set 1 输出1
GPCLR0------ GPIO Pin Output Clear 0 清零
(我们驱动pin4为输出)
所以功能选择寄存器FSEL4,14-12对应为001
//对应内核驱动中pin4_open中GPFSEL0的配置
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
//配置pin4为输出引脚, bit12-14配置为001
*GPFSEL0 &= ~(0x6 << 12); //把bit13 bit14引脚配置为0
*GPFSEL0 |= (0x6 << 12); //把13 14引脚配置为0
return 0;
}
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
int usercmd;
printfk("pin4_write\n");
copy_from_user(&usercmd, buf, count); //获取上层write函数的值
printk("get value\n");
if (usercmd == 1){
*GPSET0 |= 0x1 << 4; //置一寄存器配置
}
else if (usercmd == 0){
*GPCLR0 |= 0x1 << 4;//清零寄存器配置
}
else {
printfk("undo\n");
}
printk("pin4_write\n");
return 0;
}