文章目录
1. 字符设备驱动框架
1.1 框架简介
下图所展现的就是整个字符设备驱动开发的整体框架,简而言之,就是将驱动程序编入内核中,然后在用户态下即可像使用系统调用函数一样,通过传入相关参数就能够控制硬件设备。
1.2 如何实现自己的驱动
在做嵌入式驱动开发时,我们所需要明白的就是站在“巨人的肩膀上”,比如学习并理解Linux内核中驱动程序是如何运作的,参考其内核源码,然后去编写自己的驱动程序,这样在针对不同的芯片或者是开发板,便可以自己写出一套适应自己的驱动(重点是多参照Linux内核源码)。
2. 字符设备开发实验
2.1 环境搭建
- 开发板:I.mx6uLL
- 编译过后的Linux4.1.15源码
- Uboot 4.6源码、Linux4.1.15的镜像、根文件系统rootfs。
- Ubuntu16.04,vscode,nfs服务器,mobaXterm终端,tftp。
重点:上面的环境搭建所需要的均会放到Github以及百度网盘中,请参考下面链接,可自行下载。
链接:GitHub、百度网盘。
2.2 驱动实验1
2.2.1 需求
要求:
1. 实现open、read、write、release函数;
2. 在用户态下输入数据,传送到内核态下;
3. 将内核态的数据发送到用户态。
2.2.2 字符驱动源文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define CHRDEVBASE_MAJOR 200 // 设备号
#define CHRDEVBASE_NAME "chrdevbase" // 设备名
static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"kernel data"};
static int chrdevbase_open(struct inode *inode, struct file *file) {
return 0;
}
static int chrdevbase_release(struct inode *inode, struct file *file) {
return 0;
}
static ssize_t chrdevbase_read(struct file *filp, char __user *ubuf,
size_t len, loff_t *offset) {
int ret = 0;
memcpy(readbuf, kerneldata, sizeof(kerneldata));
ret = copy_to_user(ubuf, readbuf, sizeof(kerneldata)); // 将用户态数据拷贝到内核中
if(ret < 0) {
printk("copy data to user failed ...\r\n");
}
return 0;
}
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos) {
int ret = 0;
ret = copy_from_user(writebuf, buf, count); // 将内核态数据拷贝到用户态中
printk("data from user is %s\r\n", writebuf);
if(ret < 0) {
printk("copy data to kernel failed ...\r\n");
}
return 0;
}
// 操作函数集合
static const struct file_operations chrdev_base_fops = {
.owner = THIS_MODULE,
.open = chrdevbase_open,
.release = chrdevbase_release,
.read = chrdevbase_read,
.write = chrdevbase_write,
};
/*
*驱动入口
*/
static int __init chrdevbase_init(void) {
int ret = 0;
// 设备注册
ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME,
&chrdev_base_fops);
if(ret < 0) {
printk("chrdev register failed ...\r\n");
}
return 0;
}
/*
*驱动出口
*/
static void __exit chrdevbase_exit(void) {
unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
}
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
MODULE_LICENSE("GPL"); // 表明认可证书
MODULE_AUTHOR("wangyu"); // 作者
2.2.3 驱动测试程序
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define READDATA 1 // 读数据
#define WRITEDATA 2 // 写数据
static char readbuf[100];
static char writebuf[100];
static char userdata[100];
int main(int argc, char* argv[]) {
int ret = 0;
int fd = 0;
const char* filename = argv[1]; // 驱动文件路径
int value = atoi(argv[2]); // 控制读/写数据
if(argc != 3) {
return -1;
}
fd = open(filename, O_RDWR); // 获得文件描述符
if(value == 1) { // 读取数据
ret = read(fd, readbuf, sizeof(readbuf));
printf("read from kernel's data is %s\r\n", readbuf);
if(ret < 0) {
printf("read data failed ...\r\n");
}
}
if(value == 2) { // 写数据
printf("please input data ...\r\n");
scanf("%[^\n]", userdata);
ret = write(fd, userdata, sizeof(userdata));
if(ret < 0) {
printf("write data failed ...\r\n");
}
}
return 0;
}
驱动测试程序需要用到交叉编译工具,在linux中下载交叉编译工具链即可。
2.2.4 Makefile
KERNELDIR := /home/wy/linux/IMX6ULL/linux/alientek_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
用于将上面的字符驱动源文件编译为.ko模块。
2.2.5 json配置文件
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/wy/linux/IMX6ULL/linux/alientek_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include",
"/home/wy/linux/IMX6ULL/linux/alientek_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include",
"/home/wy/linux/IMX6ULL/linux/alientek_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
由于我开发使用的是Vscode,所以需要在.vscode中加入一个json文件用于导入l之前编译的Linux内核源码的路径。
2.3 驱动实验2
2.3.1 需求
要求:
1. 实现控制开发板上的LED灯;
2. 在用户态下调用write函数可以控制开发板上的LED灯。
2.3.2 LED驱动源文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.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 CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 地址映射后的虚拟地址 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
#define LEDOFF 0 /* 关闭 */
#define LEDON 1 /* 打开 */
/* LED灯打开/关闭 */
static void led_switch(s8 sta) {
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1 << 3); // bit3清零,打开LED灯
writel(val, GPIO1_DR);
} else if(sta == LEDOFF) {
val = readl(GPIO1_DR);
val |= 1 << 3; // bit3清零,关闭LED灯
writel(val, GPIO1_DR);
}
}
static int led_open(struct inode *inode, struct file *file) {
return 0;
}
static int led_release(struct inode *inode, struct file *file) {
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t * ppos) {
int retvalue = 0;
unsigned char databuf[1];
retvalue = copy_from_user(databuf, buf, count);
if(retvalue < 0) {
printk("kernel write falied ...\r\n");
return -EFAULT;
}
/* 判断是开灯还是关灯 */
led_switch(databuf[0]);
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_release,
.read = led_read,
};
// 驱动入口
static int __init led_init(void) {
int ret = 0;
unsigned int val = 0;
/* 初始化led灯,地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
/* 初始化 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);
val |= 3 << 26;
writel(val, IMX6U_CCM_CCGR1);
writel(0x5, SW_MUX_GPIO1_IO03); // 设置复用
writel(0x10B0, SW_PAD_GPIO1_IO03); // 设置电气属性
val = readl(GPIO1_GDIR);
val |= 1 << 3; // bit3置1,设置为输出
writel(val, GPIO1_GDIR);
led_switch(LEDOFF); // 默认LED灯处于关闭状态
/* 注册字符设备 */
ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
if(ret < 0) {
printk("register_chrdev falied ...\r\n");
return -EIO;
}
printk("led_init ...\r\n");
return 0;
}
// 驱动出口
static void __exit led_exit(void) {
led_switch(LEDOFF);
/* 注销地址映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 注销字符设备 */
unregister_chrdev(LED_MAJOR, LED_NAME);
printk("led_exit ...\r\n");
}
/* 注册驱动加载和卸载 */
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");
LED驱动源文件中所实现的是对寄存器的操作,通过操作寄存器来控制LED灯的亮灭。
2.3.3 LED驱动测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
/*
* argc: 应用程序参数个数
* argv[]: 具体的参数内容字符串形式
* ./ledAPP <filename> <0/1> 0表示关灯,1表示开灯
* ./ledAPP /dev/led 0 关灯
* ./ledAPP /dev/led 1 开灯
*/
#define LEDOFF 0
#define LEDON 1
int main(int argc, char* argv[]) {
int ret = 0;
int fd = 0;
const char* filename = argv[1];
unsigned char databuf[1];
if(argc != 3) {
printf("Error usage!");
return -1;
}
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("%s open failed ...\r\n", filename);
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;
}
测试写的LED驱动代码。
2.4 学习设备树
2.4.1 设备树基础
如下图所示,设备树简单理解就是用来标识不同的设备信息,这些设备信息可以是片上信息,亦可以是外设。
2.4.2 设备树实验
要求:在原来的设备树即开发板所对应的.dts文件中加入自己的设备树节点,,然后从编写驱动程序来读取节点的属性信息并打印出来。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#if 0
backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 5000000>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <7>;
status = "okay";
};
#endif
/*
* 模块入口
*/
static int __init dtsof_init(void) {
int ret = 0;
struct device_node *bl_nd = NULL; // 节点
struct property *comppro = NULL; // 属性
const char *str;
u32 def_value = 0;
u32 elemsize = 0;
u32 *brival;
u8 i = 0;
/* 找到backlight节点 */
bl_nd = of_find_node_by_path("/backlight");
if(bl_nd == NULL) {
ret = -EINVAL;
goto fail_findnd;
}
/* 获取属性 */
comppro = of_find_property(bl_nd, "compatible", NULL);
if(comppro == NULL) {
ret = -EINVAL;
goto fail_findpro;
} else {
printk("compatible = %s\r\n", (char*)comppro->value);
}
ret = of_property_read_string(bl_nd, "status", &str);
if(ret < 0) {
goto fail_rs;
} else {
printk("status = %s\r\n", str);
}
/* 读取数字属性值 */
ret = of_property_read_u32(bl_nd, "default-brightness-level", &def_value);
if(ret < 0) {
goto fail_read32;
} else {
printk("default-brightness-level = %d\r\n", def_value);
}
/* 获取数组类型的属性 */
elemsize = of_property_count_elems_of_size(bl_nd, "brightness-levels", sizeof(u32));
if(elemsize < 0) {
goto fail_readele;
} else {
printk("brightness-level = %d\r\n", ret);
}
/* 申请内存 */
brival = kmalloc(elemsize * sizeof(u32), GFP_KERNEL);
if(!brival) {
ret = -EINVAL;
goto fail_mem;
}
/* 获取数组 */
ret = of_property_read_u32_array(bl_nd, "brightness-levels", brival, elemsize);
if(ret < 0) {
goto fail_read32array;
} else {
for(i = 0; i < elemsize; i++) {
printk("brightness-levels[%d] = %d\r\n", i, *(brival + i));
}
}
kfree(brival);
return 0;
fail_read32array:
kfree(brival);
fail_mem:
fail_readele:
fail_read32:
fail_rs:
fail_findpro:
fail_findnd:
return ret;
}
/*
* 模块出口
*/
static void __exit dtsof_exit(void) {
}
/* 注册模块入口和出口 */
module_init(dtsof_init);
module_exit(dtsof_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");
2.5 驱动实验3
2.5.1 需求
前面所实现的LED驱动程序都是通过的自定义的宏来实现的,现在我们需要的就是通过在设备树中添加与LED相关的寄存器地址信息,然后去控制LED灯。
2.5.2 驱动源程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#define DTSLED_CNT 1 // 设备号个数
#define DTSLED_NAME "dtsled" // 名字
#define LEDOFF 0 // 关灯
#define LEDON 1 // 开灯
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
#if 0
alphaled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha, alphaled";
status = "okay";
reg = < 0X020C406C 0X04 /* CCM_CCGR1_BAE */
0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
0X0209C000 0X04 /* GPIO1_DR_BASE */
0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
};
#endif
/* dtsled设备结构体 */
struct dtsled_dev {
dev_t devid; // 设备号
struct cdev cdev; // 字符设备
struct class *class; // 类
struct device *device; // 设备
int major; // 主设备号
int minor; // 次设备号
struct device_node *nd; // 设备节点
};
struct dtsled_dev dtsled; // led设备
void led_switch(u8 sta) {
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}else if(sta == LEDOFF) {
val = readl(GPIO1_DR);
val|= (1 << 3);
writel(val, GPIO1_DR);
}
}
static int dtsled_open(struct inode *inode, struct file *filp) {
filp->private_data = &dtsled;
return 0;
}
static int dtsled_release(struct inode *inode, struct file *filp) {
// struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;
return 0;
}
static ssize_t dtsled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {
// struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == LEDON) {
led_switch(LEDON); /* 打开LED灯 */
} else if(ledstat == LEDOFF) {
led_switch(LEDOFF); /* 关闭LED灯 */
}
return 0;
}
/* dtsled字符设备操作集合 */
static const struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.write = dtsled_write,
.open = dtsled_open,
.release = dtsled_release,
};
/* 入口 */
static int __init dtsled_init(void) {
int ret = 0;
unsigned int val = 0;
const char* str;
u32 regdata[10];
u8 i = 0;
/* 注册字符设备 */
// 申请设备号
dtsled.major = 0; // 内核分配设备号
if(dtsled.major) {
dtsled.devid = MKDEV(dtsled.major, 0);
ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
} else { // 未给定设备号
ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);
dtsled.major = MAJOR(dtsled.devid);
dtsled.minor = MINOR(dtsled.devid);
}
if(ret < 0) {
goto fail_devid;
}
/* 添加字符设备 */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
if(ret < 0) {
goto fail_cdev;
}
/* 自动创建设备节点 */
dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
if(IS_ERR(dtsled.class)) {
ret = PTR_ERR(dtsled.class);
goto fail_class;
}
dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
if(IS_ERR(dtsled.device)) {
ret = PTR_ERR(dtsled.device);
goto fail_device;
}
/* 获取设备树属性内容 */
dtsled.nd = of_find_node_by_path("/alphaled");
if(dtsled.nd == NULL) {
ret = -EINVAL;
goto fail_findnd;
}
#if 0
/* 获取属性 */
ret = of_property_read_string(dtsled.nd, "status", &str);
if(ret < 0) {
goto fail_rs;
} else {
printk("status = %s\r\n", str);
}
ret = of_property_read_string(dtsled.nd, "compatible", &str);
if(ret < 0) {
goto fail_rs;
} else {
printk("compatible = %s\r\n", str);
}
ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
if(ret < 0) {
goto fail_rs;
} else {
printk("regdata: \r\n");
for(i = 0; i < 10; i++) {
printk("%#X ", regdata[i]);
}
printk("\r\n");
}
/* LED灯初始化 */
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
GPIO1_DR = ioremap(regdata[6], regdata[7]);
GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#endif
IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
GPIO1_DR = of_iomap(dtsled.nd, 3);
GPIO1_GDIR = of_iomap(dtsled.nd, 4);
/* 2、使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 清楚以前的设置 */
val |= (3 << 26); /* 设置新值 */
writel(val, IMX6U_CCM_CCGR1);
writel(5, SW_MUX_GPIO1_IO03); // 设置复用
writel(0x10B0, SW_PAD_GPIO1_IO03); // 设置电气属性
/* 设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); /* 清除以前的设置 */
val |= (1 << 3); /* 设置为输出 */
writel(val, GPIO1_GDIR);
/* 默认关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
return 0;
fail_rs:
fail_findnd:
device_destroy(dtsled.class, dtsled.devid);
fail_device:
class_destroy(dtsled.class);
fail_class:
cdev_del(&dtsled.cdev);
fail_cdev:
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
fail_devid:
return ret;
}
/* 出口 */
static void __exit dtsled_exit(void) {
unsigned int val = 0;
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
/* 释放内存映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 删除字符设备 */
cdev_del(&dtsled.cdev);
/* 释放设备号 */
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
/* 摧毁设备 */
device_destroy(dtsled.class, dtsled.devid);
/* 摧毁类 */
class_destroy(dtsled.class);
}
/* 注册和卸载驱动 */
module_init(dtsled_init);
module_exit(dtsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");
源程序实现的是两种方法来控制LED灯,一种是通过从设备树获取属性来,另一种则是通过使用of函数来实现物理地址到虚拟地址的映射,同时也获取到设备信息。
2.6 驱动实验4(pinctrl和gpio子系统)
2.6.1 需求
上一个实验我们用的是在设备树中添加相关的gpio的属性值,也就是相关的寄存器的地址,然后再通过of函数去获取属性值,进而去向寄存器中写入值来实现相应功能,但实际上,我们很少直接去操作寄存器,而是通过pinctrl和gpio子系统去实现功能。
2.6.2 驱动程序(控制LED灯和蜂鸣器)
简介:通过向设备树中加入gpioled标签和pinctrl标签。 {
filp->private_data = &gpioled;
return 0;
}
static int led_release(struct inode *inode, struct file *filp) {
// struct gpioled_dev *dev = (struct dtsled_dev*)filp->private_data;
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {
int ret;
unsigned char databuf[1];
struct gpioled_dev *dev = (struct gpioled_dev*)filp->private_data;
ret = copy_from_user(databuf, buf, cnt);
if(ret < 0) {
return -EINVAL;
}
if(databuf[0] == LEDON) {
gpio_set_value(dev->led_gpio, 0);
} else {
gpio_set_value(dev->led_gpio, 1);
}
return 0;
}
/* 操作集合 */
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
};
/* 驱动入口函数 */
static int __init led_init(void) {
int ret = 0;
/* 1. 注册字符设备驱动 */
gpioled.major = 0;
if(gpioled.major) { // 给定主设备号
gpioled.devid = MKDEV(gpioled.major, 0);
ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
} else { // 未给定设备号
ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
if(ret < 0) {
goto fail_devid;
}
printk("gpioled major = %d, minor = %d\r\n", gpioled.major, gpioled.minor);
/* 2. 初始化cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &led_fops);
/* 3. 添加cdev */
ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT); // 自己去进行错误处理
if(ret < 0) {
goto fail_cdev;
}
/* 4. 创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)) {
ret = PTR_ERR(gpioled.class);
goto fail_class;
}
/* 5. 创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)) {
ret = PTR_ERR(gpioled.device);
goto fail_device;
}
/* 1. 获取设备节点 */
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
ret = -EINVAL;
goto fail_findnode;
}
/* 2. 获取节点LED所对应的GPIO */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
if(gpioled.led_gpio < 0) {
printk("can't find led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
printk("led gpio num = %d\r\n", gpioled.led_gpio);
/* 3. 申请IO */
ret = gpio_request(gpioled.led_gpio, "led-gpios");
if(ret < 0) {
printk("Failed to request the led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
/* 4. 使用IO,设置为输出 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0) {
goto fail_setoutput;
}
/* 5. 输出低电平,点亮LED */
gpio_set_value(gpioled.led_gpio, 0);
return 0;
fail_setoutput:
gpio_free(gpioled.led_gpio);
fail_findnode:
fail_device:
class_destroy(gpioled.class);
fail_class:
cdev_del(&gpioled.cdev);
fail_cdev:
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
fail_devid:
return ret;
}
/* 驱动出口函数 */
static void __exit led_exit(void) {
/* 关灯 */
gpio_set_value(gpioled.led_gpio, 1);
/* 注销字符设备驱动 */
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
/* 释放IO */
gpio_free(gpioled.led_gpio);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");
注意:在编写这个驱动的时候我们要做好异常处理,同时在编写属性值的时候需要参照内核所编写好了,仿照它们的去编写。
2.7 驱动实验5(并发与竞争)
2.7.1 需求
学习Linux中的并发与竞争,知道什么是原子操作,掌握自旋锁、信号量、互斥锁等相关的锁。
2.7.2 驱动程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"
#define LEDON 1
#define LEDOFF 0
/* gpioled设备结构体 */
struct gpioled_dev {
dev_t devid; // 设备号
int major; // 主设备号
int minor; // 次设备号
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int led_gpio;
int dev_status; // 0表示设备可以使用,大于1表示不可使用
spinlock_t lock;
};
struct gpioled_dev gpioled; /* LED */
static int led_open(struct inode *inode, struct file *filp) {
unsigned long flag;
filp->private_data = &gpioled;
// spin_lock(&gpioled.lock);
spin_lock_irqsave(&gpioled.lock, flag);
if(gpioled.dev_status) { // 驱动不能使用
spin_unlock_irqrestore(&gpioled.lock, flag);
return -EBUSY;
}
gpioled.dev_status++; // 标记被使用
// spin_unlock(&gpioled.lock);
spin_unlock_irqrestore(&gpioled.lock, flag);
return 0;
}
static int led_release(struct inode *inode, struct file *filp) {
unsigned long flag;
struct gpioled_dev *dev = (struct gpioled_dev*)filp->private_data;
// spin_lock(&dev->lock);
spin_lock_irqsave(&dev->lock, flag);
if(dev->dev_status) {
gpioled.dev_status--; // 标记驱动可以使用
}
// spin_unlock(&dev->lock);
spin_unlock_irqrestore(&dev->lock, flag);
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {
int ret;
unsigned char databuf[1];
struct gpioled_dev *dev = (struct gpioled_dev*)filp->private_data;
ret = copy_from_user(databuf, buf, cnt);
if(ret < 0) {
return -EINVAL;
}
if(databuf[0] == LEDON) {
gpio_set_value(dev->led_gpio, 0);
} else {
gpio_set_value(dev->led_gpio, 1);
}
return 0;
}
/* 操作函数集合 */
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
};
/* 驱动入口函数 */
static int __init led_init(void) {
int ret = 0;
/* 初始化自旋锁 */
spin_lock_init(&gpioled.lock);
gpioled.dev_status = 0;
/* 1. 注册字符设备驱动 */
gpioled.major = 0;
if(gpioled.major) { // 给定主设备号
gpioled.devid = MKDEV(gpioled.major, 0);
ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
} else { // 未给定设备号
ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
if(ret < 0) {
goto fail_devid;
}
printk("gpioled major = %d, minor = %d\r\n", gpioled.major, gpioled.minor);
/* 2. 初始化cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &led_fops);
/* 3. 添加cdev */
ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT); // 自己去进行错误处理
if(ret < 0) {
goto fail_cdev;
}
/* 4. 创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)) {
ret = PTR_ERR(gpioled.class);
goto fail_class;
}
/* 5. 创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)) {
ret = PTR_ERR(gpioled.device);
goto fail_device;
}
/* 1. 获取设备节点 */
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
ret = -EINVAL;
goto fail_findnode;
}
/* 2. 获取节点LED所对应的GPIO */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
if(gpioled.led_gpio < 0) {
printk("can't find led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
printk("led gpio num = %d\r\n", gpioled.led_gpio);
/* 3. 申请IO */
ret = gpio_request(gpioled.led_gpio, "led-gpios");
if(ret < 0) {
printk("Failed to request the led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
/* 4. 使用IO,设置为输出 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0) {
goto fail_setoutput;
}
/* 5. 输出低电平,点亮LED */
gpio_set_value(gpioled.led_gpio, 0);
return 0;
fail_setoutput:
gpio_free(gpioled.led_gpio);
fail_findnode:
fail_device:
class_destroy(gpioled.class);
fail_class:
cdev_del(&gpioled.cdev);
fail_cdev:
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
fail_devid:
return ret;
}
/* 驱动出口函数 */
static void __exit led_exit(void) {
/* 关灯 */
gpio_set_value(gpioled.led_gpio, 1);
/* 注销字符设备驱动 */
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
/* 释放IO */
gpio_free(gpioled.led_gpio);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");
2.8 驱动实验6(定时器)
2.8.1 定时器版本1
2.8.1.1 需求
通过使用Linux内核中所实现的定时器来控制开发板上的LED灯,初步了解定时器的使用。
2.8.1.2 驱动程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#define TIMER_CNT 1
#define TIMER_NAME "timer"
/* timer设备结构体 */
struct timer_dev {
dev_t devid; // 设备号
int major; // 主设备号
int minor; // 次设备号
struct cdev cdev; // 字符设备
struct class *class;
struct device *device;
struct device_node *nd;
struct timer_list timer; // 定时器
int led_gpio; // led 的GPIO
};
struct timer_dev timerdev; /* 定时器 */
static int timer_open(struct inode *inode, struct file *filp) {
filp->private_data = &timerdev;
return 0;
}
static int timer_release(struct inode *inode, struct file *filp) {
// sstruct timer_dev *dev = (struct timer_dev*)filp->private_data;
return 0;
}
static ssize_t timer_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {
// sstruct timer_dev *dev = (struct timer_dev*)filp->private_data;
return 0;
}
/* 操作集合 */
static const struct file_operations timer_fops = {
.owner = THIS_MODULE,
.open = timer_open,
.write = timer_write,
.release = timer_release,
};
/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
struct timer_dev *dev = (struct timer_dev*)arg;
static int sta = 1;
sta = !sta;
gpio_set_value(dev->led_gpio, sta);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(500));
}
/* 初始化LED */
static int led_init(struct timer_dev *dev) {
int ret = 0;
dev->nd = of_find_node_by_path("/gpioled");
if(dev->nd == NULL) {
ret = -EINVAL;
goto fail_fd;
}
dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpios", 0);
if(dev->led_gpio < 0) {
ret = -EINVAL;
goto fail_gpio;
}
ret = gpio_request(dev->led_gpio, "led");
if(ret) {
ret = -EBUSY;
printk("IO %d can't request!\r\n", dev->led_gpio);
goto fail_request;
}
ret = gpio_direction_output(dev->led_gpio, 1); // 默认关灯
if(ret < 0) {
ret = -EINVAL;
goto fail_gpioset;
}
return 0;
fail_gpioset:
gpio_free(dev->led_gpio);
fail_request:
fail_gpio:
fail_fd:
return ret;
}
/* 驱动函数入口 */
static int __init timer_init(void) {
int ret = 0;
/* 1.注册字符设备驱动 */
timerdev.major = 0;
if(timerdev.major) { // 指定了主设备号
timerdev.devid = MKDEV(timerdev.major, 0);
ret = register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);
} else {
ret = alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME);
timerdev.major = MAJOR(timerdev.devid);
timerdev.minor = MINOR(timerdev.minor);
}
if(ret < 0) {
goto fail_devid;
}
printk("timer major = %d, minor = %d\r\n", timerdev.major, timerdev.minor);
/* 2.初始化cdev */
timerdev.cdev.owner = THIS_MODULE;
cdev_init(&timerdev.cdev, &timer_fops);
/* 3.添加cdev */
ret = cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
if(ret < 0) {
goto fail_cdev;
}
/* 4.创建类 */
timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
if(IS_ERR(timerdev.class)) {
ret = PTR_ERR(timerdev.class);
goto fail_class;
}
/* 5.创建设备 */
timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);
if(IS_ERR(timerdev.device)) {
ret = PTR_ERR(timerdev.device);
goto fail_device;
}
/* 6.初始化LED */
ret = led_init(&timerdev);
if(ret < 0) {
goto fail_ledinit;
}
/* 7.初始化定时器 */
init_timer(&timerdev.timer);
timerdev.timer.function = timer_func;
timerdev.timer.expires = jiffies + msecs_to_jiffies(500);
timerdev.timer.data = (unsigned long)&timerdev;
add_timer(&timerdev.timer); // 添加到系统
return 0;
fail_ledinit:
fail_device:
class_destroy(timerdev.class);
fail_class:
cdev_del(&timerdev.cdev);
fail_cdev:
unregister_chrdev_region(timerdev.devid, TIMER_CNT);
fail_devid:
return ret;
}
/* 驱动函数出口 */
static void __exit timer_exit(void) {
/* 关灯 */
gpio_set_value(timerdev.led_gpio, 1);
/* 删除定时器 */
del_timer(&timerdev.timer);
/* 注销字符设备驱动 */
cdev_del(&timerdev.cdev);
unregister_chrdev_region(timerdev.devid, TIMER_CNT);
device_destroy(timerdev.class, timerdev.devid);
class_destroy(timerdev.class);
/* 释放IO */
gpio_free(timerdev.led_gpio);
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");
2.8.2 定时器版本2
2.8.2.1 需求
本次需要实现的就是用户程序通过使用ioctl函数来控制LED灯,同时还可以调节灯闪烁的时间和周期,同时结合前面所学的,需要用到自旋锁来保护共享资源。
2.8.2.2 驱动程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#define TIMER_CNT 1 // 设备个数
#define TIMER_NAME "timer" // 名字
#if 0
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, __s64)
#define BINDER_SET_MAX_THREADS _IOW('b', 5, __u32)
#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, __s32)
#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, __s32)
#define BINDER_THREAD_EXIT _IOW('b', 8, __s32)
#define BINDER_VERSION _IOWR('b', 9, struct binder_version)
#endif
#define CLOSE_CMD _IO(0xEF, 1) // 关闭命令
#define OPEN_CMD _IO(0xEF, 2) // 打开命令
#define SETPERIOD_CMD _IOW(0xEF, 3, int) // 设置周期
/* timer设备结构体 */
struct timer_dev {
dev_t devid; // 设备号
int major; // 主设备号
int minor; // 次设备号
struct cdev cdev; // 字符设备
struct class *class; // 类
struct device *device; // 设备
struct device_node *nd; // 节点
struct timer_list timer; // 定时器
int timeperiod; // 定时周期ms
int led_gpio; // led 的GPIO
spinlock_t lock; // 自旋锁
};
struct timer_dev timerdev; /* 定时器 */
static int timer_open(struct inode *inode, struct file *filp) {
filp->private_data = &timerdev;
return 0;
}
static int timer_release(struct inode *inode, struct file *filp) {
// sstruct timer_dev *dev = (struct timer_dev*)filp->private_data;
return 0;
}
static long timer_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { // arg是应用传递给驱动的周期数据的首地址,占4字节
int ret = 0;
int value = 0;
int timerperiod;
unsigned long flags;
struct timer_dev *dev = (struct timer_dev*)file->private_data;
switch(cmd) {
case CLOSE_CMD:
del_timer_sync(&dev->timer);
break;
case OPEN_CMD:
spin_lock_irqsave(&dev->lock, flags); // 上锁
timerperiod = dev->timeperiod;
spin_unlock_irqrestore(&dev->lock, flags); // 解锁
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
break;
case SETPERIOD_CMD:
ret = copy_from_user(&value, (int *)arg, sizeof(int));
if(ret < 0) {
return -EFAULT;
}
spin_lock_irqsave(&dev->lock, flags);
dev->timeperiod = value; // 修改周期
spin_unlock_irqrestore(&dev->lock, flags);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
break;
default:
break;
}
return 0;
}
/* 操作集合 */
static const struct file_operations timer_fops = {
.owner = THIS_MODULE,
.open = timer_open,
.unlocked_ioctl = timer_ioctl,
.release = timer_release,
};
/* 定时器回调函数 */
static void timer_func(unsigned long arg) {
int timerperiod;
unsigned long flags;
struct timer_dev *dev = (struct timer_dev*)arg;
static int sta = 1;
sta = !sta;
gpio_set_value(dev->led_gpio, sta);
spin_lock_irqsave(&dev->lock, flags); // 上锁
timerperiod = dev->timeperiod;
spin_unlock_irqrestore(&dev->lock, flags); // 解锁
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod)); // 重启定时器
}
/* 初始化LED */
static int led_init(struct timer_dev *dev) {
int ret = 0;
dev->nd = of_find_node_by_path("/gpioled");
if(dev->nd == NULL) {
ret = -EINVAL;
goto fail_fd;
}
dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpios", 0);
if(dev->led_gpio < 0) {
ret = -EINVAL;
goto fail_gpio;
}
ret = gpio_request(dev->led_gpio, "led");
if(ret) {
ret = -EBUSY;
printk("IO %d can't request!\r\n", dev->led_gpio);
goto fail_request;
}
ret = gpio_direction_output(dev->led_gpio, 1); // 默认关灯
if(ret < 0) {
ret = -EINVAL;
goto fail_gpioset;
}
return 0;
fail_gpioset:
gpio_free(dev->led_gpio);
fail_request:
fail_gpio:
fail_fd:
return ret;
}
/* 驱动函数入口 */
static int __init timer_init(void) {
int ret = 0;
/* 1.注册字符设备驱动 */
timerdev.major = 0;
if(timerdev.major) { // 指定了主设备号
timerdev.devid = MKDEV(timerdev.major, 0);
ret = register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);
} else {
ret = alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME);
timerdev.major = MAJOR(timerdev.devid);
timerdev.minor = MINOR(timerdev.minor);
}
if(ret < 0) {
goto fail_devid;
}
printk("timer major = %d, minor = %d\r\n", timerdev.major, timerdev.minor);
/* 2.初始化cdev */
timerdev.cdev.owner = THIS_MODULE;
cdev_init(&timerdev.cdev, &timer_fops);
/* 3.添加cdev */
ret = cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
if(ret < 0) {
goto fail_cdev;
}
/* 4.创建类 */
timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
if(IS_ERR(timerdev.class)) {
ret = PTR_ERR(timerdev.class);
goto fail_class;
}
/* 5.创建设备 */
timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);
if(IS_ERR(timerdev.device)) {
ret = PTR_ERR(timerdev.device);
goto fail_device;
}
/* 6.初始化LED */
ret = led_init(&timerdev);
if(ret < 0) {
goto fail_ledinit;
}
/* 7.初始化定时器 */
init_timer(&timerdev.timer);
timerdev.timeperiod = 500;
timerdev.timer.function = timer_func;
timerdev.timer.expires = jiffies + msecs_to_jiffies(timerdev.timeperiod);
timerdev.timer.data = (unsigned long)&timerdev;
add_timer(&timerdev.timer); // 添加到系统
return 0;
fail_ledinit:
fail_device:
class_destroy(timerdev.class);
fail_class:
cdev_del(&timerdev.cdev);
fail_cdev:
unregister_chrdev_region(timerdev.devid, TIMER_CNT);
fail_devid:
return ret;
}
/* 驱动函数出口 */
static void __exit timer_exit(void) {
/* 关灯 */
gpio_set_value(timerdev.led_gpio, 1);
/* 删除定时器 */
del_timer(&timerdev.timer);
/* 注销字符设备驱动 */
cdev_del(&timerdev.cdev);
unregister_chrdev_region(timerdev.devid, TIMER_CNT);
device_destroy(timerdev.class, timerdev.devid);
class_destroy(timerdev.class);
/* 释放IO */
gpio_free(timerdev.led_gpio);
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");
该驱动程序是通过Linux内核的定时器来实现控制LED闪烁间隔不同。为了一直闪烁,则在定时器时间结束之后重启定时器。
2.8.2.3 用户测试程序
用户测试程序用来测试驱动程序的,用户只需要调用open、ioctl函数就可以控制LED灯。
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* argc: 应用程序参数个数
* argv: 具体的参数内容,字符串形式
* ./timerAPP <filename>
* ./timeAPP /dev/timer
*/
#define CLOSE_CMD _IO(0xEF, 1) // 关闭命令
#define OPEN_CMD _IO(0xEF, 2) // 打开命令
#define SETPERIOD_CMD _IOW(0xEF, 3, int) // 设置周期
int main(int argc, char* argv[]) {
int fd, retvalue;
char *filename;
unsigned char databuf[1];
unsigned int cmd;
unsigned int arg;
unsigned char str[100];
if(argc !=2) {
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", filename);
return -1;
}
while(1) {
printf("Input cmd:");
retvalue = scanf("%d", &cmd);
if(retvalue != 1) {
gets(str); // 防止卡死
}
if(cmd == 1) { // 关闭
ioctl(fd, CLOSE_CMD, &arg);
} else if(cmd == 2) { // 打开
ioctl(fd, OPEN_CMD, &arg);
} else if(cmd == 3) { // 设置周期
printf("Input timer period:");
retvalue = scanf("%d", &arg);
if(retvalue != 1) {
gets(str);
}
ioctl(fd, SETPERIOD_CMD, &arg);
}
}
close(fd);
return 0;
}
2.9 驱动实验7(中断)
2.9.1 基础知识
中断,简单来讲就是cpu停止正在做的事情去执行另外的任务,然后执行完之后回到中断前的状态继续执行任务,中断又分为硬件中断和软中断,软中断所针对的是SMP系统。
2.9.2 需求
本次实验的需求就是采用按键中断实验,分为三个版本,基础按键中断版本,定时器按键消抖,中断上下部分完善。
2.9.3 基础按键中断版本
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
/*
* timer_temp 文件是编写驱动的模板文件,以后均在这上面增加内容,
* 这个字符驱动框架必须熟悉。
*/
#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "imx6uirq"
#define KEY_NUM 1
#define KEY0VALUE 0x01
#define INVAKEY 0xFF
/* key结构体 */
struct irq_keydesc {
int gpio; // io编号
int irqnum; // 中断号
unsigned char value; // 键值
char name[10]; // 名字
irqreturn_t (*handler) (int, void*); // 中断处理函数
};
/* imx6uirq设备结构体 */
struct imx6uirq_dev {
dev_t devid; // 设备号
int major; // 主设备号
int minor; // 次设备号
struct cdev cdev; // 字符设备
struct class *class;
struct device *device;
struct device_node *nd;
struct irq_keydesc irqkey[KEY_NUM];
struct timer_list timer; // 定时器
atomic_t keyvalue;
atomic_t releasekey;
};
struct imx6uirq_dev imx6uirq; /* 中断 */
static int imx6uirq_open(struct inode *inode, struct file *filp) {
filp->private_data = &imx6uirq;
return 0;
}
static int imx6uirq_release(struct inode *inode, struct file *filp) {
// sstruct imx6uirq_dev *dev = (struct imx6uirq_dev*)filp->private_data;
return 0;
}
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
int ret = 0;
unsigned char keyvalue;
unsigned char releasekey;
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if(releasekey) { // 有效按键
if(keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0); // 按下标志清零
} else {
goto data_error;
}
return ret;
data_error:
return -EINVAL;
}
/* 操作集合 */
static const struct file_operations im6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.release = imx6uirq_release,
};
/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id) {
struct imx6uirq_dev *dev = dev_id;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); // 20ms定时
return IRQ_HANDLED;
}
/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
int value = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)arg;
value = gpio_get_value(dev->irqkey[0].gpio);
if(value == 0) { // 按下
atomic_set(&dev->keyvalue, dev->irqkey[0].value);
} else if(value == 1) { // 释放
atomic_set(&dev->keyvalue, 0x80 | dev->irqkey[0].value);
atomic_set(&dev->releasekey, 1); // 完整的按键过程
}
}
/* 按键初始化 */
static int keyio_init(struct imx6uirq_dev *dev) {
int ret = 0;
int i = 0;
/* 1.按键初始化 */
dev->nd = of_find_node_by_path("/key");
if(dev->nd == NULL) {
ret = -EINVAL;
goto fail_nd;
}
for(i = 0; i < KEY_NUM; i++) {
dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i); // 可以判断返回值
}
for(i = 0; i < KEY_NUM; i++) {
memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
sprintf(dev->irqkey[i].name, "KEY%d", i);
gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
gpio_direction_input(dev->irqkey[i].gpio);
dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); // 获取中断号
#if 0
dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i); // 获取中断号(通用)
#endif
}
dev->irqkey[0].handler = key0_handler; // 初始化
dev->irqkey[0].value = KEY0VALUE;
/* 2.按键中断初始化 */
for(i = 0; i < KEY_NUM; i++) {
ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev->irqkey[i].name, &imx6uirq);
if(ret) {
printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
goto fail_irq;
}
}
/* 3.初始化定时器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_func;
return 0;
fail_irq:
for(i = 0; i < KEY_NUM; i++) {
gpio_free(dev->irqkey[i].gpio);
}
fail_nd:
return ret;
}
/* 驱动函数入口 */
static int __init timer_init(void) {
int ret = 0;
/* 1.注册字符设备驱动 */
imx6uirq.major = 0;
if(imx6uirq.major) { // 指定了主设备号
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.minor);
}
if(ret < 0) {
goto fail_devid;
}
printk("timer major = %d, minor = %d\r\n", imx6uirq.major, imx6uirq.minor);
/* 2.初始化cdev */
imx6uirq.cdev.owner = THIS_MODULE;
cdev_init(&imx6uirq.cdev, &im6uirq_fops);
/* 3.添加cdev */
ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
if(ret < 0) {
goto fail_cdev;
}
/* 4.创建类 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if(IS_ERR(imx6uirq.class)) {
ret = PTR_ERR(imx6uirq.class);
goto fail_class;
}
/* 5.创建设备 */
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if(IS_ERR(imx6uirq.device)) {
ret = PTR_ERR(imx6uirq.device);
goto fail_device;
}
/* 初始化IO */
ret = keyio_init(&imx6uirq);
if(ret < 0) {
goto fail_keyinit;
}
/* 初始化原子变量(按键) */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
return 0;
fail_keyinit:
fail_device:
class_destroy(imx6uirq.class);
fail_class:
cdev_del(&imx6uirq.cdev);
fail_cdev:
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:
return ret;
}
/* 驱动函数出口 */
static void __exit timer_exit(void) {
int i = 0;
/* 1.释放中断 */
for(i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
}
/* 2.释放IO */
for(i = 0; i < KEY_NUM; i++) {
gpio_free(imx6uirq.irqkey[i].irqnum);
}
/* 3.删除定时器 */
del_timer_sync(&imx6uirq.timer);
/* 注销字符设备驱动 */
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");
2.9.4 定时器按键消抖中断
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
/*
* timer_temp 文件是编写驱动的模板文件,以后均在这上面增加内容,
* 这个字符驱动框架必须熟悉。
*/
#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "imx6uirq"
#define KEY_NUM 1
#define KEY0VALUE 0x01
#define INVAKEY 0xFF
/* key结构体 */
struct irq_keydesc {
int gpio; // io编号
int irqnum; // 中断号
unsigned char value; // 键值
char name[10]; // 名字
irqreturn_t (*handler) (int, void*); // 中断处理函数
struct tasklet_struct tasklet;
};
/* imx6uirq设备结构体 */
struct imx6uirq_dev {
dev_t devid; // 设备号
int major; // 主设备号
int minor; // 次设备号
struct cdev cdev; // 字符设备
struct class *class;
struct device *device;
struct device_node *nd;
struct irq_keydesc irqkey[KEY_NUM];
struct timer_list timer; // 定时器
atomic_t keyvalue;
atomic_t releasekey;
};
struct imx6uirq_dev imx6uirq; /* 中断 */
static int imx6uirq_open(struct inode *inode, struct file *filp) {
filp->private_data = &imx6uirq;
return 0;
}
static int imx6uirq_release(struct inode *inode, struct file *filp) {
// sstruct imx6uirq_dev *dev = (struct imx6uirq_dev*)filp->private_data;
return 0;
}
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
int ret = 0;
unsigned char keyvalue;
unsigned char releasekey;
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if(releasekey) { // 有效按键
if(keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0); // 按下标志清零
} else {
goto data_error;
}
return ret;
data_error:
return -EINVAL;
}
/* 操作集合 */
static const struct file_operations im6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.release = imx6uirq_release,
};
/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id) {
struct imx6uirq_dev *dev = dev_id;
tasklet_schedule(&dev->irqkey[0].tasklet);
return IRQ_HANDLED;
}
/* tasklet */
static void key_tasklet(unsigned long data) {
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)data;
dev->timer.data = data;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); // 20ms定时
}
/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
int value = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)arg;
value = gpio_get_value(dev->irqkey[0].gpio);
if(value == 0) { // 按下
atomic_set(&dev->keyvalue, dev->irqkey[0].value);
} else if(value == 1) { // 释放
atomic_set(&dev->keyvalue, 0x80 | dev->irqkey[0].value);
atomic_set(&dev->releasekey, 1); // 完整的按键过程
}
}
/* 按键初始化 */
static int keyio_init(struct imx6uirq_dev *dev) {
int ret = 0;
int i = 0;
/* 1.按键初始化 */
dev->nd = of_find_node_by_path("/key");
if(dev->nd == NULL) {
ret = -EINVAL;
goto fail_nd;
}
for(i = 0; i < KEY_NUM; i++) {
dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i); // 可以判断返回值
}
for(i = 0; i < KEY_NUM; i++) {
memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
sprintf(dev->irqkey[i].name, "KEY%d", i);
gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
gpio_direction_input(dev->irqkey[i].gpio);
dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); // 获取中断号
#if 0
dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i); // 获取中断号(通用)
#endif
}
dev->irqkey[0].handler = key0_handler; // 初始化
dev->irqkey[0].value = KEY0VALUE;
/* 2.按键中断初始化 */
for(i = 0; i < KEY_NUM; i++) {
ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev->irqkey[i].name, &imx6uirq);
if(ret) {
printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
goto fail_irq;
}
tasklet_init(&dev->irqkey[i].tasklet, key_tasklet, (unsigned long)dev);
}
/* 3.初始化定时器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_func;
return 0;
fail_irq:
for(i = 0; i < KEY_NUM; i++) {
gpio_free(dev->irqkey[i].gpio);
}
fail_nd:
return ret;
}
/* 驱动函数入口 */
static int __init timer_init(void) {
int ret = 0;
/* 1.注册字符设备驱动 */
imx6uirq.major = 0;
if(imx6uirq.major) { // 指定了主设备号
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.minor);
}
if(ret < 0) {
goto fail_devid;
}
printk("timer major = %d, minor = %d\r\n", imx6uirq.major, imx6uirq.minor);
/* 2.初始化cdev */
imx6uirq.cdev.owner = THIS_MODULE;
cdev_init(&imx6uirq.cdev, &im6uirq_fops);
/* 3.添加cdev */
ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
if(ret < 0) {
goto fail_cdev;
}
/* 4.创建类 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if(IS_ERR(imx6uirq.class)) {
ret = PTR_ERR(imx6uirq.class);
goto fail_class;
}
/* 5.创建设备 */
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if(IS_ERR(imx6uirq.device)) {
ret = PTR_ERR(imx6uirq.device);
goto fail_device;
}
/* 初始化IO */
ret = keyio_init(&imx6uirq);
if(ret < 0) {
goto fail_keyinit;
}
/* 初始化原子变量(按键) */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
return 0;
fail_keyinit:
fail_device:
class_destroy(imx6uirq.class);
fail_class:
cdev_del(&imx6uirq.cdev);
fail_cdev:
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:
return ret;
}
/* 驱动函数出口 */
static void __exit timer_exit(void) {
int i = 0;
/* 1.释放中断 */
for(i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
}
/* 2.释放IO */
for(i = 0; i < KEY_NUM; i++) {
gpio_free(imx6uirq.irqkey[i].irqnum);
}
/* 3.删除定时器 */
del_timer_sync(&imx6uirq.timer);
/* 注销字符设备驱动 */
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");
2.9.4 完善按键中断上下部分
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
/*
* timer_temp 文件是编写驱动的模板文件,以后均在这上面增加内容,
* 这个字符驱动框架必须熟悉。
*/
#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "imx6uirq"
#define KEY_NUM 1
#define KEY0VALUE 0x01
#define INVAKEY 0xFF
/* key结构体 */
struct irq_keydesc {
int gpio; // io编号
int irqnum; // 中断号
unsigned char value; // 键值
char name[10]; // 名字
irqreturn_t (*handler) (int, void*); // 中断处理函数
struct tasklet_struct tasklet;
};
/* imx6uirq设备结构体 */
struct imx6uirq_dev {
dev_t devid; // 设备号
int major; // 主设备号
int minor; // 次设备号
struct cdev cdev; // 字符设备
struct class *class;
struct device *device;
struct device_node *nd;
struct irq_keydesc irqkey[KEY_NUM];
struct timer_list timer; // 定时器
atomic_t keyvalue;
atomic_t releasekey;
struct work_struct work;
};
struct imx6uirq_dev imx6uirq; /* 中断 */
static int imx6uirq_open(struct inode *inode, struct file *filp) {
filp->private_data = &imx6uirq;
return 0;
}
static int imx6uirq_release(struct inode *inode, struct file *filp) {
// sstruct imx6uirq_dev *dev = (struct imx6uirq_dev*)filp->private_data;
return 0;
}
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
int ret = 0;
unsigned char keyvalue;
unsigned char releasekey;
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if(releasekey) { // 有效按键
if(keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0); // 按下标志清零
} else {
goto data_error;
}
return ret;
data_error:
return -EINVAL;
}
/* 操作集合 */
static const struct file_operations im6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.release = imx6uirq_release,
};
/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id) {
struct imx6uirq_dev *dev = dev_id;
// tasklet_schedule(&dev->irqkey[0].tasklet);
schedule_work(&dev->work);
return IRQ_HANDLED;
}
/* tasklet */
static void key_tasklet(unsigned long data) {
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)data;
dev->timer.data = data;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); // 20ms定时
}
/* work */
static void key_work(struct work_struct *work) {
// struct imx6uirq_dev *dev = (struct imx6uirq_dev*)data;
struct imx6uirq_dev *dev = container_of(work, struct imx6uirq_dev, work);
dev->timer.data = (unsigned long)dev;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); // 20ms定时
}
/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
int value = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)arg;
value = gpio_get_value(dev->irqkey[0].gpio);
if(value == 0) { // 按下
atomic_set(&dev->keyvalue, dev->irqkey[0].value);
} else if(value == 1) { // 释放
atomic_set(&dev->keyvalue, 0x80 | dev->irqkey[0].value);
atomic_set(&dev->releasekey, 1); // 完整的按键过程
}
}
/* 按键初始化 */
static int keyio_init(struct imx6uirq_dev *dev) {
int ret = 0;
int i = 0;
/* 1.按键初始化 */
dev->nd = of_find_node_by_path("/key");
if(dev->nd == NULL) {
ret = -EINVAL;
goto fail_nd;
}
for(i = 0; i < KEY_NUM; i++) {
dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i); // 可以判断返回值
}
for(i = 0; i < KEY_NUM; i++) {
memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
sprintf(dev->irqkey[i].name, "KEY%d", i);
gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
gpio_direction_input(dev->irqkey[i].gpio);
dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); // 获取中断号
#if 0
dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i); // 获取中断号(通用)
#endif
}
dev->irqkey[0].handler = key0_handler; // 初始化
dev->irqkey[0].value = KEY0VALUE;
/* 2.按键中断初始化 */
for(i = 0; i < KEY_NUM; i++) {
ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev->irqkey[i].name, &imx6uirq);
if(ret) {
printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
goto fail_irq;
}
// tasklet_init(&dev->irqkey[i].tasklet, key_tasklet, (unsigned long)dev);
}
INIT_WORK(&dev->work, key_work);
/* 3.初始化定时器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_func;
return 0;
fail_irq:
for(i = 0; i < KEY_NUM; i++) {
gpio_free(dev->irqkey[i].gpio);
}
fail_nd:
return ret;
}
/* 驱动函数入口 */
static int __init timer_init(void) {
int ret = 0;
/* 1.注册字符设备驱动 */
imx6uirq.major = 0;
if(imx6uirq.major) { // 指定了主设备号
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.minor);
}
if(ret < 0) {
goto fail_devid;
}
printk("timer major = %d, minor = %d\r\n", imx6uirq.major, imx6uirq.minor);
/* 2.初始化cdev */
imx6uirq.cdev.owner = THIS_MODULE;
cdev_init(&imx6uirq.cdev, &im6uirq_fops);
/* 3.添加cdev */
ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
if(ret < 0) {
goto fail_cdev;
}
/* 4.创建类 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if(IS_ERR(imx6uirq.class)) {
ret = PTR_ERR(imx6uirq.class);
goto fail_class;
}
/* 5.创建设备 */
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if(IS_ERR(imx6uirq.device)) {
ret = PTR_ERR(imx6uirq.device);
goto fail_device;
}
/* 初始化IO */
ret = keyio_init(&imx6uirq);
if(ret < 0) {
goto fail_keyinit;
}
/* 初始化原子变量(按键) */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
return 0;
fail_keyinit:
fail_device:
class_destroy(imx6uirq.class);
fail_class:
cdev_del(&imx6uirq.cdev);
fail_cdev:
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:
return ret;
}
/* 驱动函数出口 */
static void __exit timer_exit(void) {
int i = 0;
/* 1.释放中断 */
for(i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
}
/* 2.释放IO */
for(i = 0; i < KEY_NUM; i++) {
gpio_free(imx6uirq.irqkey[i].irqnum);
}
/* 3.删除定时器 */
del_timer_sync(&imx6uirq.timer);
/* 注销字符设备驱动 */
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");