0. 引言
最近一直在做点灯实验,做了各种各样的点灯,感觉其实也是一个循序渐进的过程,通过点灯这么一个小小的工程,一步步的套进linux的各种框架中。
所以萌生了一个比较各个工程的点灯区别和联系的想法。 这个学习步骤应该是各家教程都公用的一个流程。
原意是想把相关代码和流程全部附上来。后来发现有些繁琐,而且没有意义,因为就是把《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.4.pdf》教程上的一些流程搬了上来,干脆就不写这些具体的了,只记录一下自己学习这几部分的整理的一个异同点吧。
需要源码的可以去这本书里找。链接放在文章开头了,0积分下载。
主要就是要分清,“设备” 和 “驱动” 是两个东西。要分清楚。
另外还有“总线”的概念。
设备是资源。
驱动是使用资源的方法。
总线是匹配两者的机制。
重点看:2.4 、2.7
吐槽下:
NXP这个引脚命名真的绕,习惯了PA0,PA1这种的,看NXP这种绕来绕去晕,还有寄存器名字。
NXP自己的图形配置软件也是用的PA0/PA1这种,为啥datasheet引脚命名要这么绕。
1. 点灯基础知识
1.1 硬件电路
本文硬件使用的是正点原子 IMX6UL Alpha 阿尔法开发板。
使用的引脚是GPIO_3,对应datasheet上的GPIO1_IO03
当输出低电平时,LED亮。
当输出高电平时,LED灭。
1.2 涉及IMX6ULL的寄存器
配置引脚主要是配置:
- 时钟使能(为了省电,平时不用的外设时钟都是关的)
- 功能选择(配置为GPIO功能,而不是uart、spi等功能)
- 方向配置(输入还是输出)
- 设置值(高电平还是低电平)
涉及到的寄存器有:
分两类,配置IO的寄存器和配置GPIO的寄存器。
配置IO的寄存器是MUX选择功能和PAD选择驱动能力等,
配置GPIO的寄存器一共有八个:DR、 GDIR、 PSR、 ICR1、 ICR2、 EDGE_SEL、 IMR 和 ISR。
另外还有打开时钟的寄存器CCM_CCGR1.
- 配置时钟,CCM_CCGR1。
- 配置 IO,IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 ( 20E_0068h) 。
进行这个引脚的功能选择ALT。虽然这个引脚名为GPIO1_IO03,但是实际可以选为很多功能。 - 配置IO,IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 (20E_02F4h)。
进行上下拉,速率,驱动能力,摆压率 等功能的选择。 - 配置GPIO,DR获取GPIO的输入电平值 和 控制 输出电平值。
- 配置GPIO,GDIR控制方向(输入输出)。
- 配置GPIO,PSR获取GPIO的输入电平值。
- 配置GPIO ,ICR1和ICR2都是配置中断的。
- 配置GPIO ,IMR 是配置中断屏蔽的。
- 配置GPIO,ISR是中断状态寄存器
2. 点灯的进阶之路
先放结论:
版本 | 变化 |
---|---|
裸核汇编点灯 | 直接汇编配置寄存器 裸机程序 |
裸核C语言点灯 | 直接C语言配置寄存器 裸机程序 |
linux内核点灯 (老字符设备接口) | 在linux内核框架下 直接C语言配置寄存器 |
linux内核点灯 (新字符设备接口) | 在linux内核框架下 直接C语言配置寄存器,使用更灵活的接口注册驱动 |
资源分离点灯 (左右分离) | 实现了 设备 和 驱动的分离 |
设备树点灯 | 实现了利用单独文件来描述设备,把设备从内核源码中剥离 |
platform点灯 | 2.6之后引入的一种 用来匹配 设备和驱动的机制 |
设备树platform点灯 | 实现了利用单独文件来描述设备,把设备从内核源码中剥离,然后利用platform总线自动匹配 |
pinctrl & GPIO子系统 | 原厂官方给的操作GPIO的接口 |
2.1 裸核汇编点灯
对应《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.4.pdf》的 第八章。
这个没什么说的,直接找到要操作的几个寄存器,查找寄存器说明,直接把需要的值配置到对应的寄存器里即可。
2.2 裸核C点灯
对应《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.4.pdf》的 第十章。
和汇编点灯很像,要注意的就是前面要写汇编的启动代码,准备C语言运行环境(堆栈),然后再跳转C。
2.3 linux内核点灯(老字符设备接口)
对应《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.4.pdf》的 第四十一章。
在驱动文件中直接定义所需要的寄存器地址,然后ioremap过来使用。
在驱动文件中,通过register_chrdev注册驱动。
这种情况下,可以在/proc/devices下看到这个驱动,但是不会生成设备节点,还需要mknod来生成设备节点。
register_chrdev 是比较老的注册函数,需要指明主设备号。
2.4 linux内核点灯(新字符设备接口)
对应《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.4.pdf》的 第四十二章。
和1.3类似,只是注册设备的接口变了。
这一章要好好理解一下。
同样的,定义需要的寄存器地址,然后ioremap过来。
在注册驱动时,使用register_chrdev_region、alloc_chrdev_region、cdev_init、cdev_add、class_create、device_create 接口来注册。比上一节单纯使用register_chrdev多了不少接口。
register_chrdev_region 和 上一节的 register_chrdev 很像,都是静态申请设备号。
alloc_chrdev_region 是动态申请。
那为什么,换个动态申请就解决的事,要搞这么多新的接口出来呢?每个接口是做什么的呢?
因为register_chrdev 接口其实内部已经把cdev_init、cdev_add这些集成了。
新的接口 register_chrdev_region 和 alloc_chrdev_region 其实相当于把 register_chrdev又细分了。
register_chrdev_region + cdev_init + cdev_add ,就等于之前的 register_chrdev。
我们对比看一下这次实验和上一个实验的代码区别。
我们再看下register_chrdev的实现,可以看出,他内部集成了 cdev_add 等操作,所以很简洁,同样,也很不灵活。
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}
2.5 资源分离点灯(左右分离)
这一部分在书中没有单独涉及,在韦东山的课程中有提过,作为设备树之前的课程。
之前写驱动,都是在驱动里定好要用的寄存器,直接ioremap过来用,其实这个耦合度很高。一个好的驱动,要适应各种各样的开发板和硬件方法,肯定不是这样的用的。
就涉及到了设备资源 和 驱动的分离。
最早的时候,是把这些用到的资源(eg:寄存器,中断 等)都放在另外一个.c中描述,驱动中获取资源,只负责资源的使用,实现左右的分离。
如果修改硬件,则去修改或添加这些.c里的资源即可。
后来板子太多,另加的.c太多,lonus发火了,全面引入设备树,把这些资源描述代码统统从内核中剥离开,作为一个配置文件加载,这样保持linux代码的优雅。
2.6 设备树点灯(左右分离)
对应《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.4.pdf》的 第四十四章。
这一章就是把LED使用到的资源(寄存器)都写在设备树文件中,内核是通过设备树来获取要资源,进而进行驱动。
使用的接口有,of_find_node_by_path,of_find_property,of_property_read_string,of_property_read_u32_array 等。
使用起来类似:
dtsled.nd = of_find_node_by_path("/alphaled");
通过一个明确的结点名称,去找到设备相关的资源来使用。
匹配的机制就是去找一个固定的结点名称。
这里我们引入了,除了 驱动 和 设备 之外的第三个概念:匹配。
怎么样把驱动和设备很好的匹配起来?
Linux官方给出的答案是platform总线。
2.7 platform点灯(Linux内核框架)
对应《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.4.pdf》的 第五十四章。
我理解paltform总线的提出,就是为了解决一些驱动和设备进行匹配的问题,platform是针对那些映射在内存空间的设备而提出的虚拟总线。
这一章也很重要,是最常用的一类设备驱动模板。
注意,platform设备 和 我们学过的字符设备、块设备、网络设备,不是同一种分类标准。
一个设备,可以是字符设备,同时也是platform设备。
这一节就是引入了总线匹配的机制,
设备资源描述好之后,使用 platform_device_register注册,
驱动使用 platform_driver_register 注册,
都挂在platform虚拟总线上,匹配的工作交给总线。如果匹配上了,则运行驱动的probe函数,类似之前的init函数。
2.8 设备树的platform点灯
这个和前面引入设备树的原因类似,设备不再使用单独的 .c文件来描述,设备也不再使用platform_device_register注册,而是通过写在设备树文件中,系统启动时,自动地去根据设备树文件去注册这些设备,进而进行驱动的匹配。
2.9 pinctrl点灯
对应《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.4.pdf》的 第四十五章。
这一部分和驱动的整体框架关系不大,主要是针对IO操作的。
因为IO的操作其实很固定,所以linux官方帮忙写了一个现成的IO操作框架,然后各个芯片厂家都会去根据自己的芯片来适配这个框架,我们直接用即可。这个就是pinctrl和gpio的作用。
文章开头我们去整理了驱动IMX6ULL的GPIO所需要的的寄存器,其实可以跳过这一步,我们查看datasheet也不会比厂家的驱动工程师更熟,由他们完成的驱动,更加完善一些。
3. 总结
这篇文章没有写实验细节,只是个人把这堆实验做完一轮的一些理解和感触。我会尽量地把相关的东西都先列进来,这样有个大概的印象,不然看到这么多的LED实验也会有些困惑。