21、基于Firefly-rk3399字符设备驱动寄存器控制LED

文章目录

开发平台:Firefly-Rk3399 2+16

一、电路分析

显然电路POS图可以看到两个LED分别为LED1和LED2,我们只需要根据元器件名称(网络名)找到对应的引脚即可,接下来查看电路图。搜索LED1可以找到这部分的模块。

分析上面电路:三极管正向导通,DIY_LED引脚输出高电平导通:灯亮。

三级管的作用: 兼容低电压(3.3/1.2v),对于NPN正向导通,P电压大于N, 即可导通

R307作用:保护led,给LED分流

由命名可知:WORK_LED为工作灯;DIY为用户自定义灯。

查看DIY引脚:GPIO0_B5

引脚配置功能(R/W register)

​ pwer

​ clock

​ mode

二、RK3399数据手册分析:

下载瑞芯微技术指导手册:

Rockchip_RK3399TRM_V1.4_Part1-20170408.pdf (rock-chips.com)

查看CRU PMU GPIO GRF章节



1、GPIO(General-purpose input/output)介绍:

GPIO 共有5组,GPIO0-GPIO4,分组信息如上。每一组又分为八个一组A B C D。对于GPIO的描述如:GPIO0_A6是指第一组的第6个引脚

GPIO_SWPORTA_DR data寄存器, 对于输出引脚有用, 共32位,对应一组里面的4组(ABCD)引脚。

GPIO_SWPORTA_DDR 方向寄存器,控制输入输出,0输入, 1输出, 共32位,对应一组里面的4组引脚。

GPIO_EXT_PORTA 外部寄存器, 输入模式时,读取寄存器对应位的值获取当前引脚的电平。共32位,对应一组里面的4组引脚。

为了便于后续驱动编写,整理表格如下

模块 基地址 寄存器 偏移地址 备注
GPIO0 0XFF720000(64K) / / /
GPIO1 0XFF730000(64K) / / /
GPIO2 0xFF780000(32K) / / /
GPIO3 0xFF788000(32K) / / /
GPIO4 0xFF790000(32K) / / /
GPIO_SWPORTA_DR 0x0000 data寄存器, 对于输出引脚有用, 共32位,对应一组里面的4组引脚
GPIO_SWPORTA_DDR 0x0004 方向寄存器,控制输入输出,0输入, 1输出, 共32位,对应一组里面的4组引脚
GPIO_EXT_PORTA 0x0050 外部寄存器, 输入模式时,读取寄存器对应位的值获取当前引脚的电平。共32位,对应一组里面的4组引脚
2、CRU(Clock & Reset Unit)介绍

CRU提供时钟流程:

查找时钟使能寄存器(PMUCRU_CLKGATE_CONx CRU_CLKGATE_CONx):PMU全称Power Management Unit



查找GPIO相关内容:

PMUCRU_CLKGATE_CON1 和PMUCRU_CLKGATE_CON31配置GPIO0-4



对应位置0为使能clock。

需要注意:

修改寄存器之前需要修改对应位的mask。写1(high)使能mask。

Operational Base:CRU基地址如上图所示。

为了便于后续驱动编写,整理表格如下

模块 基地址 寄存器 偏移地址 偏移位数 备注(mask位(WO)需置1用于启用对应的GATA位)
CRU 0XFF760000
PMUCRU_CLKGATE_CON1 0x0104 bit3(RW) pclk_gpio0 clock disable bit When HIGH, disable clock mask_bit:19(W1)
bit4(RW) pclk_gpio1 clock disable bit When HIGH, disable clock mask_bit:20(W1)
CRU_CLKGATE_CON31 0x037C bit3(RW) pclk_gpio2 clock disable bit When HIGH, disable clock mask_bit:19(W1)
bit4(RW) pclk_gpio3 clock disable bit When HIGH, disable clock mask_bit:20(W1)
bit5(RW) pclk_gpio4 clock disable bit When HIGH, disable clock mask_bit:21(W1)
3、PMU(Power Management Uni)

IOMUX:实现IO口的分时复用,在使用GPIO之前需要先配置IOMUX

根据DATASHEET

在PMU部分配置如下:

先看PMU_GRF下的引脚配置:

PMUGRF_GPIO0A_IOMUX

