Linux驱动开发第2步_“物理内存”和“虚拟内存”的映射

“新字符设备的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)、根节点“/”下创建一个名为“gpio_led”的子节点,添加内容如下:

led0{

compatible = "zgq,led";

/*设置属性compatible的值为"zgq,led"*/

status = "okay";/*设置属性status的值为"okay"*/

led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;

/*“&gpioi”表示led-gpio引脚所使用的IO属于GPIOI组*/,

/*“0’表示GPIOI组的第0号IO,即PI0引脚*/

/*“GPIO_ACTIVE_LOW”表示低电平有效,“GPIO_PULL_UP”表示上拉*/

};

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("/led0");

  //获取设备节点:strMyGpioLED

  //path="/led0,使用“全路径的节点名“在“stm32mp157d-atk.dts“中查找节点“led0

  //返回值:返回找到的节点,如果为NULL,表示查找失败。

  if(strMyGpioLED.nd == NULL) return -EINVAL;

  strMyGpioLED.led_gpio = of_get_named_gpio(strMyGpioLED.nd, "led-gpio", 0);

  //获取设备树中的gpio属性,得到LED所使用的LED编号

  //在led0节点中,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灯 */

}

}

//函数功能:释放MyGpioLED的gpio

void MyLED_free(void)

{

  gpio_free(strMyGpioLED.led_gpio);

}

通过比较,我们发现:“新字符设备的GPIO驱动”和“设备树下的GPIO驱动”都要用到寄存器地址,使用“物理内存”和“虚拟内存”映射时,非常不方便,而pinctrl和gpio子系统的GPIO驱动,非常简化。因此,要重点学习pinctrl和gpio子系统下的GPIO驱动开发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/472852.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【0x001C】HCI_Write_Page_Scan_Activity详解

