文章目录
- 一、电路分析
- 引脚配置功能(R/W register)
- 二、RK3399数据手册分析:
- 1、GPIO(General-purpose input/output)介绍:
- 2、CRU(Clock & Reset Unit)介绍
- 查找GPIO相关内容:
- 3、PMU(Power Management Uni)
- 4、GRF(General Register Files)
- 三、地址映射
- 1、ioremap函数
- 2、iounmap函数
- 3、读操作函数
- 4、写操作函数(地址为虚拟地址)
- 四、驱动编写
- 1、框架编写
- 编译
- 2、应用层框架
- 编译
- 执行
- 3、基于Firefly-RK3399开发板编写字符设备驱动
- (1)流程
- (2)寄存器分析
- (3)代码编写(单LED驱动代码)
- 4、编译LED-filrefly驱动
- 5、测试
- 6、控制两个LED驱动
- (1)寄存器分析:
- (2)驱动修改
- (3)编译
- (4)测试
- 五、应用层控制两个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)
为了便于后续驱动编写,整理表格如下
模块 | 基地址 | 寄存器 | 偏移地址 | 备注 |
---|---|---|---|---|
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 文件中,定义如下:
void __iomem *ioremap(resource_size_t res_cookie, size_t size);
res_cookie:要映射的物理起始地址。
size:要映射的内存空间大小。
返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。整页映射:4096字节
例如:
假如我们要获取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 函数原型如下:
void iounmap (volatile void __iomem *addr)
iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。假如我们现在要取消掉 GPIO_MODER_PI 寄存器的地址映射,使用如下代码即可:
iounmap(GPIO_MODER_PI);
申请到的虚拟地址有MMU(Memory manager UNIT)做隔离之后MMU进行地址映射,并且会做权限保护。
3、读操作函数
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、写操作函数(地址为虚拟地址)
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
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>#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<LED_NUM; i++) {/* MKDEV(major, i) 通过主设备号和次设备号生成设备号 "diy_led_%d", i 为设备节点名 */device_create(led_class, NULL, MKDEV(major, i), NULL, "diy_led_%d", i);}/*调用 board_demo 文件(见下文)中的 get_board_led_opr,参数为 void 返回值为struct led_operations *的函数结构体指针*/p_ledopr = get_board_led_opr();return 0;
}static void __exit led_exit(void)
{int i = 0;printk("led exit!!!\r\n");/* 销毁子设备节点 */for(i=0; i<LED_NUM; 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");
led_operations.h
#ifndef _LED_OPR_H
#define _LED_OPR_H struct led_operations {/* 函数指针init和ctl */int (*init) (int which);int (*ctl) (int which, char status);
};
/* 函数声明 */
struct led_operations *get_board_led_opr(void);#endif
board_demo.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>#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.obuild:kernel_moduleskernel_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
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>int main(int argc, const char *argv[]) {int fd; char status;int ret;if(argc != 3) {printf("para must rathor than three, Usage: ./%s <dev> <on/off>", 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位)置一
#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
需要给第10 11位写0,并且根据上面的写使能对应关系可以得知给26、27位置一。
#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 输出高电平
根据手册:需要将B5引脚置一,B5为GPIO0的第二组的第5个(0开始数)引脚,所以需要偏移 8 + 5 = 13
#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 引脚
同GPIO_SWPORTA_DR,将第13位置一设置为输出引脚
#define GPIO_SWPORTA_DDR (0XFF720000 + 0x0004);
GPIO_EXT_PORTA:读取 GPIO 引脚电平
#define GPIO_EXT_PORTA (0XFF720000 + 0x0050);
(3)代码编写(单LED驱动代码)
led_firefly.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>#include "led_operation.h"#define LED_NAME "diy_led"
// #define LED_NUM 2static 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; i<p_ledopr->num; 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; i<p_ledopr->num; 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
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>#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
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.obuild:kernel_moduleskernel_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灯的亮灭
内核打印如下:
[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,通过网络号查找电路原理图:
所以我们只需要配置GPIO2_D3的引脚即可。
(1)寄存器分析:
使能时钟引脚CRU(clock reset unit)
GPIO2:CRU_CLKGATE_CON31
#define PMUCRU_CLKGATE_CON31 (0xFF760000 + 0x037C)
/*使能GPIO Clock*/
*PMUCRU_CLKGATE_CON31 = (1 << 3 + 16) | (0 << 3);
设置GPIO功能IOMUX
GPIO2_D3:GRF_GPIO2D_IOMUX
#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 个引脚
基地址如下
#define GPIO_SWPORTA_DR (0XFF780000 + 0x0000);
/*输出高电平*/
(*GPIO_SWPORTA_DR) | (1 << 27);
/*输出低电平*/
*GPIO_SWPORTA_DR = (*GPIO_SWPORTA_DR) & (~(1 << 27));
设置GPIO data寄存器
GPIO_SWPORTA_DDR
#define GPIO_SWPORTA_DDR (0XFF780000 + 0x0004);
*GPIO2_SWPORTA_DDR = (*GPIO2_SWPORTA_DDR) | (1 << 27);
(2)驱动修改
修改 board_firefly.c函数实现程序
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>#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
#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字符设备驱动程序
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>#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; i<p_ledopr->num; 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; i<p_ledopr->num; 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灯交替闪烁
花样彩灯
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>int main(int argc, const char *argv[])
{int fd_led1, fd_led2;unsigned char led1, led2;// Open the LED0 filesfd_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 filesfd_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 ONif(mode == 0) {mode = 30;}if(mode < 5) {led1 = !led1;write(fd_led1, &led1, 1);usleep(1000 * 100);// Set LED1 to OFFled2 = !led2;write(fd_led2, &led2, 1);usleep(1000 * 100);}else if (mode < 10) {led1 = !led1;write(fd_led1, &led1, 1);// Set LED1 to OFFled2 = !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 OFFled2 = !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函数映射和取消映射代码
改为安装驱动时映射寄存器,卸载驱动时取消映射寄存器
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>#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);elseprintk(KERN_ERR "Failed to map PMUCRU_CLKGATE_CON1\r\n");if(!PMUGRF_GPIO0B_IOMUX)PMUGRF_GPIO0B_IOMUX = ioremap(0xFF320000 + 0x0004, 4);elseprintk(KERN_ERR "Failed to map PMUGRF_GPIO0B_IOMUX\r\n");if(!GPIO_SWPORTA_DR)GPIO_SWPORTA_DR = ioremap(0XFF720000 + 0x0000, 4); elseprintk(KERN_ERR "Failed to map GPIO_SWPORTA_DR\r\n");if(!GPIO_SWPORTA_DDR)GPIO_SWPORTA_DDR = ioremap(0XFF720000 + 0x0004, 4);elseprintk(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);elseprintk(KERN_ERR "Failed to map PMUCRU_CLKGATE_CON31\r\n");if(!PMUGRF_GPIO2D_IOMUX)PMUGRF_GPIO2D_IOMUX = ioremap(0xFF770000 + 0x0e00c, 4);elseprintk(KERN_ERR "Failed to map PMUGRF_GPIO2D_IOMUX\r\n");if(!GPIO2_SWPORTA_DR)GPIO2_SWPORTA_DR = ioremap(0XFF780000 + 0x0000, 4);elseprintk(KERN_ERR "Failed to map GPIO2_SWPORTA_DR\r\n");if(!GPIO2_SWPORTA_DDR)GPIO2_SWPORTA_DDR = ioremap(0XFF780000 + 0x0004, 4);elseprintk(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的头文件
#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
改为安装驱动时映射寄存器,卸载驱动时取消映射寄存器
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>#include "led_operation.h"#define LED_NAME "diy_led"
// #define LED_NUM 2static 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; i<p_ledopr->num; 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; i<p_ledopr->num; 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)查看。