4.1 Linux 下 LED 灯驱动原理
Linux 下的任何外设驱动,最终都是要配置相应的硬件寄存器。所以本章的 LED 灯驱动
最终也是对 RK3588 的 IO 口进行配置,与裸机实验不同的是,在 Linux 下编写驱动要符合
Linux 的驱动框架。开发板上的 LED 连接到 RK3588 的 GPIO1_A3 这个引脚上,因此本章实验
的重点就是编写 Linux 下 RK3588 引脚控制驱动。
4.1.1 地址映射
在编写驱动之前,我们需要先简单了解一下 MMU 这个神器,MMU 全称叫做 Memory
Manage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU,但是现在
Linux 内核已经支持无 MMU 的处理器了。MMU 主要完成的功能如下:
①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
我们重点来看一下第①点,也就是虚拟空间到物理空间的映射,也叫做地址映射。首先了
解两个地址概念:虚拟地址(VA,Virtual Address)、物理地址(PA,Physcical Address)。对于 64
位的处理器来说,虚拟地址范围是 2^64=16EB(1EB=1024PB=1024*1024TB)。
虚拟地址: 就是针对系统调用按照系统位数虚拟的地址
物理地址: 就是实际的地址,包括SOC上和DDR上的地址
系统要访问物理地址需要通过虚拟地址映射到物理地址进行访问。
Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚
拟地址。RK3588 的 GPIO1_A3 引脚的 IO 复用寄存器 BUS_IOC_GPIO1A_IOMUX_SEL_L 物
理地址为 0xFD5F8020。如果没有开启 MMU 的话直接向 0xFD5F8020)这个寄存器地址写入数
据就可以配置 GPIO1_A3 的引脚的复用功能。现在开启了 MMU,并且设置了内存映射,因此
就不能直接向 0xFD5F8020 这个地址写入数据了。我们必须得到 0xFD5F8020 这个物理地址在
Linux 系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到
两个函数:ioremap 和 iounmap。
1、ioremap 函数
ioremap 函数用于获取指定物理地址空间对应的虚拟地址空间,定义在
arch/arm/include/asm/io.h 文件中,定义如下:
示例代码 4.1-1 ioremap 函数声明
431 void __iomem *ioremap(resource_size_t res_cookie, size_t size);
函数的实现是在 arch/arm/mm/ioremap.c 文件中,实现如下:
示例代码 4.1-2 ioremap 函数实现
376 void __iomem *ioremap(resource_size_t res_cookie, size_t size)
377 {
378 return arch_ioremap_caller(res_cookie, size, MT_DEVICE,
379 __builtin_return_address(0));
380 }
381 EXPORT_SYMBOL(ioremap);
ioremap 有两个参数:res_cookie 和 size,真正起作用的是函数 arch_ioremap_caller。
ioremap 函数有两个参数和一个返回值,这些参数和返回值的含义如下:
res_cookie:要映射的物理起始地址。
size:要映射的内存空间大小。
返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。
假如我们要获取 RK3588 的 BUS_IOC_GPIO1A_IOMUX_SEL_L 寄存器对应的虚拟地址,
使用如下代码即可:
#define BUS_IOC_GPIO1A_IOMUX_SEL_L (0xFD5F8020)
static void __iomem* BUS_IOC_GPIO1A_IOMUX_SEL_L_PI;
BUS_IOC_GPIO1A_IOMUX_SEL_L_PI = ioremap(BUS_IOC_GPIO1A_IOMUX_SEL_L, 4);
对于 RK3588 来说一个寄存器是4 字节(32 位),因此映射的内存长度为 4。映射完成以后直接对BUS_IOC_GPIO1A_IOMUX_SEL_L_PI 进行读写操作即可。
2、iounmap 函数
卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原
型如下:
示例代码 4.1-3 iounmap 函数原型
460 void iounmap (volatile void __iomem *addr)
iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。假如我们现
在要取消掉 BUS_IOC_GPIO1A_IOMUX_SEL_L_PI 寄存器的地址映射,使用如下代码即可:
iounmap(BUS_IOC_GPIO1A_IOMUX_SEL_L_PI);
4.1.2I/O 内存访问函数
使用 ioremap 函数将寄存器
的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不
建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。
1、读操作函数
读操作函数有如下几个:
示例代码 4.1-4 读操作函数
1 u8 readb(const volatile void __iomem *addr)
2 u16 readw(const volatile void __iomem *addr)
3 u32 readl(const volatile void __iomem *addr)
readb、readw 和 readl 这三个函数分别对应 8bit、16bit 和 32bit 读操作,参数 addr 就是要
读取写内存地址,返回值就是读取到的数据。
2、写操作函数
写操作函数有如下几个:
示例代码 4.1-5 写操作函数
1 void writeb(u8 value, volatile void __iomem *addr)
2 void writew(u16 value, volatile void __iomem *addr)
3 void writel(u32 value, volatile void __iomem *addr)
writeb、writew 和 writel 这三个函数分别对应 8bit、16bit 和 32bit 写操作,参数 value 是要
写入的数值,addr 是要写入的地址。
4.2 硬件原理图分析
图中R113是限流电阻,避免NPN三极管基极电流过大烧毁三极管,取值计算方法如下:
三极管进入饱和态:基极电流
假设 LED 电流
Ic ≈ 5 ~ 10mA,三极管 β ≈ 100。 则基极电流
𝐼𝑏≈0.1𝑚𝐴
假设 GPIO 输出高电平 3.3V,V_BE ≈ 0.7V:
实际使用中,通常会放大电流裕量,10kΩ 是比较常见的保守值,确保三极管能充分导通。
R115 的作用(下拉电阻):
作用:R115 是下拉电阻,用于在 GPIO 输出为高阻态(Hi-Z)或系统上电初始化期间,防止三极管误导通。
保证 Q4 基极电位默认为低电平,避免 LED 误亮。提高系统上电稳定性。
数值选择:
数值需远大于 R113,不能分流太多基极电流,一般选用几十 kΩ 到几百 kΩ。
51kΩ 是一个常见的折中选择。
三极管(以NPN型为例)具有以下三个工作状态:
-
截止区(Cutoff Region)
条件:基极-发射极电压
𝑉𝐵𝐸<0.7𝑉(未导通)
特征:基极电流
𝐼𝐵≈0,集电极电流 𝐼𝐶≈0
作用:三极管完全关闭,相当于开关断开 -
放大区(Active Region)
条件:
𝑉𝐵𝐸≈0.7𝑉,且 𝑉𝐶𝐸>𝑉𝐵𝐸
特征:三极管工作在线性放大状态,
𝐼𝐶≈𝛽⋅𝐼𝐵
作用:主要用于模拟信号放大,不是开关工作区
- 饱和区(Saturation Region)
条件:基极电流足够大,使得
𝑉𝐶𝐸≈0.2𝑉
特征:三极管“完全导通”,
𝐼𝐶不再严格依赖
𝐼𝐵
作用:相当于导通的开关,集电极与发射极接近短路
4.3 RK3588 GPIO 驱动原理讲解
4.3.1 引脚复用设置
RK3588 的一个引脚一般用多个功能,也就是引脚复用,比如 GPIO1_A3 这个 IO 就可以
用作:GPIO,HDMI_TX1_SDA_M2、SPI4_CS0_M2、I2C4_SCL_M3、UART6_CTSN_M1、
PWM1_M2 这六个功能,所以我们首先要设置好当前引脚用作什么功能,这里我们要使用
GPIO1_A3 的 GPIO 功能。
打开《Rockchip RK3588 TRM V1.0-Part1-20220309(RK3588 参考手册 1).pdf》这份文
档,找到 BUS_IOC_GPIO1A_IOMUX_SEL_L 这个寄存器,寄存器描述如下图所示:
BUS_IOC_GPIO1A_IOMUX_SEL_L 寄存器地址为:base+offset,其中 base 就是 PMU_GRF 外设的基地址,为 0xFD5F8000,offset 为 0x0020,所以BUS_IOC_GPIO1A_IOMUX_SEL_L 寄存器地址为 0xFD5F8000 + 0x0020 = 0xFD5F8020。
BUS_IOC_GPIO1A_IOMUX_SEL_L 寄存器分为 2 部分:
① 、bit31:16:低 16 位写使能位,这 16 个 bit 控制着寄存器的低 16 位写使能。比如 bit16
就对应着 bit0 的写使能,如要要写 bit0,那么 bit16 要置 1,也就是允许对 bit0 进行写
操作。
② 、bit15:0:功能设置位。
可以看出,BUS_IOC_GPIO1A_IOMUX_SEL_L 寄存器用于设置 GPIO1_A0~A3 这 4 个 IO
的复用功能,其中 bit15:12 用于设置 GPIO1_A3 的复用功能,有六个可选功能:
0:GPIO0_C0
5:HDMI_TX1_SDA_M2
8:SPI4_CS0_M2
9:I2C4_SCL_M3
a:UART6_CTSN_M1
b:PWM1_M2
我们要将 GPIO1_A3 设置为 GPIO,所以 BUS_IOC_GPIO1A_IOMUX_SEL_L 的 bit15:12
这四位设置 0000。另外 bit31:28 要设置为 1111,允许写 bit15:12
4.3.2引脚驱动能力设置
RK3588 的 IO 引脚