目录 一、命令概述 二、命令格式和参数说明 2.1. HCI_Write_Page_Scan_Activity命令格式 2.2. Page_Scan_Interval 2.3. Page_Scan_Window 三、响应事件及参数说明 3.1. HCI_Command_Complete事件 3.2. Status 3.3. 示例 四、命令执行流程 4.1. 命令发起阶段(主机端…

【AI图像生成网站Golang】雪花算法

AI图像生成网站 目录 一、项目介绍 二、雪花算法 三、JWT认证与令牌桶算法 四、项目架构(等待更新) 五、图床上传与图像生成API搭建(等待更新) 六、项目测试与调试(等待更新) 雪花算法 雪花算法 (Snowflake) 是一种高效、可扩展的分布式唯一ID生成算法&#xff0c;最早…

JMeter与大模型融合应用之JMeter日志分析服务化实战应用

JMeter与大模型融合应用之JMeter日志分析服务化 引言 在当今的互联网时代,网站和应用程序的性能直接影响到用户的体验和业务的成功。为了保证系统的稳定性和高效性,性能测试成为了软件开发过程中的一个重要环节。在这其中,Apache JMeter作为一款开源的性能测试工具,凭借其…

Docker环境搭建Cloudreve网盘服务(附shell脚本一键搭建)

Docker搭建Cloudreve Cloudreve介绍&#xff1a; Cloudreve 是一个基于 ThinkPHP 框架构建的开源网盘系统&#xff0c;旨在帮助用户以较低的成本快速搭建起既能满足个人也能满足企业需求的网盘服务。Cloudreve 支持多种存储介质&#xff0c;包括但不限于本地存储、阿里云OSS、…

浪浪云轻量服务器搭建vulfocus网络安全靶场

什么是网络安全靶场 网络安全靶场是一个模拟真实网络环境的训练平台&#xff0c;旨在为网络安全专业人员提供一个安全的环境来测试和提高他们的技能。靶场通常包括各种网络设备、操作系统、应用程序和安全工具&#xff0c;允许用户在其中进行攻击和防御练习。以下是网络安全靶…

对称加密算法DES的实现

一、实验目的 1、了解对称密码体制基本原理 2、掌握编程语言实现对称加密、解密 二、实验原理 DES 使用一个 56 位的密钥以及附加的 8 位奇偶校验位&#xff0c;产生最大 64 位的分组大小。这是一个迭代的分组密码&#xff0c;使用称为 Feistel 的技术&#xff0c;其中将加密…

【linux学习指南】VSCode部署Ubantu云服务器,与Xshell进行本地通信文件编写

文章目录 &#x1f4dd;前言&#x1f320; 步骤&#x1f309;测试同步 &#x1f6a9;总结 &#x1f4dd;前言 本文目的是讲使用Vscode连接Ubantu,与本地Xshell建立通信同步文件编写。 查看本机系统相关信息&#xff1a; cat /etc/lsb*DISTRIB_IDUbuntu: 表示这是 Ubuntu 发行…

实战:一文讲透模糊匹配的三种方式的区别

在 SQL 查询中,模糊查询是我们常用的工具之一。LIKE 关键字配合 % 符号,可以实现前缀匹配、后缀匹配和包含匹配等多种查询方式。然而,不同的匹配方式对查询性能会有显著影响。本文将详细探讨在 SQL 查询中,字符串前后加 % 与只在后面加 % 的性能差异及其应用场景。 一、SQL…

利用Blackbox AI让编程更轻松

引言 随着人工智能技术的发展&#xff0c;AI已经成为工作中不可缺少的工具之一。俗话讲“术业有专攻”&#xff0c;对AI来说当然也是如此。由于训练集、调教等方面的差别&#xff0c;不同的AI适用的工作也不尽相同。在编程辅助方面&#xff0c;已经有一系列比较成熟的平台&…

Vue学习记录03

响应式基础 声明响应式状态 ref() 在组合式API中&#xff0c;推荐使用ref()函数来声明响应式状态&#xff1a; import { ref } from vueconst count ref(0) ref()接收参数&#xff0c;并将其包裹在一个带有.value属性的ref对象中返回&#xff1a; const count ref(0)con…

排序排序的概念及其运用和选择排序

排序排序的概念及其运用和选择排序 7. 排序7.1 排序的概念及其运用7.2 选择排序算法——直接选择排序选择排序基本思想&#xff1a;直接选择排序选择排序原理参考程序 如何交换数据直接选择排序的特性总结&#xff1a; 7. 排序 7.1 排序的概念及其运用 排序&#xff1a;所谓排…

【目标检测】用YOLOv8-Segment训练语义分割数据集(保姆级教学)

前言 这篇教程会手把手带你用 YOLOv8-Segment 搭建一个属于自己的分割任务项目。从环境配置到数据集准备&#xff0c;再到模型训练和测试&#xff0c;所有步骤都有详细说明&#xff0c;适合初学者使用。你将学会如何安装必要的软件&#xff0c;标注自己的数据&#xff0c;并使…

爬虫开发工具与环境搭建——开发工具介绍

第二章&#xff1a;爬虫开发工具与环境搭建 第一节 开发工具介绍 爬虫开发需要一些合适的工具和框架来高效地抓取网页数据。在这节中&#xff0c;我们将介绍常用的开发工具&#xff0c;帮助开发者快速搭建爬虫开发环境。 1. Python与爬虫框架选择 Python因其简洁、易学的语法…

类和对象——拷贝构造函数,赋值运算符重载(C++)

1.拷⻉构造函数 如果⼀个构造函数的第⼀个参数是自身类类型的引用&#xff0c;且任何额外的参数都有默认值&#xff0c;则此构造函数也叫做拷贝构造函数&#xff0c;也就是说拷贝构造是⼀个特殊的构造函数。 // 拷贝构造函数//d2(d1) Date(const Date& d) {_year d._yea…

高级数据结构——hash表与布隆过滤器

文章目录 hash表与布隆过滤器1. hash函数2. 选择hash函数3. 散列冲突3.1 负载因子3.2 冲突解决3. STL中的散列表 4. 布隆过滤器4.1 背景1. 应用场景2. 常见的处理场景&#xff1a; 4.2 布隆过滤器构成4.3 原理4.4 应用分析4.5 要点 5. 分布式一致性hash5.1 缓存失效问题 6. 大数…

xcode-select: error: tool ‘xcodebuild‘ requires Xcode, but active developer

打开 .sh 文件所在的终端窗口&#xff0c;执行终端命令&#xff1a;sh 文件名.sh&#xff0c;出现如下错误&#xff1a; 解决办法&#xff1a;

java中volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗?

大家好&#xff0c;我是锋哥。今天分享关于【java中volatile 类型变量提供什么保证&#xff1f;能使得一个非原子操作变成原子操作吗&#xff1f;】面试题。希望对大家有帮助&#xff1b; java中volatile 类型变量提供什么保证&#xff1f;能使得一个非原子操作变成原子操作吗&…

candence : 通孔焊盘、插装器件封装绘制

通孔焊盘、插装器件封装绘制 以2.54mm 2x10的 排针为例绘制封装 一、 flash 热风焊盘制作 1、新建 2、选择 flash SYMBOL&#xff0c;并设置保存路径 3、add flash 具体参数 花焊盘参数 Inner diameter 通孔直径(1.0mm) 圆形补偿值(0.4mm)1.4mm Outer diameter 通孔直径…

VSCode设置

打开设置页 VSCode打开配置页面&#xff0c;有多种方式&#xff1a; a. 点击左上角 File(文件) -> Preferences (首选项) -> Settings(设置)。 b. 使用快捷键 Ctrl ,(Windows) 或 Cmd ,(Mac)。 c. 点击左下角 Manage(管理) -> Settings(设置)。 VSCode设置页面打…

SpringMVC数据校验、数据格式化处理、国际化设置

SpringMVC数据校验、数据格式化处理、国际化设置 1.数据验证 &#xff08;1)使用JSR-303验证框架 JSR&#xff08;Java Specification Requests&#xff09;&#xff0c;意思是Java 规范提案。JSR-303是JAVA EE 6中的一项子规范&#xff0c;叫做Bean Validation。JSR 303&am…