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)查看。

相关推荐
梅秃头2 分钟前
CentOS 8修改Linux配置文件指定属性的值
linux·运维·服务器
一个小坑货5 分钟前
CentOS9 Stream上安装Edge浏览器
linux·centos
我是如子啊9 分钟前
【UBuntu20 配置usb网卡】 记录Ubuntu20配置usb网卡(特别是建立热点)
linux·网卡·教程·ubuntu20.04·wifi热点·ap·usb网卡
sdau_blue16 分钟前
pycharm远程服务器Linux下解决cannot connect to X server问题
linux·运维·服务器
CP-DD23 分钟前
linux 高级 I/O
linux
baidu_3755288124 分钟前
温度传感器 芯片ms5607 linux 驱动代码
linux·运维·服务器
言之。24 分钟前
linux 删除当前目录下修改时间超过1天的文件
linux·运维·服务器
陌夏微秋27 分钟前
Linux_01 Linux 系统文件
linux·运维·arm开发
江池俊36 分钟前
Linux上本地部署KubeSphere与cpolar实现远程管理和监控集群
linux·运维·服务器
茶馆大橘1 小时前
Docker部署学习
linux·运维·学习·docker·容器