Rk3568驱动开发_点亮led灯代码完善(手动挡)_6
1.实现思路:
应用层打开设备后通过write函数向内核中写值,1代表要打开灯,0代表要关闭灯
Linux配置gpio和控制gpio多了一个虚拟内存映射操作
2.注意事项:
配置和读写操作的时候要谨慎,比如先关掉gpio再注销掉虚拟内存否则照成内存泄漏设备直接死机(别问我怎么知道的)
3.实现:
驱动代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define LED_MAJOR 200
#define LED_NAME "led"
#define PMU_GRF_BASE (0xFDC20000)
#define PMU_GRF_GPIO0C_IOMUX_L (PMU_GRF_BASE + 0x0010)
#define PMU_GRF_GPIO0C_DS_0 (PMU_GRF_BASE + 0X0090)
#define GPIO0_BASE (0xFDD60000)
#define GPIO0_SWPORT_DR_H (GPIO0_BASE + 0X0004)
#define GPIO0_SWPORT_DDR_H (GPIO0_BASE + 0X000C)
#define LEDOPEN 1
#define LEDCLOSE 0
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *PMU_GRF_GPIO0C_IOMUX_L_PI;
static void __iomem *PMU_GRF_GPIO0C_DS_0_PI;
static void __iomem *GPIO0_SWPORT_DR_H_PI;
static void __iomem *GPIO0_SWPORT_DDR_H_PI;
// led gpio初始化操作
void gpio_init(void){
u32 val = 0;
// 设置GPIO0_c0为GPIO功能
val = readl(PMU_GRF_GPIO0C_IOMUX_L_PI);
val &= ~(0x7 << 0); //最低三位置0
val |= ((0x7 << 16) | (0x0 << 0)); // 16 17 18位置1其他不变,bit2:0:0,用作GPIO0_C0
writel(val, PMU_GRF_GPIO0C_IOMUX_L_PI);
// 设置GPIO_C0驱动能力为level5
val = readl(PMU_GRF_GPIO0C_DS_0_PI);
val &= ~(0x3f << 0); // 0 ~ 5置0
val |= ((0x3f << 16) | (0x3f << 0)); // 16 ~ 21置1,0~5置1同时用作GPIO0c0
writel(val, PMU_GRF_GPIO0C_DS_0_PI);
// 设置GPIOO0_c0为输出
val = readl(GPIO0_SWPORT_DDR_H_PI);
val &= ~(0x1 << 0); // 0置0
val |= ((0x1 << 16) | (0x1 << 0)); // 16置1,0置1
writel(val, GPIO0_SWPORT_DDR_H_PI);
// 设置GPIO_c0为低电平,关闭LED
val = readl(GPIO0_SWPORT_DR_H_PI);
val &= ~(0x1 << 0);
val |= ((0x1 << 16) | (0x0 << 0));
writel(val, GPIO0_SWPORT_DR_H_PI);
}
// 真实物理地址映射虚拟内存函数
void led_remap(void){
PMU_GRF_GPIO0C_IOMUX_L_PI = ioremap(PMU_GRF_GPIO0C_IOMUX_L, 4);
PMU_GRF_GPIO0C_DS_0_PI = ioremap(PMU_GRF_GPIO0C_DS_0, 4);
GPIO0_SWPORT_DR_H_PI = ioremap(GPIO0_SWPORT_DR_H, 4);
GPIO0_SWPORT_DDR_H_PI = ioremap(GPIO0_SWPORT_DDR_H, 4);
}
void led_releaseMap(void){
// 取消地址映射
iounmap(PMU_GRF_GPIO0C_IOMUX_L_PI);
iounmap(PMU_GRF_GPIO0C_DS_0_PI);
iounmap(GPIO0_SWPORT_DR_H_PI);
iounmap(GPIO0_SWPORT_DDR_H_PI);
}
void led_switch(int status){
u32 val = 0;
if(status == LEDOPEN){
// 开灯
val = readl(GPIO0_SWPORT_DR_H_PI);
val &= ~(0X1 << 0); /* bit0 清零*/
val |= ((0X1 << 16) | (0X1 << 0)); /* bit16 置1,允许写bit0,
bit0,高电平*/
writel(val, GPIO0_SWPORT_DR_H_PI);
}else if(status == LEDCLOSE){
// 关灯
val = readl(GPIO0_SWPORT_DR_H_PI);
val &= ~(0X1 << 0); /* bit0 清零*/
val |= ((0X1 << 16) | (0X0 << 0)); /* bit16 置1,允许写bit0,
bit0,低电平 */
writel(val, GPIO0_SWPORT_DR_H_PI);
}
}
static int led_open(struct inode* inode, struct file* filp){
return 0;
}
static int led_release(struct inode* inode, struct file* filp){
return 0;
}
static ssize_t led_write(struct file* filp, const char __user* buf, size_t count, loff_t* ppos){
int ret;
unsigned char databuf[1];
unsigned char state;
ret = copy_from_user(databuf, buf, count);
if(ret < 0){
printk("kernel write failed\r\n");
return -EFAULT;
}
state = databuf[0];
if(state == LEDOPEN){
led_switch(LEDOPEN);
}else if(state == LEDCLOSE){
led_switch(LEDCLOSE);
}
return 0;
}
/* 字符设备操作集*/
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_release,
};
/*注册驱动加载卸载*/
static int __init led_init(void){ // 入口
int ret = 0;
led_remap(); // 内存映射
gpio_init(); // 初始化led灯
ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops); // 注册字符设备
if(ret < 0){
printk("register chrdev failed!\r\n");
return -EIO;
}
printk("led_init\r\n");
return 0;
}
static void __exit led_exit(void){ // 出口
led_releaseMap(); // 注销内存映射
unregister_chrdev(LED_MAJOR, LED_NAME); // 注销
printk("led_exit\r\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Narnat");
应用层代码:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
int main(int argc, char* argv[]){
int fd, ret;
char* filename;
unsigned char databuf[1];
if(argc < 3){
printf("ERROR Usage\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]);
ret = write(fd, databuf, sizeof(databuf));
if(ret < 0){
printf("led control failed\r\n");
close(fd);
return -1;
}
close(fd);
return 0;
}
额外的:
配置了如下.vscode文件让内核函数能跳转到内核函数实现
c_cpp_properties.json:
{
"configurations": [
{
"name": "RK3568 Linux",
"includePath": [
"${workspaceFolder}/**",
// ARM64架构核心头文件
"/home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel/include",
"/home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel/arch/arm64/include",
// Rockchip芯片专用头文件
"/home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel/drivers/soc/rockchip",
// 编译生成的头文件(需先编译内核)
"/home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel/output/include/generated",
"/home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel/output/include/config"
],
"defines": [
"__KERNEL__", // 必须定义内核模式
"CONFIG_ARM64", // 明确ARM64架构
"CONFIG_ARCH_ROCKCHIP"
],
"compilerPath": "/usr/bin/aarch64-linux-gnu-gcc", // 交叉编译器
"cStandard": "gnu11",
"cppStandard": "gnu++14",
"intelliSenseMode": "linux-gcc-arm64"
}
],
"version": 4
}
setting.json:
{
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/*.o": true,
"**/*.su": true,
"**/*.cmd": true,
"Documentation": true
},
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/*.o": true,
"**/*.su": true,
"**/*.cmd": true,
"Documentation": true
},
"C_Cpp.intelliSenseEngineFallback": "Disabled",
"C_Cpp.intelliSenseEngine": "Tag Parser"
}
4.现象:
1为开灯0为关灯
5.额外补充:
由于最开始设备led灯会闪烁,应该是有这个驱动用echo 0 > /sys/class/leds/work/brightness命令关闭后再挂载驱动
一、open函数的特殊性:
内核默认实现,当驱动未显式实现file_operations结构体中的.open函数时,内核会自动提供一个默认的open函数实现。该默认实现仅完成以下操作:
分配文件描述符:在内核的文件描述符表中分配一个未使用的索引。
初始化文件对象:创建struct file对象并关联到设备的file_operations结构体。
权限校验:检查用户是否有权限访问设备(如设备文件的权限位)。
这解释了为什么用户层调用open(“/dev/led”, O_RDWR)即使驱动未实现.open也能成功返回文件描述符
open函数在驱动中属于可选实现,而write/read等函数则是必须实现的。如果驱动未实现write,用户层调用write会直接返回-EINVAL(无效操作)
open函数作用:
驱动的.open函数是设备初始化的入口,但不影响文件描述符的分配机制。文件描述符的返还是内核的默认行为,仅当驱动.open返回错误时,内核会撤销分配。