16:31\]位为写使能位,写1使能,后面每两位代表一个引脚,写入对应的值代表不同的功能。比如要配置GPIOA_7为admmc_dectn, 此时15位为0, 14位为1。 reg \| (1 \<\< 14)。14位指的是数据的第14位。 **PMUGRF_GPIO0A_IOMUX** **PMUGRF_GPIO0B_IOMUX** **PMUGRF_GPIO1A_IOMUX** **PMUGRF_GPIO1B_IOMUX** **PMUGRF_GPIO1C_IOMUX** **PMUGRF_GPIO1D_IOMUX** 为了便于后续驱动编写,整理表格如下 | 模块 | 基地址 | 寄存器 | 偏移地址 | 备注 | |---------|------------|---------------------|---------|----------------------| | PMU_GRF | 0xFF320000 | PMUGRF_GPIO0A_IOMUX | 0x00000 | GPIO0A iomux control | | | | PMUGRF_GPIO0B_IOMUX | 0x00004 | GPIO0B iomux control | | | | PMUGRF_GPIO1A_IOMUX | 0x00010 | GPIO1A iomux control | | | | PMUGRF_GPIO1B_IOMUX | 0x00014 | GPIO1B iomux control | | | | PMUGRF_GPIO1C_IOMUX | 0x00018 | GPIO1C iomux control | | | | PMUGRF_GPIO1D_IOMUX | 0x0001c | GPIO1D iomux control | ###### 4、GRF(General Register Files) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/51b82fefba8b4425b78a639201d75131.png) 为了便于后续驱动编写,整理表格如下 | 模块 | 基地址 | 寄存器 | 偏移地址 | 备注 | |-----|------------|------------------|---------|----------------------| | GRF | 0xFF770000 | | | | | | | GRF_GPIO2A_IOMUX | 0x0e000 | GPIO2A iomux control | | | | GRF_GPIO2B_IOMUX | 0x0e004 | GPIO2B iomux control | | | | GRF_GPIO2C_IOMUX | 0x0e008 | GPIO2C iomux control | | | | GRF_GPIO2D_IOMUX | 0x0e00c | GPIO2D iomux control | | | | GRF_GPIO3A_IOMUX | 0x0e010 | GPIO3A iomux control | | | | GRF_GPIO3B_IOMUX | 0x0e014 | GPIO3B iomux control | | | | GRF_GPIO3C_IOMUX | 0x0e018 | GPIO3C iomux control | | | | GRF_GPIO3D_IOMUX | 0x0e01C | GPIO3D iomux control | | | | GRF_GPIO4A_IOMUX | 0x0e020 | GPIO4A iomux control | | | | GRF_GPIO4B_IOMUX | 0x0e024 | GPIO4B iomux control | | | | GRF_GPIO4C_IOMUX | 0x0e028 | GPIO4C iomux control | | | | GRF_GPIO4D_IOMUX | 0x0e02C | GPIO4D iomux control | ##### 三、地址映射 ​ Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址。 ###### 1、ioremap函数 ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 , 定 义 在arch/arm/include/asm/io.h 文件中,定义如下: ```C void __iomem *ioremap(resource_size_t res_cookie, size_t size); res_cookie:要映射的物理起始地址。 size:要映射的内存空间大小。 返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。 整页映射:4096字节 ``` 例如: ```C 假如我们要获取GPIOI_MODER 寄存器对应的虚拟地址,使用如下代码即可: #define GPIOI_MODER (0X5000A000) static void __iomem* GPIO_MODER_PI; GPIO_MODER_PI = ioremap(GPIOI_MODER, 4); ``` 宏 GPIOI_MODER 是寄存器物理地址,GPIO_MODER_PI 是映射后的虚拟地址。对于RK3399来说一个寄存器是 4 字节(32 位),因此映射的内存长度为 4。映射完成以后直接对 GPIO_MODER_PI 进行读写操作即可。 ###### 2、iounmap函数 卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原型如下: ```C void iounmap (volatile void __iomem *addr) ``` iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。假如我们现在要取消掉 GPIO_MODER_PI 寄存器的地址映射,使用如下代码即可: ```C iounmap(GPIO_MODER_PI); ``` 申请到的虚拟地址有MMU(Memory manager UNIT)做隔离之后MMU进行地址映射,并且会做权限保护。 ###### 3、读操作函数 ```C u8 readb(const volatile void __iomem *addr) 8bit读操作,addr 就是要读取写内存地址,返回值就是读取到的数据 u16 readw(const volatile void __iomem *addr) 16bit读操作,addr 就是要读取写内存地址,返回值就是读取到的数据 u32 readl(const volatile void __iomem *addr) 32bit读操作,addr 就是要读取写内存地址,返回值就是读取到的数据 ``` ###### 4、写操作函数(地址为虚拟地址) ```C void writeb(u8 value, volatile void __iomem *addr) 8bit,参数 value 是要写入的数值,addr 是要写入的地址。 void writew(u16 value, volatile void __iomem *addr) 16bit,参数 value 是要写入的数值,addr 是要写入的地址。 void writel(u32 value, volatile void __iomem *addr) 32bit,参数 value 是要写入的数值,addr 是要写入的地址。 ``` ##### 四、驱动编写 ###### 1、框架编写 leds.c ```C #include #include #include #include #include #include #include #include "led_operation.h" /* 设备节点名 加载驱动后:cat /proc/devices | grep diy_led 可以查到它的设备号 */ #define LED_NAME "diy_led" /* 设备子节点个数 */ #define LED_NUM 2 /* 设备号 static防止命名污染 */ static int major = 0; /* 设备节点类 */ static struct class *led_class; /* 自定义封装的结构体的指针,详见led_operations.h文件内的struct led_operations,下文有写 */ struct led_operations *p_ledopr; /* 对应app中的open() */ static int led_open(struct inode *inode, struct file *file) { /* 通过文件的inode号唯一标识获取子设备号 */ int minor = iminor(inode); printk("%s %s %d led device open\r\n", __FILE__, __FUNCTION__, __LINE__); /* init(minor)通过子设备号初始化LED, init函数见board_demo文件 */ p_ledopr->init(minor); return 0; } static ssize_t led_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) { printk("%s %s %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static ssize_t led_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt) { int err; char status; /* 同上 参考的drivers/char/dsp56k.c文件用法*/ struct inode *inode = file_inode(file); int minor = iminor(inode); printk("%s %s %d led device write\r\n", __FILE__, __FUNCTION__, __LINE__); err = copy_from_user(&status, buf, 1); /* Control LED by status and sub-device number.*/ p_ledopr->ctl(minor, status); return 1; } static int led_release(struct inode *inode, struct file *file) { printk("%s %s %d led device release\r\n", __FILE__, __FUNCTION__, __LINE__); return 0; } const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .read = led_read, .write = led_write, }; /* First run function */ static int __init led_init(void) { int i = 0; printk("led init.\r\n"); /* 参数 1 设置 0 , 动态申请主设备号, 返回值为申请到的主设备号 注册设备驱动*/ major = register_chrdev(0, LED_NAME, &led_fops); /* 申请类 */ led_class = class_create(THIS_MODULE, "LED_CLASS"); /* 在led_class类下申请两个设备节点 */ for(i=0; i #include #include #include #include #include #include #include "led_operation.h" static int board_demo_init(int which) { printk("%s %s %d led %d\r\n", __FILE__, __FUNCTION__, __LINE__, which); return 0; } static int board_demo_ctl(int which, char status) { printk("%s %s %d led %d %s\r\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off"); return 0; } static struct led_operations board_demo_led_opr = { .init = board_demo_init, .ctl = board_demo_ctl, }; struct led_operations *get_board_led_opr(void) { return &board_demo_led_opr; } ``` Makefile KERNELDIR := /home/liu/rockchip/kernel/kernel-develop-4.4_back CURRENT_PATH := $(shell pwd) leds-y := led.o board_demo.o obj-m := leds.o build:kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean ###### 编译 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- **生成 leds.ko** ###### 2、应用层框架 led_test.c ```C #include #include #include #include #include #include int main(int argc, const char *argv[]) { int fd; char status; int ret; if(argc != 3) { printf("para must rathor than three, Usage: ./%s ", argv[0]); return -1; } fd = open(argv[1], O_RDWR); if(fd < 0) { printf("can't open file %s\n", argv[1]); return -1; } if(strcmp("on", argv[2]) == 0) { printf("I'll open led\n"); status = 1; } else { printf("I'll close led\n"); status = 0; } /* 写0或写1 */ ret = write(fd, &status, 1); close(fd); return 0; } ``` ###### 编译 gcc led_test.c -o led_test ###### 执行 ./led_test /dev/diy_led_0 on ./led_test /dev/diy_led_0 off ./led_test /dev/diy_led_1 on ./led_test /dev/diy_led_1 off ###### 3、基于Firefly-RK3399开发板编写字符设备驱动 **由电路分析章节可以得到:DIY引脚为:GPIO0_B5,且输出高电平LED灯亮,输出低电平LED灯灭,且兼容1.2v和3.3v** ###### (1)流程 ​ 使能时钟引脚CRU(clock reset unit) ​ 设置GPIO功能IOMUX ​ 设置GPIO输出GPIO方向 ​ 设置GPIO data寄存器 ###### (2)寄存器分析 写使能位对应关系: ​ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ​ 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 GPIO0_B5需要用到的地址有: ​ **PMUCRU_CLKGATE_CON1**:使能GPIO0需要给它bit3置0,bit19(mask位)置一 ```C #define PMUCRU_CLKGATE_CON1 (0xFF760000 + 0x0104) (*PMUCRU_CLKGATE_CON1) | (1 << 19) & (~(1 << 3)) // 因为只有把mask位写1才会写入寄存器的值,所以不需要按位去写入 // 直接赋值 *PMUCRU_CLKGATE_CON1 = (1 << 3 + 16) & (0 << 3); ``` ​ PMUGRF_GPIO0B_IOMUX:设置 GPIO0B 用于 GPIO,查找GPIO0_B5 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9fe41f0a8d4a4fb1a17a6c16eab3e220.png) 需要给第10 11位写0,并且根据上面的写使能对应关系可以得知给26、27位置一。 ```C #define PMUGRF_GPIO0B_IOMUX (0xFF320000 + 0x0004); (*PMUGRF_GPIO0B_IOMUX) | (3 << 26) & (~(3 << 10)) // 因为只有把mask位写1才会写入寄存器的值,所以不需要按位去写入 // 直接赋值 *PMUGRF_GPIO0B_IOMUX = (3 << 10 + 16) & (0 << 10); ``` ​ **GPIO_SWPORTA_DR**:设置 GPIO 输出高电平 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9bc1a560c4d049e0a1326f5b788fe607.png) 根据手册:需要将B5引脚置一,B5为GPIO0的第二组的第5个(0开始数)引脚,所以需要偏移 8 + 5 = 13 ```C #define GPIO_SWPORTA_DR (0XFF720000 + 0x0000); (*GPIO_SWPORTA_DR) | (1 << 13); ``` **GPIO_SWPORTA_DR**:设置 GPIO 输出低电平 *GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 13)); ​ **GPIO_SWPORTA_DDR**:设置 GPIO 作为 output 引脚 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/14a438e77bc5411ebc2de578011d4303.png) 同GPIO_SWPORTA_DR,将第13位置一设置为输出引脚 ```C #define GPIO_SWPORTA_DDR (0XFF720000 + 0x0004); ``` ​ GPIO_EXT_PORTA:读取 GPIO 引脚电平 #define GPIO_EXT_PORTA (0XFF720000 + 0x0050); ###### (3)代码编写(单LED驱动代码) **led_firefly.c** ```C #include #include #include #include #include #include #include #include "led_operation.h" #define LED_NAME "diy_led" // #define LED_NUM 2 static int major = 0; static struct class *led_class; struct led_operations *p_ledopr; static int led_open(struct inode *inode, struct file *file) { int minor = iminor(inode); printk("%s %s %d led device open\r\n", __FILE__, __FUNCTION__, __LINE__); /*Init LED device by sub-device number */ p_ledopr->init(minor); return 0; } static ssize_t led_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) { printk("%s %s %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static ssize_t led_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt) { int err; char status; struct inode *inode = file_inode(file); int minor = iminor(inode); printk("%s %s %d led device write\r\n", __FILE__, __FUNCTION__, __LINE__); err = copy_from_user(&status, buf, 1); /*Control LED by status and sub-device number.*/ p_ledopr->ctl(minor, status); return 1; } static int led_release(struct inode *inode, struct file *file) { printk("%s %s %d led device release\r\n", __FILE__, __FUNCTION__, __LINE__); return 0; } const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .read = led_read, .write = led_write, }; static int __init led_init(void) { int i = 0; printk("led init.\r\n"); major = register_chrdev(0, LED_NAME, &led_fops); led_class = class_create(THIS_MODULE, "LED_CLASS"); p_ledopr = get_board_led_opr(); for(i=0; inum; i++) { device_create(led_class, NULL, MKDEV(major, i), NULL, "diy_led_%d", i); } return 0; } static void __exit led_exit(void) { int i = 0; printk("led exit!!!\r\n"); for(i=0; inum; i++) { device_destroy(led_class, MKDEV(major, i)); } class_destroy(led_class); unregister_chrdev(major, LED_NAME); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("liu"); MODULE_INFO(intree, "Y"); ``` board_firefly.c ```C #include #include #include #include #include #include #include #include "led_operation.h" static volatile unsigned int *PMUCRU_CLKGATE_CON1; static volatile unsigned int *PMUGRF_GPIO0B_IOMUX; static volatile unsigned int *GPIO_SWPORTA_DR; static volatile unsigned int *GPIO_SWPORTA_DDR; // static volatile unsigned int *GPIO_EXT_PORTA; /* 此处代码由3、2寄存器分析章节详细分析 */ static int board_demo_init(int which) { printk("%s %s %d led %d\r\n", __FILE__, __FUNCTION__, __LINE__, which); if(!PMUCRU_CLKGATE_CON1) PMUCRU_CLKGATE_CON1 = ioremap(0xFF760000 + 0x0104, 4); if(!PMUGRF_GPIO0B_IOMUX) PMUGRF_GPIO0B_IOMUX = ioremap(0xFF320000 + 0x0004, 4); if(!GPIO_SWPORTA_DR) GPIO_SWPORTA_DR = ioremap(0XFF720000 + 0x0000, 4); if(!GPIO_SWPORTA_DDR) GPIO_SWPORTA_DDR = ioremap(0XFF720000 + 0x0004, 4); /* operations register */ *PMUCRU_CLKGATE_CON1 = (1 << (3 + 16)) & (0 << 3); *PMUGRF_GPIO0B_IOMUX = (3 << (10 + 16)) & (0 << 10); *GPIO_SWPORTA_DDR = (*GPIO_SWPORTA_DDR) | (1 << 13); return 0; } static int board_demo_ctl(int witch, char status) { printk("%s %s %d led %d %s\r\n", __FILE__, __FUNCTION__, __LINE__, witch, status ? "on" : "off"); /* 此处代码由3、2寄存器分析章节详细分析 */ if(witch == 0) { if(status) *GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) | (1 << 13); else *GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 13)); } return 0; } static struct led_operations board_demo_led_opr = { .num = 1, .init = board_demo_init, .ctl = board_demo_ctl, }; struct led_operations *get_board_led_opr(void) { return &board_demo_led_opr; } ``` led_operation.h #ifndef _LED_OPR_H #define _LED_OPR_H struct led_operations { int num; int (*init) (int which); int (*ctl) (int which, char status); }; struct led_operations *get_board_led_opr(void); #endif Makefile ```C KERNELDIR := /home/liu/rockchip/kernel/kernel-develop-4.4_back CURRENT_PATH := $(shell pwd) leds_firefly-y := led_firefly.o board_firefly.o obj-m := leds_firefly.o build:kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean ``` ###### 4、编译LED-filrefly驱动 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- ###### 5、测试 **加载驱动** `insmod leds_firefly.ko` **applycation程序测试亮灭** ./led_test /dev/diy_led_0 on ./led_test /dev/diy_led_0 off 可以观察到LED灯的亮灭 **内核打印如下:** ```shell [16159.055914] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c led_open 21 led device open [16159.055928] /home/liu/rockchip/kernel/Drivers/4_led_firefly/board_firefly.c board_demo_init 18 led 0 [16159.056226] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c led_write 41 led device write [16159.056234] /home/liu/rockchip/kernel/Drivers/4_led_firefly/board_firefly.c board_demo_ctl 38 led 0 on [16159.056244] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c led_release 51 led device release [16229.367954] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c led_open 21 led device open [16229.367967] /home/liu/rockchip/kernel/Drivers/4_led_firefly/board_firefly.c board_demo_init 18 led 0 [16229.368161] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c led_write 41 led device write [16229.368169] /home/liu/rockchip/kernel/Drivers/4_led_firefly/board_firefly.c board_demo_ctl 38 led 0 off [16229.368193] /home/liu/rockchip/kernel/Drivers/4_led_firefly/led_firefly.c led_release 51 led device release ``` ###### 6、控制两个LED驱动 已知GPIO0_B5为DIY_LED,且实现了点亮 对于另一颗LED Work_Led,通过网络号查找电路原理图: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b04f0ab333844db8a476dba89662c078.png) 所以我们只需要配置GPIO2_D3的引脚即可。 ###### (1)寄存器分析: ​ 使能时钟引脚CRU(clock reset unit) ​ GPIO2:**CRU_CLKGATE_CON31** ```C #define PMUCRU_CLKGATE_CON31 (0xFF760000 + 0x037C) /*使能GPIO Clock*/ *PMUCRU_CLKGATE_CON31 = (1 << 3 + 16) | (0 << 3); ``` ​ 设置GPIO功能IOMUX ​ GPIO2_D3:**GRF_GPIO2D_IOMUX** ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e7289937a00f4a4480a9e5cfbff290d0.png) ```C #define GRF_GPIO2D_IOMUX (0xFF770000 + 0x0e00c); /*设置引脚为GPIO引脚功能*/ *PMUGRF_GPIO2D_IOMUX = (3 << 6 + 16) | (0 << 6); ``` ​ 设置GPIO输出GPIO方向 ​ **GPIO_SWPORTA_DR** ​ D3: \<==\> GPIO2的第 24 + 3 = 27 个引脚 ​ 基地址如下 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c628025b0a494995a325b9c5147071b5.png) ```C #define GPIO_SWPORTA_DR (0XFF780000 + 0x0000); /*输出高电平*/ (*GPIO_SWPORTA_DR) | (1 << 27); /*输出低电平*/ *GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 27)); ``` ​ 设置GPIO data寄存器 ​ **GPIO_SWPORTA_DDR** ```C #define GPIO_SWPORTA_DDR (0XFF780000 + 0x0004); *GPIO2_SWPORTA_DDR = (*GPIO2_SWPORTA_DDR) | (1 << 27); ``` ###### (2)驱动修改 修改 board_firefly.c函数实现程序 ```C #include #include #include #include #include #include #include #include "led_operation.h" static volatile unsigned int *PMUCRU_CLKGATE_CON1; static volatile unsigned int *PMUGRF_GPIO0B_IOMUX; static volatile unsigned int *GPIO_SWPORTA_DR; static volatile unsigned int *GPIO_SWPORTA_DDR; static volatile unsigned int *PMUCRU_CLKGATE_CON31; static volatile unsigned int *PMUGRF_GPIO2D_IOMUX; static volatile unsigned int *GPIO2_SWPORTA_DR; static volatile unsigned int *GPIO2_SWPORTA_DDR; // static volatile unsigned int *GPIO_EXT_PORTA; static int board_demo_init(int witch) { printk("%s %s %d led %d\r\n", __FILE__, __FUNCTION__, __LINE__, witch); if(witch == 0) { /* GPIO0_B5 */ if(!PMUCRU_CLKGATE_CON1) PMUCRU_CLKGATE_CON1 = ioremap(0xFF760000 + 0x0104, 4); if(!PMUGRF_GPIO0B_IOMUX) PMUGRF_GPIO0B_IOMUX = ioremap(0xFF320000 + 0x0004, 4); if(!GPIO_SWPORTA_DR) GPIO_SWPORTA_DR = ioremap(0XFF720000 + 0x0000, 4); if(!GPIO_SWPORTA_DDR) GPIO_SWPORTA_DDR = ioremap(0XFF720000 + 0x0004, 4); /* GPIO0_B5 operations register */ *PMUCRU_CLKGATE_CON1 = (1 << (3 + 16)) | (0 << 3); *PMUGRF_GPIO0B_IOMUX = (3 << (10 + 16)) | (0 << 10); *GPIO_SWPORTA_DDR = (*GPIO_SWPORTA_DDR) | (1 << 13); } /* 新增work_led */ if(witch == 1) { /* GPIO2_D3 */ if(!PMUCRU_CLKGATE_CON31) PMUCRU_CLKGATE_CON31 = ioremap(0xFF760000 + 0x037C, 4); if(!PMUGRF_GPIO2D_IOMUX) PMUGRF_GPIO2D_IOMUX = ioremap(0xFF770000 + 0x0e00c, 4); if(!GPIO2_SWPORTA_DR) GPIO2_SWPORTA_DR = ioremap(0XFF780000 + 0x0000, 4); if(!GPIO2_SWPORTA_DDR) GPIO2_SWPORTA_DDR = ioremap(0XFF780000 + 0x0004, 4); /* GPIO2_D3 operations register */ *PMUCRU_CLKGATE_CON31 = (1 << (3 + 16)) | (0 << 3); *PMUGRF_GPIO2D_IOMUX = (3 << (6 + 16)) | (0 << 6); *GPIO2_SWPORTA_DDR = (*GPIO2_SWPORTA_DDR) | (1 << 27); } return 0; } static int board_demo_ctl(int witch, char status) { printk("%s %s %d led %d %s\r\n", __FILE__, __FUNCTION__, __LINE__, witch, status ? "on" : "off"); if(witch == 0) { if(status) *GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) | (1 << 13); else *GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 13)); } /* 新增work_led */ else if(witch == 1) { if(status) *GPIO2_SWPORTA_DR = (*GPIO2_SWPORTA_DR) | (1 << 27); else *GPIO2_SWPORTA_DR = (*GPIO2_SWPORTA_DR) & (~(1 << 27)); } return 0; } static int board_demo_exit(int witch) { if(witch == 0) { iounmap(PMUCRU_CLKGATE_CON1); iounmap(PMUGRF_GPIO0B_IOMUX); iounmap(GPIO_SWPORTA_DR); iounmap(GPIO_SWPORTA_DDR); } /* 新增work_led*/ else if(witch == 1) { iounmap(PMUCRU_CLKGATE_CON31); iounmap(PMUGRF_GPIO2D_IOMUX); iounmap(GPIO2_SWPORTA_DR); iounmap(GPIO2_SWPORTA_DDR); } return 0; } static struct led_operations board_demo_led_opr = { /* 创建两个子设备节点 */ .num = 2, .init = board_demo_init, .ctl = board_demo_ctl, /* 取消映射 */ .exit = board_demo_exit, }; struct led_operations *get_board_led_opr(void) { return &board_demo_led_opr; } ``` 修改board_firefly.c的头文件:led_operation.h ```C #ifndef _LED_OPR_H #define _LED_OPR_H struct led_operations { int num; int (*init) (int which); int (*ctl) (int which, char status); /*增加iounmap*/ int (*exit) (int which); }; struct led_operations *get_board_led_opr(void); #endif ``` 修改led_firefly.c字符设备驱动程序 ```C #include #include #include #include #include #include #include #include "led_operation.h" #define LED_NAME "diy_led" static int major = 0; static struct class *led_class; struct led_operations *p_ledopr; static int led_open(struct inode *inode, struct file *file) { int minor = iminor(inode); printk("%s %s %d led device open\r\n", __FILE__, __FUNCTION__, __LINE__); /*Init LED device by sub-device number */ p_ledopr->init(minor); return 0; } static ssize_t led_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) { printk("%s %s %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static ssize_t led_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt) { int err; char status; struct inode *inode = file_inode(file); int minor = iminor(inode); printk("%s %s %d led device write\r\n", __FILE__, __FUNCTION__, __LINE__); err = copy_from_user(&status, buf, 1); /*Control LED by status and sub-device number.*/ p_ledopr->ctl(minor, status); return 1; } static int led_release(struct inode *inode, struct file *file) { int minor = iminor(inode); printk("%s %s %d led device release\r\n", __FILE__, __FUNCTION__, __LINE__); //iounmap 对应的设备子节点 p_ledopr->exit(minor); return 0; } const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .read = led_read, .write = led_write, }; static int __init led_init(void) { int i = 0; printk("led init.\r\n"); major = register_chrdev(0, LED_NAME, &led_fops); led_class = class_create(THIS_MODULE, "LED_CLASS"); p_ledopr = get_board_led_opr(); /*创建两个设备子节点*/ for(i=0; inum; i++) { device_create(led_class, NULL, MKDEV(major, i), NULL, "diy_led_%d", i); } return 0; } static void __exit led_exit(void) { int i = 0; printk("led exit!!!\r\n"); /* 销毁两个设备子节点 */ for(i=0; inum; i++) { device_destroy(led_class, MKDEV(major, i)); } class_destroy(led_class); unregister_chrdev(major, LED_NAME); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("liu"); MODULE_INFO(intree, "Y"); ``` ###### (3)编译 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- ###### (4)测试 root@liu:/home/zhisensor# rmmod leds_firefly.ko root@liu:/home/zhisensor# insmod leds_firefly.ko root@liu:/home/zhisensor# ./leds_ctl /dev/diy_led_0 off I'll close led root@liu:/home/zhisensor# ./leds_ctl /dev/diy_led_0 on I'll open led root@liu:/home/zhisensor# ./leds_ctl /dev/diy_led_1 on I'll open led root@liu:/home/zhisensor# ./leds_ctl /dev/diy_led_1 off 内核日志: [ 1583.113405] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_open 21 led device open [ 1583.113435] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_init 24 led 0 [ 1583.113737] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_write 41 led device write [ 1583.113744] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_ctl 59 led 0 off [ 1583.113753] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_release 51 led device release [ 1585.539956] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_open 21 led device open [ 1585.539986] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_init 24 led 0 [ 1585.540399] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_write 41 led device write [ 1585.540423] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_ctl 59 led 0 on [ 1585.540447] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_release 51 led device release [ 1591.806877] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_open 21 led device open [ 1591.806890] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_init 24 led 1 [ 1591.807037] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_write 41 led device write [ 1591.807044] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_ctl 59 led 1 on [ 1591.807053] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_release 51 led device release [ 1595.141492] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_open 21 led device open [ 1595.141521] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_init 24 led 1 [ 1595.141856] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_write 41 led device write [ 1595.141863] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_ctl 59 led 1 off [ 1595.141872] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_release 51 led device release ##### 五、应用层控制两个LED灯交替闪烁 ###### 花样彩灯 ```C #include #include #include #include #include #include int main(int argc, const char *argv[]) { int fd_led1, fd_led2; unsigned char led1, led2; // Open the LED0 files fd_led1 = open("/dev/diy_led_0", O_WRONLY); if (fd_led1 < 0) { printf("Error opening /dev/diy_led_0\n"); return 1; } // Open the LED1 files fd_led2 = open("/dev/diy_led_1", O_WRONLY); if (fd_led2 < 0) { printf("Error opening /dev/diy_led_1\n"); return 1; } led1 = 0; led2 = 0; int mode = 0; while(1) { // Set LED0 to ON if(mode == 0) { mode = 30; } if(mode < 5) { led1 = !led1; write(fd_led1, &led1, 1); usleep(1000 * 100); // Set LED1 to OFF led2 = !led2; write(fd_led2, &led2, 1); usleep(1000 * 100); } else if (mode < 10) { led1 = !led1; write(fd_led1, &led1, 1); // Set LED1 to OFF led2 = !led2; write(fd_led2, &led2, 1); usleep(1000 * 100); } else if (mode < 15) { led1 = !led1; write(fd_led1, &led1, 1); usleep(1000 * 100); } else if (mode <= 20) { led2 = !led2; write(fd_led2, &led2, 1); usleep(1000 * 100); } else if (mode < 30) { led1 = !led1; write(fd_led1, &led1, 1); // Set LED1 to OFF led2 = !led2; write(fd_led2, &led2, 1); usleep(1000 * 300); } mode--; } close(fd_led1); close(fd_led2); return 0; } ``` ##### 六、错误排查 多次测试报错如下 /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/led_firefly.c led_open 21 led device open [ 7407.030865] /home/liu/rockchip/kernel/Drivers/5_double_led_firefly/board_firefly.c board_demo_init 24 led 0 [ 7407.031756] Unable to handle kernel paging request at virtual address ffffff800b2aa104 [ 7407.032466] pgd = ffffffc076bb9000 [ 7407.032768] [ffffff800b2aa104] *pgd=0000000000000000, *pud=0000000000000000 [ 7407.033416] Internal error: Oops: 96000047 [#4] SMP [ 7407.033844] Modules linked in: leds_firefly [ 7407.034235] CPU: 4 PID: 1247 Comm: blingblingled Tainted: G D 4.4.194 #2 [ 7407.034925] Hardware name: Rockchip RK3399 Firefly Board (Linux Opensource) (DT) [ 7407.035579] task: ffffffc07836de80 task.stack: ffffffc07255c000 [ 7407.036107] PC is at board_demo_init+0xcc/0x1e0 [leds_firefly] [ 7407.036627] LR is at board_demo_init+0x30/0x1e0 [leds_firefly] 修改board_firefly.c函数映射和取消映射代码 改为安装驱动时映射寄存器,卸载驱动时取消映射寄存器 ```C #include #include #include #include #include #include #include #include "led_operation.h" static volatile unsigned int *PMUCRU_CLKGATE_CON1; static volatile unsigned int *PMUGRF_GPIO0B_IOMUX; static volatile unsigned int *GPIO_SWPORTA_DR; static volatile unsigned int *GPIO_SWPORTA_DDR; static volatile unsigned int *PMUCRU_CLKGATE_CON31; static volatile unsigned int *PMUGRF_GPIO2D_IOMUX; static volatile unsigned int *GPIO2_SWPORTA_DR; static volatile unsigned int *GPIO2_SWPORTA_DDR; // static volatile unsigned int *GPIO_EXT_PORTA; static int board_demo_init(void) { printk("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__); /* GPIO0_B5 */ if(!PMUCRU_CLKGATE_CON1) PMUCRU_CLKGATE_CON1 = ioremap(0xFF760000 + 0x0104, 4); else printk(KERN_ERR "Failed to map PMUCRU_CLKGATE_CON1\r\n"); if(!PMUGRF_GPIO0B_IOMUX) PMUGRF_GPIO0B_IOMUX = ioremap(0xFF320000 + 0x0004, 4); else printk(KERN_ERR "Failed to map PMUGRF_GPIO0B_IOMUX\r\n"); if(!GPIO_SWPORTA_DR) GPIO_SWPORTA_DR = ioremap(0XFF720000 + 0x0000, 4); else printk(KERN_ERR "Failed to map GPIO_SWPORTA_DR\r\n"); if(!GPIO_SWPORTA_DDR) GPIO_SWPORTA_DDR = ioremap(0XFF720000 + 0x0004, 4); else printk(KERN_ERR "Failed to map GPIO_SWPORTA_DDR\r\n"); /* GPIO0_B5 operations register */ *PMUCRU_CLKGATE_CON1 = (1 << (3 + 16)) | (0 << 3); *PMUGRF_GPIO0B_IOMUX = (3 << (10 + 16)) | (0 << 10); *GPIO_SWPORTA_DDR = (*GPIO_SWPORTA_DDR) | (1 << 13); /* GPIO2_D3 */ if(!PMUCRU_CLKGATE_CON31) PMUCRU_CLKGATE_CON31 = ioremap(0xFF760000 + 0x037C, 4); else printk(KERN_ERR "Failed to map PMUCRU_CLKGATE_CON31\r\n"); if(!PMUGRF_GPIO2D_IOMUX) PMUGRF_GPIO2D_IOMUX = ioremap(0xFF770000 + 0x0e00c, 4); else printk(KERN_ERR "Failed to map PMUGRF_GPIO2D_IOMUX\r\n"); if(!GPIO2_SWPORTA_DR) GPIO2_SWPORTA_DR = ioremap(0XFF780000 + 0x0000, 4); else printk(KERN_ERR "Failed to map GPIO2_SWPORTA_DR\r\n"); if(!GPIO2_SWPORTA_DDR) GPIO2_SWPORTA_DDR = ioremap(0XFF780000 + 0x0004, 4); else printk(KERN_ERR "Failed to map GPIO2_SWPORTA_DDR\r\n"); /* GPIO2_D3 operations register */ *PMUCRU_CLKGATE_CON31 = (1 << (3 + 16)) | (0 << 3); *PMUGRF_GPIO2D_IOMUX = (3 << (6 + 16)) | (0 << 6); *GPIO2_SWPORTA_DDR = (*GPIO2_SWPORTA_DDR) | (1 << 27); return 0; } static int board_demo_ctl(int witch, char status) { printk("%s %s %d led %d %s\r\n", __FILE__, __FUNCTION__, __LINE__, witch, status ? "on" : "off"); if(witch == 0) { if(status) *GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) | (1 << 13); else *GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 13)); } else if(witch == 1) { if(status) *GPIO2_SWPORTA_DR = (*GPIO2_SWPORTA_DR) | (1 << 27); else *GPIO2_SWPORTA_DR = (*GPIO2_SWPORTA_DR) & (~(1 << 27)); } return 0; } static int board_demo_exit(void) { iounmap(PMUCRU_CLKGATE_CON1); iounmap(PMUGRF_GPIO0B_IOMUX); iounmap(GPIO_SWPORTA_DR); iounmap(GPIO_SWPORTA_DDR); iounmap(PMUCRU_CLKGATE_CON31); iounmap(PMUGRF_GPIO2D_IOMUX); iounmap(GPIO2_SWPORTA_DR); iounmap(GPIO2_SWPORTA_DDR); printk("led io unmap end\\r\n"); return 0; } static struct led_operations board_demo_led_opr = { .num = 2, .init = board_demo_init, .ctl = board_demo_ctl, .exit = board_demo_exit, }; struct led_operations *get_board_led_opr(void) { return &board_demo_led_opr; } ``` 修改board_firefly.c的头文件 ```C #ifndef _LED_OPR_H #define _LED_OPR_H struct led_operations { int num; int (*init) (void); int (*ctl) (int which, char status); int (*exit) (void); }; struct led_operations *get_board_led_opr(void); #endif ``` 修改led_firefly.c 改为安装驱动时映射寄存器,卸载驱动时取消映射寄存器 ```C #include #include #include #include #include #include #include #include "led_operation.h" #define LED_NAME "diy_led" // #define LED_NUM 2 static int major = 0; static struct class *led_class; struct led_operations *p_ledopr; static int led_open(struct inode *inode, struct file *file) { printk("%s %s %d led device open\r\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static ssize_t led_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt) { printk("%s %s %d led device read\r\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static ssize_t led_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt) { int err; char status; struct inode *inode = file_inode(file); int minor = iminor(inode); printk("%s %s %d led device write\r\n", __FILE__, __FUNCTION__, __LINE__); err = copy_from_user(&status, buf, 1); /*Control LED by status and sub-device number.*/ p_ledopr->ctl(minor, status); return 1; } static int led_release(struct inode *inode, struct file *file) { printk("%s %s %d led device release\r\n", __FILE__, __FUNCTION__, __LINE__); return 0; } const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .read = led_read, .write = led_write, }; static int __init led_init(void) { int i = 0; printk("led init.\r\n"); major = register_chrdev(0, LED_NAME, &led_fops); led_class = class_create(THIS_MODULE, "LED_CLASS"); p_ledopr = get_board_led_opr(); for(i=0; inum; i++) { device_create(led_class, NULL, MKDEV(major, i), NULL, "diy_led_%d", i); } /*Init LED device by sub-device number */ p_ledopr->init(); return 0; } static void __exit led_exit(void) { int i = 0; printk("led exit!!!\r\n"); for(i=0; inum; i++) { device_destroy(led_class, MKDEV(major, i)); } class_destroy(led_class); unregister_chrdev(major, LED_NAME); p_ledopr->exit(); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("liu"); MODULE_INFO(intree, "Y"); ``` 具体修改位置,请使用代码比较工具(如diffuse)查看。

