“新字符设备的GPIO驱动”和“设备树下的GPIO驱动”都要用到寄存器地址,使用“物理内存”和“虚拟内存”映射时,非常不方便,而pinctrl和gpio子系统的GPIO驱动,非常简化。因此,要重点学习pinctrl和gpio子系统下的GPIO驱动开发。但是“设备树下的GPIO驱动”也要去学习,因为“设备树下的GPIO驱动”重点是学习“OF函数”。最好三个都要去认真学习,有助于加强和巩固学习Linux驱动框架。
1、了解“虚拟内存”
STM32MP157是32位的处理器,因此它的“虚拟地址”范围是2^32=4GB,这就是说它有4GB的虚拟空间,或者说它有4GB的虚拟内存。
2、了解“物理内存”
STM32MP157开发板上的DDR3为1GB,这个1GB的内存就是“物理内存”。
3、了解“MMU内存管理单元”
MMU是Memory Manage Unit的缩写,意思是“内存管理单元”。老版本的Linux内核要求处理器必须有“MMU内存管理单元”,而新版本的Linux内核不再要求处理器具有“MMU内存管理单元”了,因此,需要我们完成“虚拟内存”到“物理内存”之间的映射。
4、内存映射
5、“新字符设备中的GPIO驱动”
在“新字符设备驱动”中,“物理内存”和“虚拟内存”可以通过ioremap()和iounmap()进行转换。
1)、ioremap()函数
void __iomem *ioremap(resource_size_t res_cookie, size_t size);
函数功能:将“物理内存中首地址为res_cookie”映射到虚拟内存中的“首地址”,向“虚拟内存”申请一块内存,用作“虚拟地址”映射。
res_cookie:“物理内存”中的起始地址;
size:“物理内存中首地址为res_cookie”的空间大小;
返回值:映射到“虚拟内存中的首地址”;
ioremap()函数定义在arch/arm/include/asm/io.h文件中。
2)、iounmap()函数
void iounmap (volatile void __iomem *addr);
函数功能:释放掉“虚拟内存中的首地址为addr的数据块”,即取消ioremap()函数所做的“物理地址”映射;
3)、读操作函数:
u8 readb(const volatile void __iomem *addr)
从“虚拟地址”为addr中读取1个字节;
u16 readw(const volatile void __iomem *addr)
从“虚拟地址”为addr中读取2个字节;
u32 readl(const volatile void __iomem *addr)
从“虚拟地址”为addr中读取4个字节;
4)、写操作函数:
void writeb(u8 value, volatile void __iomem *addr)
将value的值(单字节)写入到“虚拟地址”为addr的存储单元中;
void writew(u16 value, volatile void __iomem *addr)
将value的值(双字节)写入到“虚拟地址”为addr的存储单元中;
void writel(u32 value, volatile void __iomem *addr)
将value的值(4字节)写入到“虚拟地址”为addr的存储单元中;
5)、“新字符设备中的GPIO驱动”内存映射与读写操作的应用
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/*寄存器物理地址*/
#define PERIPH_BASE (0x40000000)
#define MPU_AHB4_PERIPH_BASE (PERIPH_BASE + 0x10000000)
#define RCC_BASE (MPU_AHB4_PERIPH_BASE + 0x0000)
#define RCC_MP_AHB4ENSETR (RCC_BASE + 0XA28)
#define GPIOI_BASE (MPU_AHB4_PERIPH_BASE + 0xA000)
#define GPIOI_MODER (GPIOI_BASE + 0x0000)
#define GPIOI_OTYPER (GPIOI_BASE + 0x0004)
#define GPIOI_OSPEEDR (GPIOI_BASE + 0x0008)
#define GPIOI_PUPDR (GPIOI_BASE + 0x000C)
#define GPIOI_BSRR (GPIOI_BASE + 0x0018)
/*映射后的寄存器虚拟地址指针*/
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
/*RCC_MP_AHB4ENSETR寄存器*/
static void __iomem *GPIOI_MODER_PI; /*GPIOx_MODER寄存器,x=A to K, Z*/
static void __iomem *GPIOI_OTYPER_PI;/*GPIOx_OTYPER,x=A to K,Z*/
static void __iomem *GPIOI_OSPEEDR_PI;/*GPIOx_OSPEEDR,x=A to K, Z*/
static void __iomem *GPIOI_PUPDR_PI; /*GPIOx_PUPDR,x=A to K, Z*/
static void __iomem *GPIOI_BSRR_PI;/*GPIOx_BSRR,x=A to K, Z*/
/*寄存器地址映射*/
static void led_ioremap(void)
{
MPU_AHB4_PERIPH_RCC_PI = ioremap(RCC_MP_AHB4ENSETR, 4);
//向“虚拟内存”申请一块内存,长度为4个字节,用作“物理地址为RCC_MP_AHB4ENSETR”的映射。
GPIOI_MODER_PI = ioremap(GPIOI_MODER, 4);
//向“虚拟内存”申请一块内存,长度为4个字节,用作“物理地址为GPIOI_MODER”的映射。
GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER, 4);
//向“虚拟内存”申请一块内存,长度为4个字节,用作“物理地址为GPIOI_OTYPER”的映射。
GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR, 4);
//向“虚拟内存”申请一块内存,长度为4个字节,用作“物理地址为GPIOI_OSPEEDR”的映射。
GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR, 4);
//向“虚拟内存”申请一块内存,长度为4个字节,用作“物理地址为GPIOI_PUPDR”的映射。
GPIOI_BSRR_PI = ioremap(GPIOI_BSRR, 4);
//向“虚拟内存”申请一块内存,长度为4个字节,用作“物理地址为GPIOI_BSRR”的映射。
}
/*取消“寄存器地址映射”*/
static void led_iounmap(void)
{
iounmap(MPU_AHB4_PERIPH_RCC_PI);
//释放掉“虚拟内存中的首地址为MPU_AHB4_PERIPH_RCC_PI的数据块”,即取消“物理地址”映射;
iounmap(GPIOI_MODER_PI);
//释放掉“虚拟内存中的首地址为GPIOI_MODER_PI的数据块”,即取消“物理地址”映射;
iounmap(GPIOI_OTYPER_PI);
//释放掉“虚拟内存中的首地址为GPIOI_OTYPER_PI的数据块”,即取消“物理地址”映射;
iounmap(GPIOI_OSPEEDR_PI);
//释放掉“虚拟内存中的首地址为GPIOI_OSPEEDR_PI的数据块”,即取消“物理地址”映射;
iounmap(GPIOI_PUPDR_PI);
//释放掉“虚拟内存中的首地址为GPIOI_PUPDR_PI的数据块”,即取消“物理地址”映射;
iounmap(GPIOI_BSRR_PI);
//释放掉“虚拟内存中的首地址为GPIOI_BSRR_PI的数据块”,即取消“物理地址”映射;
}
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIOI_BSRR_PI);
/*从“虚拟地址”为GPIOI_BSRR_PI中读取1个字节,即相当于读GPIOI_BSRR寄存器*/
val &= ~(0X1 << 16); /* bit16 清零*/
val |= (0x1 << 16); /*bit16 设置为1,令PI0输出低电平*/
writel(val, GPIOI_BSRR_PI);
/* 将val的值(单字节)写入到“虚拟地址”为GPIOI_BSRR_PI的存储单元中,即相当于将val的值写入GPIOI_BSRR寄存器 */
}
else if(sta == LEDOFF) {
val = readl(GPIOI_BSRR_PI);
/*从“虚拟地址”为GPIOI_BSRR_PI中读取1个字节,即相当于读读GPIOI_BSRR寄存器*/
val &= ~(0X1 << 0); /* bit0 清零*/
val |= (0x1 << 0);/*bit0 设置为1,令PI0输出高电平*/
writel(val, GPIOI_BSRR_PI);
/* 将val的值(单字节)写入到“虚拟地址”为GPIOI_BSRR_PI的存储单元中,即相当于将val的值写入GPIOI_BSRR寄存器 */
}
}
6、设备树下的的GPIO驱动
6.1、OF函数
1)、inline struct device_node *of_find_node_by_path(const char *path)
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径。
返回值:返回找到的节点,如果为NULL,表示查找失败。
2)、void __iomem *of_iomap(struct device_node *np, int index)
np:设备节点。
index:reg属性中要完成内存映射的段,如果reg属性只有一段的话,则index=0。
返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败。
6.2、“设备树下的的GPIO驱动”的内存映射与读写操作
1)、打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“atk-mp1”,点击“linux”,点击“my_linux”,点击“linux-5.4.31”,点击“确定”,见下图:
2)、点击“转到”,点击“转到文件”,输入“stm32mp157d-atk.dts回车”,打开设备树文件stm32mp157d-atk.dts。
3)、在根节点“/”下创建一个名为“stm32mpl_1ed”的子节点,添加内容如下:
stm32mp1_led {
compatible = "atkstm32mp1-led";
/*设置属性compatible的值为"atkstm32mp1-led"*/
status = "okay";/*设置属性status的值为"okay"*/
reg = <0X50000A28 0X04 /* RCC_MP_AHB4ENSETR */
0X5000A000 0X04 /* GPIOI_MODER */
0X5000A004 0X04 /* GPIOI_OTYPER */
0X5000A008 0X04 /* GPIOI_OSPEEDR */
0X5000A00C 0X04 /* GPIOI_PUPDR */
0X5000A018 0X04 >; /* GPIOI_BSRR */
/*设置属性reg的值为0X50000A28 0X04 0X5000A000 0X04 0X5000A004 0X04 0X5000A008 0X04 0X5000A00C 0X04 0X5000A018 0X04*/
};
见下图:
4)、编译设备树
①在VSCode终端,输入“make dtbs回车”,执行编译设备树
②输入“ls arch/arm/boot/uImage -l”
查看是否生成了新的“uImage”文件
③输入“ls arch/arm/boot/dts/stm32mp157d-atk.dtb -l”
查看是否生成了新的“stm32mp157d-atk.dtb”文件
5)、拷贝输出的文件:
①输入“cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC;
②输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC;
③输入“cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;
④输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;
⑤输入“ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回车”,查看“/home/zgq/linux/atk-mp1/linux/bootfs/”目录下的所有文件和文件夹;
⑥输入“ls -l /home/zgq/linux/tftpboot/回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹;
⑦输入“chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回车”
给“stm32mp157d-atk.dtb”文件赋予可执行权限;
⑧输入“chmod 777 /home/zgq/linux/tftpboot/uImage回车” ,给“uImage”文件赋予可执行权限;
⑨输入“ls /home/zgq/linux/tftpboot/回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹;
6)、“设备树下的的GPIO驱动”的内存映射与读写操作举例
#include <linux/types.h>
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include <linux/cdev.h> //使能cdev结构
#include <linux/device.h>//使能class结构和device结构
#include <linux/of.h> //使能device_node结构
#include <linux/of_address.h> //使能of_iomap()
struct MyDtsLED_dev{
dev_t devid; /*声明32位变量devid用来给保存设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /*字符设备结构变量cdev */
struct class *class; /* 类 */
struct device *device;/*设备*/
struct device_node *nd;/* 设备节点 */
};
struct MyDtsLED_dev strMyDtsLED;
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
/*RCC_MP_AHB4ENSETR寄存器*/
static void __iomem *GPIOI_MODER_PI; /*GPIOx_MODER寄存器,x=A to K, Z*/
static void __iomem *GPIOI_OTYPER_PI;/*GPIOx_OTYPER,x=A to K,Z*/
static void __iomem *GPIOI_OSPEEDR_PI;/*GPIOx_OSPEEDR,x=A to K, Z*/
static void __iomem *GPIOI_PUPDR_PI; /*GPIOx_PUPDR,x=A to K, Z*/
static void __iomem *GPIOI_BSRR_PI;/*GPIOx_BSRR,x=A to K, Z*/
/* 寄存器地址映射 */
int led_ioremap(void)
{
int ret;
u32 regdata[12];
const char *str;
struct property *proper;
strMyDtsLED.nd = of_find_node_by_path("/stm32mp1_led");
//获取设备节点
//通过全路径“/stm32mp1_led”来查找stm32mp1_led节点
//“/stm32mp1_led”就是stm32mp1_led这个节点的全路径。
//返回值:返回找到的节点,如果为NULL,表示查找失败。
if(strMyDtsLED.nd == NULL) return -EINVAL;
MPU_AHB4_PERIPH_RCC_PI = of_iomap(strMyDtsLED.nd, 0);
//将“reg属性的第0段地址信息0X50000A28”转换为“虚拟地址”
//np=strMyDtsLED.nd,指定设备节点
//index=0表示读取reg属性的第0段
//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;
GPIOI_MODER_PI = of_iomap(strMyDtsLED.nd, 1);
//将“reg属性的第1段地址信息0X5000A000”转换为“虚拟地址”
//np=strMyDtsLED.nd,指定设备节点
//index=1表示读取reg属性的第1段
//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;
GPIOI_OTYPER_PI = of_iomap(strMyDtsLED.nd, 2);
//将“reg属性的第2段地址信息0X5000A004”转换为“虚拟地址”
//np=strMyDtsLED.nd,指定设备节点
//index=2表示读取reg属性的第2段
//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;
GPIOI_OSPEEDR_PI = of_iomap(strMyDtsLED.nd, 3);
//将“reg属性的第3段地址信息0X5000A008”转换为“虚拟地址”
//np=strMyDtsLED.nd,指定设备节点
//index=3表示读取reg属性的第3段
//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;
GPIOI_PUPDR_PI = of_iomap(strMyDtsLED.nd, 4);
//将“reg属性的第4段地址信息0X5000A00C”转换为“虚拟地址”
//np=strMyDtsLED.nd,指定设备节点
//index=4表示读取reg属性的第4段
//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;
GPIOI_BSRR_PI = of_iomap(strMyDtsLED.nd, 5);
//将“reg属性的第5段地址信息0X5000A018”转换为“虚拟地址”
//np=strMyDtsLED.nd,指定设备节点
//index=5表示读取reg属性的第5段
//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;
return 0;
}
/*取消“寄存器地址映射”*/
static void led_iounmap(void)
{
iounmap(MPU_AHB4_PERIPH_RCC_PI);
//释放掉“虚拟内存中的首地址为MPU_AHB4_PERIPH_RCC_PI的数据块”,即取消“物理地址”映射;
iounmap(GPIOI_MODER_PI);
//释放掉“虚拟内存中的首地址为GPIOI_MODER_PI的数据块”,即取消“物理地址”映射;
iounmap(GPIOI_OTYPER_PI);
//释放掉“虚拟内存中的首地址为GPIOI_OTYPER_PI的数据块”,即取消“物理地址”映射;
iounmap(GPIOI_OSPEEDR_PI);
//释放掉“虚拟内存中的首地址为GPIOI_OSPEEDR_PI的数据块”,即取消“物理地址”映射;
iounmap(GPIOI_PUPDR_PI);
//释放掉“虚拟内存中的首地址为GPIOI_PUPDR_PI的数据块”,即取消“物理地址”映射;
iounmap(GPIOI_BSRR_PI);
//释放掉“虚拟内存中的首地址为GPIOI_BSRR_PI的数据块”,即取消“物理地址”映射;
}
//引脚初始化
void led_Pin_Init(void)
{
u32 val = 0;
val = readl(MPU_AHB4_PERIPH_RCC_PI);
/*从“虚拟地址”为MPU_AHB4_PERIPH_RCC_PI中读取4个字节,即相当于读RCC_MP_AHB4ENSETR寄存器*/
val &= ~(0X1 << 8);val |= (0X1 << 8);/* 设置bit8=1 */
writel(val, MPU_AHB4_PERIPH_RCC_PI);
/* 将val的值(4字节)写入到“虚拟地址”为MPU_AHB4_PERIPH_RCC_PI的存储单元中,即相当于将val的值写入RCC_MP_AHB4ENSETR寄存器 */
val = readl(GPIOI_MODER_PI);/*读GPIOI_MODER寄存器*/
/*从“虚拟地址”为GPIOI_MODER_PI中读取4个字节,即相当于读GPIOI_MODER寄存器*/
val &= ~(0X3 << 0);val |= (0X1 << 0);/* bit0:1设置01,配置为输出模式 */
writel(val, GPIOI_MODER_PI);
/* 将val的值(4字节)写入到“虚拟地址”为GPIOI_MODER_PI的存储单元中,即相当于将val的值写入GPIOI_MODER寄存器 */
val = readl(GPIOI_OTYPER_PI);/*读GPIOI_OTYPER寄存器*/
/*从“虚拟地址”为GPIOI_OTYPER_PI中读取4个字节,即相当于读GPIOI_OTYPER寄存器*/
val &= ~(0X1 << 0); /* bit0清零,设置PI0为推挽模式*/
writel(val, GPIOI_OTYPER_PI);
/* 将val的值(4字节)写入到“虚拟地址”为GPIOI_OTYPER_PI的存储单元中,即相当于将val的值写入GPIOI_OTYPER寄存器 */
val = readl(GPIOI_OSPEEDR_PI);/*读GPIOI_OSPEEDR寄存器*/
/*从“虚拟地址”为GPIOI_OSPEEDR_PI中读取4个字节,即相当于读GPIOI_OSPEEDR寄存器*/
val &= ~(0X3 << 0); val |= (0x3 << 0);/* bit0:1 设置为11,极高速*/
writel(val, GPIOI_OSPEEDR_PI);
/* 将val的值(4字节)写入到“虚拟地址”为GPIOI_OSPEEDR_PI的存储单元中,即相当于将val的值写入GPIOI_OSPEEDR寄存器 */
val = readl(GPIOI_PUPDR_PI);/*读GPIOI_PUPDR寄存器*/
/*从“虚拟地址”为GPIOI_PUPDR_PI中读取4个字节,即相当于读GPIOI_PUPDR寄存器*/
val &= ~(0X3 << 0);val |= (0x1 << 0); /*bit0:1 设置为01,配置为上拉*/
writel(val,GPIOI_PUPDR_PI);
/* 将val的值(4字节)写入到“虚拟地址”为GPIOI_PUPDR_PI的存储单元中,即相当于将val的值写入GPIOI_PUPDR寄存器 */
val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/
/*从“虚拟地址”为GPIOI_BSRR_PI中读取4个字节,即相当于读GPIOI_BSRR寄存器*/
val &= ~(0X1 << 16);val |= (0x1 << 16);/*bit16 设置为1,令PI0输出低电平*/
writel(val, GPIOI_BSRR_PI);
/* 将val的值(4字节)写入到“虚拟地址”为GPIOI_BSRR_PI的存储单元中,即相当于将val的值写入GPIOI_BSRR寄存器 */
val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/
/*从“虚拟地址”为GPIOI_BSRR_PI中读取4个字节,即相当于读GPIOI_BSRR寄存器*/
val &= ~(0X1 << 0);val |= (0x1 << 0); /*bit0 设置为1,令PI0输出高电平*/
writel(val, GPIOI_BSRR_PI);
/* 将val的值(4字节)写入到“虚拟地址”为GPIOI_BSRR_PI的存储单元中,即相当于将val的值写入GPIOI_BSRR寄存器 */
}
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIOI_BSRR_PI);
/*从“虚拟地址”为GPIOI_BSRR_PI中读取4个字节,即相当于读GPIOI_BSRR寄存器*/
val &= ~(0X1 << 16); val |= (0x1 << 16);/*bit16 设置为1,令PI0输出低电平*/
writel(val, GPIOI_BSRR_PI);
/* 将val的值(4字节)写入到“虚拟地址”为GPIOI_BSRR_PI的存储单元中,即相当于将val的值写入GPIOI_BSRR寄存器 */
}
else if(sta == LEDOFF) {
val = readl(GPIOI_BSRR_PI);
/*从“虚拟地址”为GPIOI_BSRR_PI中读取4个字节,即相当于读GPIOI_BSRR寄存器*/
val &= ~(0X1 << 0);val |= (0x1 << 0); /*bit0 设置为1,令PI0输出高电平*/
writel(val, GPIOI_BSRR_PI);
/* 将val的值(4字节)写入到“虚拟地址”为GPIOI_BSRR_PI的存储单元中,即相当于将val的值写入GPIOI_BSRR寄存器 */
}
}
7、pinctrl和gpio子系统下的GPIO驱动
“新字符设备的GPIO驱动”和“设备树下的GPIO驱动”都要用到寄存器地址,使用非常不方便。于是Limux内核提供了pinctrl和gpio子系统,用于GPIO驱动开发,借助它可简化GPIO驱动开发。
7.1、pinctrl和gpio子系统下的函数
1)、inline struct device_node *of_find_node_by_path(const char *path)
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径。
返回值:返回找到的节点,如果为NULL,表示查找失败。
2)、int of_get_named_gpio(struct device_node *np, const char *propname, int index)
//根据给定的“设备节点”,读取GPIO编号
np:指定的“设备节点”。
propname:包含要获取GPIO信息的属性名;
Index:GPIO索引,因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0;
返回值:正值,获取到的GPIO编号;负值,失败。
需要包含头文件#include <linux/gpio.h>
3)、申请“gpio编号”
int gpio_request(unsigned gpio, const char *label)
gpio:要申请的“gpio编号”
Iabel:给这个gpio引脚设置个名字为label所指向的字符串
返回值:0,申请“gpio编号”成功;其他值,申请“gpio编号”失败;
注意:GPIOA有16个引脚,因此GA0的“gpio编号”为0,GA15的“gpio编号”为15;GPIOB有16个引脚,因此GB0的“gpio编号”为16,GB15的“gpio编号”为31;GPIOC有16个引脚,因此GC0的“gpio编号”为32,GC15的“gpio编号”为47;等等以此类推;
4)、释放“gpio编号”
void gpio_free(unsigned gpio)
gpio:要释放的“gpio编号”
7.2、pinctrl和gpio子系统下的GPIO驱动的内存映射与读写操
1)、打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“atk-mp1”,点击“linux”,点击“my_linux”,点击“linux-5.4.31”,点击“确定”,见下图:
2)、点击“转到”,点击“转到文件”,输入“stm32mp157d-atk.dts回车”,打开设备树文件stm32mp157d-atk.dts。
3)、在根节点“/”下创建一个名为“stm32mpl_1ed”的子节点,添加内容如下:
gpio_led {
compatible = "zgq,led";
status = "okay";
led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;
};
4)、编译设备树
①在VSCode终端,输入“make dtbs回车”,执行编译设备树
②输入“ls arch/arm/boot/uImage -l”
查看是否生成了新的“uImage”文件
③输入“ls arch/arm/boot/dts/stm32mp157d-atk.dtb -l”
查看是否生成了新的“stm32mp157d-atk.dtb”文件
5)、拷贝输出的文件:
①输入“cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC;
②输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC
③输入“cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;
④输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;
⑤输入“ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回车”,查看“/home/zgq/linux/atk-mp1/linux/bootfs/”目录下的所有文件和文件夹
⑥输入“ls -l /home/zgq/linux/tftpboot/回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹
⑦输入“chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回车”
给“stm32mp157d-atk.dtb”文件赋予可执行权限
⑧输入“chmod 777 /home/zgq/linux/tftpboot/uImage回车” ,给“uImage”文件赋予可执行权限
⑨输入“ls /home/zgq/linux/tftpboot/回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹
5)、pinctrl和gpio子系统下的GPIO驱动的内存映射与读写操举例
#include <linux/types.h>
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include <linux/cdev.h> //使能cdev结构
#include <linux/cdev.h> //使能class结构和device结构
#include <linux/of.h> //使能device_node结构
#include <linux/of_gpio.h>
//使能of_gpio_named_count(),of_gpio_count(),of_get_named_gpio()
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
struct MyGpioLED_dev{
dev_t devid; /*声明32位变量devid用来给保存设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /*字符设备结构变量cdev */
struct class *class; /* 类 */
struct device *device;/*设备*/
struct device_node *nd;/* 设备节点 */
int led_gpio; /* led所使用的GPIO编号 */
};
struct MyGpioLED_dev strMyGpioLED;
int Get_gpio_num(void)
{
int ret = 0;
const char *str;
strMyGpioLED.nd = of_find_node_by_path("/gpio_led");
//获取设备节点:strMyGpioLED
//path="/gpio_led,使用“全路径的节点名“在“stm32mp157d-atk.dts“中查找节点“gpio_led”
//返回值:返回找到的节点,如果为NULL,表示查找失败。
if(strMyGpioLED.nd == NULL) return -EINVAL;
strMyGpioLED.led_gpio = of_get_named_gpio(strMyGpioLED.nd, "led-gpio", 0);
//获取设备树中的gpio属性,得到LED所使用的LED编号
//在gpio_led节点中,led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>
//np=strMyGpioLED.nd,指定的“设备节点”
//propname="led-gpio",给定要读取的属性名字
//Index=0,给定的GPIO索引为0
//返回值:正值,获取到的GPIO编号;负值,失败。
if(strMyGpioLED.led_gpio < 0) return -EINVAL;
printk("led-gpio num = %d\r\n", strMyGpioLED.led_gpio);
//打印结果为:“led-gpio num = 128“
//因为GPIO编号是从0开始的,GPIOI端口的序号是8,每个端口有16个IO口,因此GPIOI0的编号为8*16=128
return 0;
}
int led_GPIO_request(void)
{
int ret = 0;
ret = gpio_request(strMyGpioLED.led_gpio, "LED-GPIO");
//向gpio子系统申请使用“gpio编号”
//gpio=strMyGpioLED.led_gpio,指定要申请的“gpio编号”
//Label="LED-GPIO",给这个gpio引脚设置个名字为"LED-GPIO"
//返回值:0,申请“gpio编号”成功;其他值,申请“gpio编号”失败;
if (ret) return ret;
ret = gpio_direction_output(strMyGpioLED.led_gpio, 1);
//设置PI0为输出,并且输出高电平,默认关闭LED灯
//gpio=strMyGpioLED.led_gpio,指定的“gpio编号”,这里是128,对应的是GI0引脚
//value=1,设置引脚输出高电平
//返回值:0,设置“引脚输出为vakued的值”成功;负值,设置“引脚输出为vakued的值”失败。
if(ret < 0) {
printk("can't set gpio!\r\n");
return ret;
}
return 0;
}
void led_switch(u8 sta,struct MyGpioLED_dev *dev)
{
if(sta == LEDON) {
gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
}
else if(sta == LEDOFF) {
gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
}
}
/*释放gpio编号*/
gpio_free(strMyAtomicLED.led_gpio);
通过比较,我们发现:“新字符设备的GPIO驱动”和“设备树下的GPIO驱动”都要用到寄存器地址,使用“物理内存”和“虚拟内存”映射时,非常不方便,而pinctrl和gpio子系统的GPIO驱动,非常简化。因此,要重点学习pinctrl和gpio子系统下的GPIO驱动开发。