当前位置: 首页 > news >正文

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驱动开发。

相关文章:

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

“新字符设备的GPIO驱动”和“设备树下的GPIO驱动”都要用到寄存器地址&#xff0c;使用“物理内存”和“虚拟内存”映射时&#xff0c;非常不方便&#xff0c;而pinctrl和gpio子系统的GPIO驱动&#xff0c;非常简化。因此&#xff0c;要重点学习pinctrl和gpio子系统下的GPIO驱…...

告别多品牌乱战,吉利开始觉醒

科技新知 原创作者丨思原 编辑丨蕨影 2007年&#xff0c;是国内自主品牌汽车萌芽的一年&#xff0c;当时行业普遍奉行“多生孩子好打架”战略&#xff0c;吉利也是在这样的背景下发布了《宁波宣言》&#xff0c;奠定了之后十多年的发展主导思想。 然而&#xff0c;新能源的快…...

Target-absent Human Attention

Abstract 预测人类注视行为对于构建能够预测用户注意力的人机交互系统非常重要。已经开发出计算机视觉模型来预测人们在搜索目标物体时的注视点。但当目标不存在于图像中时,又该如何处理呢?同样重要的是要了解当人们找不到目标时,他们如何进行搜索,以及何时停止搜索。在本文…...

<QNAP 453D QTS-5.x> 日志记录:在 Docker 中运行的 Flask 应用安装 自签名 SSL 证书 解决 Chrome 等浏览器证书安全

原因&#xff1a;Chrome 不信任 ssc 证书 使启用了 HTTPS&#xff0c;即使有使用 自签名证书 (self-signed certificate 非由可信的证书颁发机构 【CA&#xff0c;Certificate Authority】签发的&#xff09;。浏览器 Chrome 默认不信任自签名证书&#xff0c;也会报 NET::ERR_…...

通过huggingface-cli下载Hugging Face上的公开数据集或模型至本地

1. 获取 Access Tokens 在使用huggingface-cli命令下载之前需要先去官网获取 Access Tokens&#xff1a; 获取tokens的官网链接&#xff1a;https://huggingface.co/settings/tokens点击新增 token&#xff1a; 然后选择 write 权限&#xff1a; 最后&#xff0c;这个 Access…...

论文阅读——Intrusion detection systems using longshort‑term memory (LSTM)

一.基本信息 论文名称&#xff1a;Intrusion detection systems using longshort‑term memory (LSTM) 中文翻译&#xff1a;基于长短期记忆(LSTM)的入侵检测系统 DOI&#xff1a;10.1186/s40537-021-00448-4 作者&#xff1a;FatimaEzzahra Laghrissi1* , Samira Douzi2*, Kha…...

SparkSQL的执行过程:从源码角度解析逻辑计划、优化计划和物理计划

SparkSQL的执行过程可以分为以下几个阶段&#xff1a;从用户的SQL语句到最终生成的RDD执行&#xff0c;涵盖逻辑计划、优化计划和物理计划。以下是详细的源码角度解析&#xff1a; 1. 解析阶段&#xff08;Parsing&#xff09; SQL语句解析&#xff1a;Spark 使用 Catalyst 引…...

Leetcode打卡:新增道路查询后的最短距离II

执行结果&#xff1a;通过 题目&#xff1a;3244 新增道路查询后的最短距离II 给你一个整数 n 和一个二维整数数组 queries。 有 n 个城市&#xff0c;编号从 0 到 n - 1。初始时&#xff0c;每个城市 i 都有一条单向道路通往城市 i 1&#xff08; 0 < i < n - 1&…...

Spring Web入门练习

加法计算器 约定前后端交互接⼝ 约定 "前后端交互接⼝" 是进⾏ Web 开发中的关键环节. 接⼝⼜叫 API&#xff08;Application Programming Interface), 我们⼀般讲到接⼝或者 API&#xff0c;指的都是同⼀个东西. 是指应⽤程序对外提供的服务的描述, ⽤于交换信息…...

计算机毕业设计 | SpringBoot+vue汽车资讯网站 汽车购买咨询管理系统(附源码+论文)

1&#xff0c;绪论 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理汽车资讯网站的相关信息成为必然…...

stm32下的ADC转换(江科协 HAL版)

十二. ADC采样 文章目录 十二. ADC采样12.1 ADC的采样原理12.2 STM32的采样基本过程1.引脚与GPIO端口的对应关系2.ADC规则组的四种转换模式(**)2.2 关于转换模式与配置之间的关系 12.3 ADC的时钟12.4 代码实现(ADC单通道 & ADC多通道)1. 单通道采样2. 多通道采样 19.ADC模数…...

解决IntelliJ IDEA的Plugins无法访问Marketplace去下载插件

勾选Auto-detect proxy setting并填入 https://plugins.jetbrains.com 代理URL&#xff0c;可以先做检查连接&#xff1a;...

