漫画做视频在线观看网站,新手搭建网站教程视频,wordpress仿天涯主题,删除不了wordpress若该文为原创文章#xff0c;转载请注明原文出处。 字符设备驱动程序的基本框架#xff0c;主要是如何申请及释放设备号、添加以及注销设备#xff0c;初始化、添加与删除 cdev 结构体#xff0c;并通过 cdev_init 函数建立 cdev 和 file_operations 之间的关联#xff0c…若该文为原创文章转载请注明原文出处。 字符设备驱动程序的基本框架主要是如何申请及释放设备号、添加以及注销设备初始化、添加与删除 cdev 结构体并通过 cdev_init 函数建立 cdev 和 file_operations 之间的关联cdev 结构体和 file_operations 结构体。 记录编写第一个真正的 Linux 字符设备驱动点灯。 使用的开发板是正点原子的ATK-DLRK3568. 一、查看原理图 通过查看原理图得知LED控制引脚接到了 GPIO0_C0上通过三极管控制LEDGPIO0_C0输出高电平LED亮输出低电平LED灭。 二、GPIO介绍
GPIO(General Purpose Input/Output Port)通用输入输出端口。
除作为一般的输入/输出功能外还可以配置为中断和模拟UART、CAN、PWM、I2C、SDMMC、CLK等功能。
比如 GPIO0_C0 这个 IO 就可以用 作GPIOPWM1_M0GPU_AVS 和 UART0_RX 这四个功能所以我们首先要设置好当前引 脚用作什么功能。
rk356x 系列对应的文档为:
• Rockchip_RK3568_TRM_Part1_xxx.pdf
• Rockchip_RK3566_Datasheet_xxx.pdf
• Rockchip_RK3568_Datasheet_xxx.pdf 1、GPIO分组
RK3568共160个GPIO引脚复用型引脚分为 5 组 (GPIO0~4)每组里面都有 32 个复 用型引脚而且又分为 4 个小组 (A、B、C、D)每个小组 8 个引脚 (0~7)。例如GPIO0_C7 是 GPIO0 大组第 3 个小组第 8 个引脚。 要查找GPIO对应的配置寄存器地址必须知道他属于哪个分组。 对于 LED 灯的控制进行控制也就是对上述 GPIO 的寄存器进行读写操作。 可大致分为以下几个 步骤 • 使能 GPIO 时钟 (默认开启不用设置) • 设置引脚复用为 GPIO(复位默认为 GPIO不用配置) • 设置引脚属性 (上下拉、速率、驱动能力, 默认) • 控制 GPIO 引脚为输出并输出高低电平 因为 GPIO 的时钟默认开启引脚默认复用为 GPIO我们只需要配置 GPIO 的引脚输入输出模式 及电平即可。 2、GPIO引脚号计算方法
pins 32*bank_num 8*group x
bank_num : 0 ~ 4对应GPIO 0~4
group : 0 ~ 3对应GPIO A~DGPIO0_C0: GPIO0_C2 32*0(bank_num) 8*2(group) 0 16根据计算GPIO0_C0序号为16。在后面驱动代码时会用到。 2、寄存器配置 这里以GPIO0_C0为例查看Rockchip_RK3568_TRM_Part1手册可知GPIO0 组复用功能是在 PMU_GRF 寄存器实验中需要对 GPIO 进行配置一般情况下需要对 GPIO 的复用寄存器方向寄存器数据寄存器进行配置, 和复用相关的总共 8 个寄存器。
1. 查找复用寄存器 搜索 GPIO0_C0,GPIO0_C0_sel 在 PMU_GRF_GPIO0C_IOMUX_H 上偏移地址为 0x0010。GPIO0_C0可以通过控制[2:0]位来选择复用为哪个功能我们要控制led 灯所以功能要复用为 GPIO。 复用寄存器的基地址: GPIO0_C0 设置为 GPIO所以 PMU_GRF_GPIO0C_IOMUX_L 的 bit2:0 这三位 设置 000。另外 bit18:16 要设置为 111允许写 bit2:0。 2. 查找方向寄存器 通过设置 GPIO 寄存器设置输入输出、高低电平、中断、抖动等一些引脚的驱动能力电 气属性等主要通过设置 General Register Files (GRF)(以 GPIO0 组为例详细自行参考 Rockchip_RK35xx_TRM_Part1 手册) • GPIO_SWPORT_DDR_L低位引脚数据方向寄存器控制输入或者输出。 • GPIO_SWPORT_DDR_H高位引脚数据方向寄存器控制输入或者输出。 通过寄存器描述该寄存器有高 16bit 和低 16bit高 16bit 控制低 16bit 的 写使能低 16bit 控制 GPIO 的输出方向 0输入1输出。 GPIO0_C0属于 GPIO0 中 A-D 组总计 64 个引脚中的高 32 引脚范围所以需要将 GPIO_SWPORT_DDR_H 寄存器的第 0bit 位和 16bit 位置 1允许写 bit16。 GPIO0~GPIO4 的基地址 GPIO_SWPORT_DDR_H 寄存器地址计算 Operational Base offset 0xFDD60000 0x000C 0xFDD6000C
3. GPIO 引脚高低电平设置 • GPIO_SWPORT_DR_L低位引脚数据寄存器设置高低电平。 • GPIO_SWPORT_DR_H高位引脚数据寄存器设置高低电平。 GPIO_SWPORT_DR_L 和 GPIO_SWPORT_DR_H 寄存器有高 16bit 和低 16bit高 16bit 控制低 16bit 的写 使能低 16bit 控制 GPIO 的高低电平。
GPIO0_C0属于 GPIO0 中 A-D 组总计 64 个引脚 中高的 32 引脚范围所以需要将 GPIO_SWPORT_DR_H 寄存器的第0bit 位和 16bit 位置 1。
4. 总结 复用关系寄存器的基地址为 0xFDC20000 偏移地址为 0x0010 所以要操作的地址为基地址偏移地址0xFDC20010 GPIO 的基地址为 0xFDD60000偏移地址为 0x000C 所以方向寄存器要操作的地址为基地址偏移地址0xFDD6000C GPIO 的基地址为 0xFDD60000偏移地址为 0x0004 所以数据寄存器要操作的地址为基地址偏移地址0xFDD60004 三、驱动程序编写
1、编写led_cdev.c驱动文件
#include linux/init.h
#include linux/module.h
#include linux/cdev.h
#include linux/fs.h
#include linux/uaccess.h
#include linux/io.h#define DEV_NAME led_chrdev
#define DEV_CNT (1)#define GPIO0_BASE (0xfdd60000)//每组GPIO,有2个寄存器,对应32个引脚每个寄存器负责16个引脚
//一个寄存器32位其中高16位都是使能位低16位对应16个引脚每个引脚占用1比特位
#define GPIO0_DR_L (GPIO0_BASE 0x0000)
#define GPIO0_DR_H (GPIO0_BASE 0x0004)#define GPIO0_DDR_L (GPIO0_BASE 0x0008)
#define GPIO0_DDR_H (GPIO0_BASE 0x000C)static dev_t devno;
struct class *led_chrdev_class;struct led_chrdev {struct cdev dev;unsigned int __iomem *va_dr; // 数据寄存器设置输出的电压unsigned int __iomem *va_ddr; // 数据方向寄存器设置输入或者输出unsigned int led_pin; // 偏移
};/* 打开设备函数 */
static int led_chrdev_open(struct inode *inode, struct file *filp)
{ unsigned int val 0;struct led_chrdev *led_cdev (struct led_chrdev *)container_of(inode-i_cdev, struct led_chrdev,dev);filp-private_data container_of(inode-i_cdev, struct led_chrdev, dev);printk(open\n);//设置输出模式val ioread32(led_cdev-va_ddr);val | ((unsigned int)0x1 (led_cdev-led_pin16));val | ((unsigned int)0X1 (led_cdev-led_pin));iowrite32(val,led_cdev-va_ddr);//输出高电平val ioread32(led_cdev-va_dr);val | ((unsigned int)0x1 (led_cdev-led_pin16));val | ((unsigned int)0x1 (led_cdev-led_pin));iowrite32(val, led_cdev-va_dr);return 0;
}static int led_chrdev_release(struct inode *inode, struct file *filp)
{return 0;
}/* 从设备读取数据 */
static ssize_t led_chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{ printk(This is led_chrdev_read\r\n);return 0;
}/* 向设备写入数据函数 */
static ssize_t led_chrdev_write(struct file *filp, const char __user * buf,size_t count, loff_t * ppos)
{unsigned long val 0;char ret 0;struct led_chrdev *led_cdev (struct led_chrdev *)filp-private_data;printk(write \n);get_user(ret, buf);val ioread32(led_cdev-va_dr);printk(val %lx\n, val);if (ret 0 || ret 0){val | ((unsigned int)0x1 (led_cdev-led_pin16));val ~((unsigned int)0x01 (led_cdev-led_pin)); /*设置GPIO引脚输出低电平*/}else{val | ((unsigned int)0x1 (led_cdev-led_pin16));val | ((unsigned int)0x01 (led_cdev-led_pin)); /*设置GPIO引脚输出高电平*/}iowrite32(val, led_cdev-va_dr);printk(val %lx\n, val);return count;
}/* 设备操作函数 */
static struct file_operations led_chrdev_fops {.owner THIS_MODULE, // 将 owner 字段指向本模块可以避免在模块的操作正在被使用时卸载该模块.open led_chrdev_open, // 将 open 字段指向 chrdev_open(...)函数.read led_chrdev_read, // 将 open 字段指向 chrdev_read(...)函数.write led_chrdev_write, // 将 open 字段指向 chrdev_write(...)函数.release led_chrdev_release, // 将 open 字段指向 chrdev_release(...)函数
};static struct led_chrdev led_cdev[DEV_CNT] {{.led_pin 0}, //偏移高16引脚,GPIO0_C0
};/* 驱动入口函数 */
static __init int led_chrdev_init(void)
{int i 0;dev_t cur_dev;printk(led_chrdev init (lubancat2 GPIO0_C7)\n);/*0 将物理地址转化为虚拟地址 */led_cdev[0].va_dr ioremap(GPIO0_DR_H, 4); //led_cdev[0].va_ddr ioremap(GPIO0_DDR_H, 4); // /*1 创建设备号 */alloc_chrdev_region(devno, 0, DEV_CNT, DEV_NAME);/*2 创建类 */led_chrdev_class class_create(THIS_MODULE, led_chrdev);for (; i DEV_CNT; i) {/*3 初始化 cdev */cdev_init(led_cdev[i].dev, led_chrdev_fops);led_cdev[i].dev.owner THIS_MODULE;/*4 获取主设备号和次设备号 */cur_dev MKDEV(MAJOR(devno), MINOR(devno) i);/*5 添加一个 cdev,完成字符设备注册到内核 */cdev_add(led_cdev[i].dev, cur_dev, 1);/*6 创建设备*/device_create(led_chrdev_class, NULL, cur_dev, NULL, DEV_NAME %d, i);}return 0;
}/* 驱动出口函数 */
static __exit void led_chrdev_exit(void)
{int i;dev_t cur_dev;printk(led chrdev exit (lubancat2 GPIO0_C7)\n);/*注销字符设备*/for (i 0; i DEV_CNT; i) {iounmap(led_cdev[i].va_dr); // 释放模式寄存器虚拟地址iounmap(led_cdev[i].va_ddr); // 释放输出类型寄存器虚拟地址}for (i 0; i DEV_CNT; i) {cur_dev MKDEV(MAJOR(devno), MINOR(devno) i);//删除设备device_destroy(led_chrdev_class, cur_dev);// 删除 cdevcdev_del(led_cdev[i].dev);}// 注销设备号unregister_chrdev_region(devno, DEV_CNT);// 删除类class_destroy(led_chrdev_class);}module_init(led_chrdev_init);
module_exit(led_chrdev_exit);MODULE_AUTHOR(yifeng);
MODULE_LICENSE(GPL);总结
模块加载
1、初始化 LED 灯结构体成员将物理寄存器的地址映射到虚拟地址空间
2、向动态申请一个设备号
3、创建设备类
4、绑定 led_cdev 与 led_chrdev_fops
5、注册设备
6、创建设备
模块卸载
1、删除设备
2、注销设备
3、释放被占用的设备号
2、编写makefile
KERNELDIR : /home/alientek/rk3568_linux_sdk/kernel
ARCHarm64
CROSS_COMPILE/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-export ARCH CROSS_COMPILECURRENT_PATH : $(shell pwd)
obj-m : led_cdev.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) clean编译 3、编写APP应用
ledApp.c
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.h
#include string.h#define LEDOFF 0
#define LEDON 1/** description : main主程序* param - argc : argv数组元素个数* param - argv : 具体参数* return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;unsigned char databuf[1];if(argc ! 3){printf(Error Usage!\r\n);return -1;}filename argv[1];/* 打开led驱动 */fd open(filename, O_RDWR);if(fd 0){printf(file %s open failed!\r\n, argv[1]);return -1;}databuf[0] atoi(argv[2]); /* 要执行的操作打开或关闭 *//* 向/dev/led文件写入数据 */retvalue write(fd, databuf, sizeof(databuf));if(retvalue 0){printf(LED Control Failed!\r\n);close(fd);return -1;}retvalue close(fd); /* 关闭文件 */if(retvalue 0){printf(file %s close failed!\r\n, argv[1]);return -1;}return 0;
}
编译
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ledApp.c -o ledApp
4、测试
测试有两个一是直接测试二是使用APP应用程序测试。
在测试前需要关闭心跳灯 echo none /sys/class/leds/work/trigger 加载LED驱动 insmod led_cdev.ko 测试方法一 直接给设备写入 1/0 来控制 LED 的亮灭 sh -c echo 0 /dev/led_chrdev0 sh -c echo 1 /dev/led_chrdev0 正点原子的LED是反的所以1是亮0是灭。 测试方法二 ./ledApp /dev/led 1 //打开 LED 灯 ./ledApp /dev/led 0 //关闭 LED 灯 经实验LED驱动工作正常。 卸载驱动 rmmod led_cdev 如有侵权或需要完整代码请及时联系博主。