相关推荐
昊虹AI笔记23 分钟前
使用STM32CubeMX和Keil在STM32上创建并运行一个简单的FreeRTOS多任务程序
stm32·单片机·嵌入式硬件
共享家952740 分钟前
深入剖析Linux常用命令,助力高效操作
linux·运维·服务器
王光环41 分钟前
单片机使用printf,不用微库
单片机·嵌入式硬件
跳跳糖炒酸奶1 小时前
第四章、Isaacsim在GUI中构建机器人(1): 添加简单对象
人工智能·python·ubuntu·机器人
Zfox_1 小时前
【C++项目】从零实现RPC框架「四」:业务层实现与项目使用
linux·开发语言·c++·rpc·项目
LS_learner1 小时前
小智机器人关键函数解析,Application::OutputAudio()处理音频数据的输出的函数
人工智能·嵌入式硬件
吃旺旺雪饼的小男孩1 小时前
Ubuntu 22.04 安装和运行 EDK2 超详细教程
linux·运维·ubuntu
IT小馋猫1 小时前
Linux 企业项目服务器组建(附脚本)
linux·服务器·网络
阿政一号2 小时前
Linux进程间通信:【目的】【管道】【匿名管道】【命名管道】【System V 共享内存】
linux·运维·服务器·进程间通信
西城微科方案开发2 小时前
体重秤PCBA电路方案组成结构
单片机·嵌入式硬件