react 如何修改弹出的modal的标题

原来标题的样子&#xff1a; 修改为&#xff1a; 实现方式&#xff1a; <Modal title<span>股价趋势/{this.state.pccode}</span> visible{this.state.isPriceModalOpen} style{{ top: 20 }} width{1320} height{400} footer{null} onCancel{()>this.hideMo…...

C#中的二维数组的应用:探索物理含义与数据结构的奇妙融合

在C#编程中&#xff0c;二维数组&#xff08;或矩阵&#xff09;是一种重要的数据结构&#xff0c;它不仅能够高效地存储和组织数据&#xff0c;还能通过其行、列和交叉点&#xff08;备注&#xff1a;此处相交处通常称为“元素”或“单元格”&#xff0c;代表二维数组中的一个…...

HTML5拖拽API学习 托拽排序和可托拽课程表

文章目录 前言拖拽API核心概念拖拽式使用流程例子注意事项综合例子&#x1f330; 可拖拽课程表拖拽排序 前言 前端拖拽功能让网页元素可以通过鼠标或触摸操作移动。HTML5 提供了标准的拖拽API&#xff0c;简化了拖放操作的实现。以下是拖拽API的基本使用指南&#xff1a; 拖拽…...

内容补充页(相关公式解释)

from 学习日记_20241117_聚类方法&#xff08;高斯混合模型&#xff09; 学习日记_20241117_聚类方法&#xff08;高斯混合模型&#xff09; 公式 P ( Z k ) π k P(Zk) \pi_k P(Zk)πk​ 在高斯混合模型 (GMM) 中&#xff0c;公式 P ( Z k ) π k P(Zk) \pi_k P(Zk…...

vue中动态渲染静态图片资源

不报错且f12查看元素的时候&#xff0c;显示的src说明已经渲染到html的src上&#xff0c;但是就是不显示在页面上 原因 在vue上&#xff0c;动态渲染静态图片资源&#xff08;比如从assets文件夹加载的图片&#xff09;需要注意打包工具对静态资源的解析方式 由于vue2的脚手…...

管伊佳ERP,原名华夏ERP,一个简约易上手的国产ERP系统

JSH_ERP&#xff08;管伊佳ERP&#xff09;是一款开源、模块化的企业资源计划系统&#xff0c;旨在为中小企业提供高效的管理工具。它基于SpringBoot框架和SaaS模式&#xff0c;支持进销存、财务、生产等业务模块&#xff0c;包括零售、采购、销售、仓库和报表管理。 核心特点…...

学习虚幻C++开发日志——委托(持续更新中)

委托 官方文档&#xff1a;Delegates and Lamba Functions in Unreal Engine | 虚幻引擎 5.5 文档 | Epic Developer Community | Epic Developer Community 简单地说&#xff0c;委托就像是一个“函数指针”&#xff0c;但它更加安全和灵活。它允许程序在运行时动态地调用不…...

开窗函数 - first_value/last_value

1、开窗函数是什么&#xff1f; 开窗函数用于为行定义一个窗口&#xff08;这里的窗口是指运算将要操作的行的集合&#xff09;&#xff0c;它对一组值进行操作&#xff0c;不需要使用 GROUP BY 子句对数据进行分组&#xff0c;能够在同一行中同时返回基础行的列和聚合列。 2、…...

「一」HarmonyOS端云一体化概要

关于作者 白晓明 宁夏图尔科技有限公司董事长兼CEO、坚果派联合创始人 华为HDE、润和软件HiHope社区专家、鸿蒙KOL、仓颉KOL 华为开发者学堂/51CTO学堂/CSDN学堂认证讲师 开放原子开源基金会2023开源贡献之星 「目录」 「一」HarmonyOS端云一体化概要 「二」体验HarmonyOS端云一…...

nodejs21: 快速构建自定义设计样式Tailwind CSS

Tailwind CSS 是一个功能强大的低级 CSS 框架&#xff0c;只需书写 HTML 代码&#xff0c;无需书写 CSS&#xff0c;即可快速构建美观的网站。 1. 安装 Tailwind CSS React 项目中安装 Tailwind CSS&#xff1a; 1.1 安装 Tailwind CSS 和相关依赖 安装 Tailwind CSS: npm…...

从JSON数据提取嵌套字段并转换为独立列的简洁方法

从JSON数据提取嵌套字段并转换为独立列的简洁方法 在数据处理和数据分析的日常工作中&#xff0c;我们经常遇到复杂的嵌套数据结构&#xff0c;特别是嵌入在JSON字段中的数据。这些数据往往需要解析并展开成独立的列&#xff0c;以便后续分析和建模。本文将详细介绍如何在Pyth…...

湘潭大学软件工程算法设计与分析考试复习笔记(四)

