自己编写驱动,应用层程序,在应用层通过ioctl控制LED灯流水,当按键KEY1按下,让风扇转动
新增设备树结点如下:
mycdev.c:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include "mycdev.h"
struct cdev *cdev;
dev_t devno;
unsigned int minor = 0;
unsigned int major = 0;
struct class *cls;
struct device *dev;
struct device_node *dnode; // 用于记录设备树结点信息
struct device_node *dnode2; // 用于记录设备树结点信息
int irqno; // 用于记录获取到的软中断号
struct gpio_desc *gpion1; // 用于记录申请到的gpio编号
struct gpio_desc *gpion2;
struct gpio_desc *gpion3;
struct gpio_desc *gpion4;
// 中断处理函数
irqreturn_t irq_handler(int irqno, void *arg)
{
gpiod_set_value(gpion4, !gpiod_get_value(gpion4));
return IRQ_HANDLED;
}
int myled_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
long myled_ioctl(struct file *file, unsigned int cmd, unsigned long args)
{
// 判断应用层传递过来的命令码 cmd
// 判断操作哪盏灯进行点亮
switch (MINOR(file->f_inode->i_rdev)) // 判断次设备号
{
case 0: // 规定次设备号为0的设备文件只能控制LED1
if (cmd == LED_ON)
gpiod_set_value(gpion1, 1);
else if (cmd == LED_OFF)
gpiod_set_value(gpion1, 0);
break;
case 1: // 规定次设备号为1的设备文件只能控制LED2
if (cmd == LED_ON)
gpiod_set_value(gpion2, 1);
else if (cmd == LED_OFF)
gpiod_set_value(gpion2, 0);
break;
case 2: // 规定次设备号为2的设备文件只能控制LED3
if (cmd == LED_ON)
gpiod_set_value(gpion3, 1);
else if (cmd == LED_OFF)
gpiod_set_value(gpion3, 0);
break;
}
return 0;
}
int myled_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops = {
.open = myled_open,
.unlocked_ioctl = myled_ioctl,
.release = myled_close,
};
static int __init mycdev_init(void)
{
int ret, i;
// 分配字符设备驱动对象
cdev = cdev_alloc();
if (NULL == cdev)
{
printk("分配对象空间失败\n");
ret = -ENOMEM;
goto ERR1;
}
printk("1.分配对象空间成功\n");
// 初始化驱动对象结构体
cdev_init(cdev, &fops);
// 动态申请设备号
ret = alloc_chrdev_region(&devno, minor, 3, "mycdev");
if (ret)
{
printk("动态申请设备号失败\n");
goto ERR2;
}
major = MAJOR(devno); // 获取主设备号
minor = MINOR(devno); // 获取次设备号
printk("2.动态申请设备号成功\n");
// 注册对象
ret = cdev_add(cdev, MKDEV(major, minor), 3);
if (ret)
{
printk("驱动对象注册进内核失败\n");
goto ERR3;
}
printk("3.驱动对象注册进内核成功\n");
// 向上提交目录
cls = class_create(THIS_MODULE, "mycdev");
if (IS_ERR(cls))
{
printk("向上提交目录失败\n");
goto ERR4;
}
printk("4.向上提交目录成功\n");
// 向上提交设备节点
for (i = 0; i < 3; i++)
{
dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);
if (IS_ERR(dev))
{
printk("向上提交节点信息失败\n");
goto ERR5;
}
}
printk("5.向上提交设备节点成功\n");
// 解析设备树节点
dnode = of_find_node_by_name(NULL, "myleds");
if (dnode == NULL)
{
printk("解析设备树节点失败\n");
return -ENXIO;
}
dnode2 = of_find_node_by_name(NULL, "myirqs");
if (dnode2 == NULL)
{
printk("解析设备树节点失败\n");
return -ENXIO;
}
printk("解析设备树节点成功\n");
// 根据设备树节点解析gpio编号
gpion1 = gpiod_get_from_of_node(dnode, "led1", 0, GPIOD_OUT_LOW, NULL); // LED1:gpioe 10
if (IS_ERR(gpion1))
{
printk("解析gpio编号失败\n");
return PTR_ERR(gpion1);
}
gpion2 = gpiod_get_from_of_node(dnode, "led2", 0, GPIOD_OUT_LOW, NULL); // LED2:gpiof 10
if (IS_ERR(gpion2))
{
printk("解析gpio编号失败\n");
return PTR_ERR(gpion2);
}
gpion3 = gpiod_get_from_of_node(dnode, "led3", 0, GPIOD_OUT_LOW, NULL); // LED3:gpioe 8
if (IS_ERR(gpion3))
{
printk("解析gpio编号失败\n");
return PTR_ERR(gpion3);
}
gpion4 = gpiod_get_from_of_node(dnode, "fan", 0, GPIOD_OUT_LOW, NULL); // fan:gpioe 9
if (IS_ERR(gpion4))
{
printk("解析gpio编号失败\n");
return PTR_ERR(gpion4);
}
printk("解析gpio编号成功\n");
// 根据设备树结点获取软中断号
irqno = irq_of_parse_and_map(dnode2, 0);
if (!irqno)
{
printk("获取软中断号失败\n");
return -ENXIO;
}
printk("根据设备树结点获取软中断号成功\n");
// 注册要使用的中断
ret = request_irq(irqno, irq_handler, IRQF_TRIGGER_FALLING, "myirq", NULL);
if (ret)
{
printk("注册中断失败\n");
return ret;
}
printk("注册中断成功\n");
return 0;
ERR5:
// 将前面提交成功的设备信息销毁
for (--i; i >= 0; i--)
{
device_destroy(cls, MKDEV(major, i));
}
// 销毁目录
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), 3);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i;
// 关闭风扇
gpiod_set_value(gpion4, 0);
// 注销中断
free_irq(irqno, NULL);
// 将LED灯全部熄灭
gpiod_set_value(gpion1, 0);
gpiod_set_value(gpion2, 0);
gpiod_set_value(gpion3, 0);
// 注销gpio编号
gpiod_put(gpion1);
gpiod_put(gpion2);
gpiod_put(gpion3);
printk("注销gpio编号成功\n");
// 1.注销向上提交的字符设备信息
for (i = 0; i < 3; i++)
{
device_destroy(cls, MKDEV(major, i));
}
printk("1.注销向上提交的字符设备信息成功\n");
// 2.注销向上提交的目录信息
class_destroy(cls);
printk("2.注销向上提交的目录信息成功\n");
// 3.注销字符设备驱动对象
cdev_del(cdev);
printk("3.注销字符设备驱动对象成功\n");
// 4.释放申请到的设备号
unregister_chrdev_region(MKDEV(major, minor), 3);
printk("4.释放申请到的设备号成功\n");
// 5.释放申请到的字符设备驱动对象空间
kfree(cdev);
printk("5.释放申请到的字符设备驱动对象空间成功\n");
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
text.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "mycdev.h"
int main(int argc, const char *argv[])
{
char buf[128] = {0};
int fd[3] = {0};
char *dir[3] = {"/dev/mycdev0","/dev/mycdev1","/dev/mycdev2"};
for (int i = 0; i < 3; i++)
{
fd[i] = open(dir[i], O_RDWR);
if (-1 == fd[i])
{
perror("open is error\n");
return -1;
}
}
while (1)
{
for (int i = 0; i < 3; i++)
{
ioctl(fd[i], LED_ON);
sleep(1);
ioctl(fd[i], LED_OFF);
sleep(1);
}
}
for (int i = 0; i < 3; i++)
close(fd[i]);
return 0;
}