文章目录
-
-
-
- 一、电路分析
-
- [引脚配置功能(R/W register)](#引脚配置功能(R/W register))
- 二、RK3399数据手册分析:
-
- [1、GPIO(General-purpose input/output)介绍:](#1、GPIO(General-purpose input/output)介绍:)
- [2、CRU(Clock & Reset Unit)介绍](#2、CRU(Clock & Reset Unit)介绍)
- [3、PMU(Power Management Uni)](#3、PMU(Power Management Uni))
- [4、GRF(General Register Files)](#4、GRF(General Register Files))
- 三、地址映射
- 四、驱动编写
- 五、应用层控制两个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 文件中,定义如下:
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 <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
C
#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
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.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 <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位)置一
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
需要给第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 输出高电平
根据手册:需要将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 引脚
同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 <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 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; 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
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
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,通过网络号查找电路原理图:
所以我们只需要配置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
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 个引脚
基地址如下
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 <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
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 <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灯交替闪烁
花样彩灯
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_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 <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);
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 <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 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; 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)查看。