回顾 湘潭大学软件工程算法设计与分析考试复习笔记&#xff08;一&#xff09;湘潭大学软件工程算法设计与分析考试复习笔记&#xff08;二&#xff09;湘潭大学软件工程算法设计与分析考试复习笔记&#xff08;三&#xff09; 前言 现在是晚上十一点&#xff0c;我平时是十…...

特征交叉-DeepCross Network学习

一 tensorflow官方实现 tensorflow的官方实现已经是V2版本 class Cross(tf.keras.layers.Layer):"""Cross Layer in Deep & Cross Network to learn explicit feature interactions.Args:projection_dim: int&#xff0c;低秩矩阵的维度&#xff0c;应该小…...

stm32cubemx+VSCODE+GCC+makefile 开发环境搭建

title: stm32cubemxVSCODEGCCmakefile 开发环境搭建 tags: FreertosHalstm32cubeMx 文章目录 内容往期内容导航第一步准备环境vscode 插件插件配置点灯 内容 往期内容导航 第一步准备环境 STM32CubeMXVSCODEMinGWOpenOcdarm-none-eabi-gcc 然后把上面下载的软件 3 4 5 bin 文…...

Go语言中的Defer机制详解与示例

在Go语言中&#xff0c;defer是一个关键字&#xff0c;用于确保资源的清理和释放&#xff0c;特别是在函数中创建的资源。defer语句会将其后的函数调用推迟到包含它的函数即将返回时执行。这使得defer成为处理文件关闭、数据库连接释放、解锁等资源清理操作的理想选择。 Defer…...

H.265流媒体播放器EasyPlayer.js H5流媒体播放器如何验证视频播放是否走硬解

随着技术的不断进步和5G网络的推广&#xff0c;中国流媒体播放器行业市场规模以及未来发展趋势都将持续保持稳定的增长&#xff0c;并将在未来几年迎来新的发展机遇。流媒体播放器将继续作为连接内容创作者和观众的重要桥梁&#xff0c;推动数字媒体产业的创新和发展。 EasyPla…...

ms-hot目录

1. ms-hot1...

vulfocus在线靶场:骑士cms_cve_2020_35339:latest 速通手册

目录 一、启动环境&#xff0c;访问页面&#xff0c;ip:端口号/index.php?madmin,进入后台管理页面&#xff0c;账号密码都是adminadmin 二、进入之后&#xff0c;根据图片所示&#xff0c;地址后追加一下代码&#xff0c;保存修改 ​三、新开标签页访问&#xff1a;①ip:端…...

开发公司移交柴油发动机需要具备哪些条件/seo站长之家

本博文主要讲述SAP S/4 1511版本的变化&#xff0c;主要是跟ECC版本的对比。变化还是挺多的&#xff0c;相当一部分是后勤&#xff0c;但绝大部分还是财务成本这一块。作为从事S/4版本的从业者&#xff0c;了解1511版本的变化还是挺有必要的。 所谓1511&#xff0c;就是15年11月…...

网站建设与管理量化考细则/2345浏览器网址

LoadRunner压力测试时&#xff0c;一直会报12261错误&#xff0c;错误内容大概如下&#xff1a; Error -26612: HTTP Status-Code500 (Internal Server Error) for。。。。。。。。 网上也没有好的解决方案&#xff0c;有些人说看日志&#xff0c;但是具体解决方案没有&#xf…...

wordpress 菜单 链接目标/百度投流

谁能告诉我为什么我每次投稿都会被删除文章再封号&#xff0c;然后发短信申请恢复再重新发就没问题&#xff1f;知乎这是出什么幺蛾子&#xff1f;提示&#xff1a;这一篇极其没有营养&#xff0c;根本算不上配置&#xff0c;为免不适&#xff0c;请提前做好心理准备。​ 这一篇…...

ps做 网站教程/优化大师官方免费

实现两个N*N矩阵的乘法&#xff0c;矩阵由一维数组表示 代码如下&#xff1a; //实现两个N*N矩阵的乘法&#xff0c;矩阵由一维数组表示。 #include<iostream> using namespace std;#define size 2int *multi(int *a, int *b, int N){int i, j, k, temp;int *c (int *)…...

工程公司起名大全/广东优化疫情防控措施

目录 3.1过程语句 3.2任务、函数和void函数 3.3任务和函数概述 3.3.1在子程序中去掉begin...end 3.4子程序参数 3.4.1C语言风格的子程序参数 3.4.2参数的方向 3.4.3高级的参数类型 3.4.4参数的缺省值 3.4.5采用名字进行参数传递 3.4.6常见的代码错误 3.5子程序的返…...

wordpress加载css样式/最新的疫情最新消息

本文以实例形式展示了Python获取电脑硬件信息及状态的实现方法&#xff0c;是Python程序设计中很有实用价值的技巧。分享给大家供大家参考之用。具体方法如下&#xff1a;主要功能代码如下&#xff1a;#!/usr/bin/env python# encoding: utf-8from optparse import OptionParse…...