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)

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

模块 基地址 寄存器 偏移地址 备注
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)查看。

相关推荐
滴水之功1 小时前
VMware OpenWrt怎么桥接模式联网
linux·openwrt
枯无穷肉1 小时前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
ldinvicible1 小时前
How to run Flutter on an Embedded Device
linux
不过四级不改名6771 小时前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式科普2 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
嵌入式大圣2 小时前
单片机UDP数据透传
单片机·嵌入式硬件·udp
YRr YRr2 小时前
解决Ubuntu 20.04上编译OpenCV 3.2时遇到的stdlib.h缺失错误
linux·opencv·ubuntu
认真学习的小雅兰.2 小时前
如何在Ubuntu上利用Docker和Cpolar实现Excalidraw公网访问高效绘图——“cpolar内网穿透”
linux·ubuntu·docker
云山工作室2 小时前
基于单片机的视力保护及身姿矫正器设计(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·毕设
嵌入式-老费2 小时前
基于海思soc的智能产品开发(mcu读保护的设置)
单片机·嵌入式硬件