8.Linux驱动-简单led驱动

1.前言

开发板:正点原子阿尔法
本文示例会结合前面知识,pinctrl与gpio,设备树,工作队列,定时器,sysfs,platform驱动做一个简单总结,会有一些不合理的地方,实现1s翻转一次led的状态

2.驱动代码

led.h

#ifndef LED_DRIVER_REG_H
#define LED_DRIVER_REG_H

#include <linux/types.h>			/*设备号所在头文件*/
#include <linux/module.h>           /*内核模块声明的相关函数*/
#include <linux/init.h>			    /*module_init和module_exit*/
#include <linux/kernel.h>		    /*内核的各种函数*/
#include <asm/io.h>                 /*readl函数*/
#include <linux/cdev.h>             /*cdev*/
#include <linux/device.h>           /*class & device*/
#include <linux/fs.h>
#include <asm/uaccess.h>           /*copy_from_user*/
#include <linux/gpio.h>            /*gpio fileoperation*/
#include <linux/of.h>             
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>

#define DEVICE_NAME "led_driver"
#define DEVICE_CNT   1
#define LED_ON		 1
#define LED_OFF      0
#define HIGH_LEVEL   1
#define LOW_LEVEL    0

struct led_device
{
    
    
	dev_t devid;		    /*设备号*/
	struct cdev cdev;       /*cdev*/
	struct class  *class;	/*类*/
	struct device *device;	/*设备*/
	int    major;			/*主设备号*/
	int    minor;			        /*次设备号*/
    int    led_gpio;
    struct device_node* dev_node;
    struct timer_list   timer;    /*定时器*/
    struct work_struct  led_work; /*工作队列*/
};
struct led_device led_device;
#endif

led.c

#include "led.h" 


static void led_switch(int led_status)
{
    
    
	switch(led_status)
	{
    
    
		case LED_ON:
			gpio_set_value(led_device.led_gpio,LOW_LEVEL);
			break;
		case LED_OFF:
			gpio_set_value(led_device.led_gpio,HIGH_LEVEL);
			break;
		default:
			gpio_set_value(led_device.led_gpio,HIGH_LEVEL);
			break;		
	}
			
}

//echo
static ssize_t led_enable_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t len)
{
    
    
	bool iLedEnable;
	u32 ret;

	ret  = strtobool(buf, &iLedEnable);
	if(ret < 0)
	{
    
    
		printk("strtobool failed\n");
		return ret;
	}

	printk("led led_enable_store is=%d\n",iLedEnable);
	led_switch(!!iLedEnable);
		
    return len;
}

//声明led_enable文件节点
static DEVICE_ATTR(led_enable, S_IWUSR, NULL,led_enable_store);

static struct attribute *atk_imx6ul_led_sysfs_attrs[] = {
    
    
	&dev_attr_led_enable.attr,
	NULL,
};


static const struct attribute_group dev_attr_grp = {
    
    
      .attrs = atk_imx6ul_led_sysfs_attrs,
	  NULL,
};


static int imx6ull_led_open(struct inode *inode, struct file *file)
{
    
    
	file->private_data = &led_device;
	return 0;
}


static int imx6ull_led_close(struct inode *inode, struct file *file)
{
    
    
	file->private_data = &led_device;
	return 0;
}

static ssize_t imx6ull_led_read(struct file *file, char __user *buf, size_t cnt, loff_t * loff)
{
    
    
	return 0;
}

static ssize_t imx6ull_led_write(struct file *file, const char __user *buf, size_t cnt, loff_t *loff)
{
    
    
	u32 ret;
	unsigned char cALedbuf[1];
	unsigned char iLedSta;
	
	ret = copy_from_user(cALedbuf,buf,cnt);
	if(ret < 0)
	{
    
    
		printk("copy from user failed\n");
		return -EINVAL;
	}
	
	iLedSta = cALedbuf[0];
	
    printk("++klz write led ,led status %d\n",iLedSta);
	led_switch(!!iLedSta);

	return 0;

}

static struct file_operations led_device_fops = {
    
    
	.owner   = THIS_MODULE,
	.read    = imx6ull_led_read,
	.write   = imx6ull_led_write,
	.open    = imx6ull_led_open,
	.release = imx6ull_led_close,
};


static int led_parse_dt(void)
{
    
    
	int ret;

	/*1.获取设备树中compatible属性的字符串值klz-led*/
	led_device.dev_node = of_find_compatible_node(NULL,NULL,"klz-led");
	if(led_device.dev_node == NULL)
	{
    
    
		printk("led device node find failed\n");
		return -1;
	}
	
	/*2.获取gpio编号,将节点中的“led-gpio”属性值转换为对应的 LED 编号。*/
	led_device.led_gpio = of_get_named_gpio(led_device.dev_node, "led-gpio", 0);
	if(led_device.led_gpio < 0)
	{
    
    
		printk("failed to get gpio\n");
		return -1;
	}
	
	/*3.申请gpio管脚*/
	ret = gpio_request(led_device.led_gpio, "klz-led");
	if(ret != 0)
	{
    
    
		printk("gpio request failed\n");
		return -1;
	}

	/*4.设置gpio为输出且输出高电平,熄灭*/
	ret = gpio_direction_output(led_device.led_gpio,HIGH_LEVEL);
	if(ret != 0)
	{
    
    
		printk("gpio direction output failed\n");
		return -1;
	}

	return 0;
}

static void led_exchange_work(struct work_struct *work)
{
    
    
	struct led_device *data = container_of(work, struct led_device, led_work);
    gpio_set_value((*data).led_gpio,HIGH_LEVEL);
    msleep(1000);
    gpio_set_value((*data).led_gpio,LOW_LEVEL);
}


//2s调度一次工作队列
static void led_exchange_timer(unsigned long handle)
{
    
    
	schedule_work(&led_device.led_work); 
	mod_timer(&led_device.timer, jiffies +msecs_to_jiffies(2000));	
}


static int led_driver_probe(struct platform_device *pdev)
{
    
    
	u32 ret;

	printk("klz");
	printk("led driver and device was matched!\r\n");
	/*1.设备树解析*/
	ret = led_parse_dt();
	if (ret < 0) {
    
    
		printk("led parse error");
		return ret;
	}
	
	/*2.字符设备驱动框架那一套*/
	/*2.1 之前定义了主设备号*/
	if(led_device.major)
	{
    
    
		/*选择次设备号*/
		led_device.devid = MKDEV(led_device.major,0);
		/*注册设备号*/
		ret = register_chrdev_region(led_device.devid, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0)
		{
    
    
			printk("register_chrdev_region failed\n");
			return ret;
		}
	}else
	{
    
    
		/*向内核申请主次设备号,DEVICE_NAME体现在/proc/devices*/
		alloc_chrdev_region(&led_device.devid, 0, DEVICE_CNT,  DEVICE_NAME); /* 申请设备号 */
		led_device.major = MAJOR(led_device.devid); /* 获取分配号的主设备号 */
		led_device.minor = MINOR(led_device.devid); /* 获取分配号的次设备号 */
	}

	led_device.cdev.owner = THIS_MODULE;
	cdev_init(&led_device.cdev,&led_device_fops);
	/*自动创建设备结点,在/dev目录下体现*/
	ret = cdev_add(&led_device.cdev,led_device.devid,DEVICE_CNT);
	if(ret < 0)
	{
    
    
		printk("cdev_add device failed\n");
		goto fail_cdev_add;
	}

	led_device.class = class_create(THIS_MODULE,DEVICE_NAME);
	if(IS_ERR(led_device.class))
	{
    
    
		printk("class creat failed\n");
		goto fail_class_create;
	}

	/*生成dev/DEVICE_NAME文件*/
	led_device.device = device_create(led_device.class,NULL,led_device.devid,NULL,DEVICE_NAME);
	if(IS_ERR(led_device.device))
	{
    
    
		printk("device class failed\n");
		goto fail_device_create;
	}

	/*创建led_enable结点,直接通过系统调用来操作驱动*/
	ret = sysfs_create_group(&led_device.device->kobj,&dev_attr_grp);
	if(ret)
	{
    
    
		printk("failed to create sys files\n");
		goto fail_sys_create;
	}

    //struct led_device *device = dev_get_drvdata(dev);
    /*初始化定时器,工作队列*/
    INIT_WORK(&led_device.led_work,led_exchange_work);
    setup_timer(&led_device.timer,led_exchange_timer,(unsigned long)&led_device);//最后一个值可以传指针

    /*激活定时器,add_timer不激活定时器*/
    mod_timer(&led_device.timer, jiffies +msecs_to_jiffies(0));
	dev_set_drvdata(&pdev->dev, &led_device);
	printk("%s:probe success\n",__func__);
    
	return 0;
fail_cdev_add:
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	return -1;
fail_class_create:
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	return -1;
fail_device_create:
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	class_destroy(led_device.class);
    return -1;
fail_sys_create:    
    sysfs_remove_group(&led_device.device->kobj,&dev_attr_grp);
    cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	class_destroy(led_device.class);    
	return -1;
	
}

static int led_driver_remove(struct platform_device *pdev)
{
    
    
    gpio_set_value(led_device.led_gpio,1);
    //依赖device,先删除
	sysfs_remove_group(&led_device.device->kobj, &dev_attr_grp);
    cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	/*依赖于class所以先删除*/
	device_destroy(led_device.class, led_device.devid);
	class_destroy(led_device.class);
	
    //删除定时器
    del_timer_sync(&led_device.timer);
    //取消工作队列
    cancel_work_sync(&led_device.led_work); 
	
	gpio_free(led_device.led_gpio);
	return 0;
}

 /* 匹配列表,led_of_match中的compatible与设备树中的
  * compatible匹配,匹配成功则跑probe函数
 */
 static const struct of_device_id led_of_match[] = {
    
    
     {
    
     .compatible = "klz-led" },
     {
    
     /* Sentinel */ }
 };

 /*
 * platform 平台驱动结构体
 */
 static struct platform_driver led_driver = {
    
    
	.driver = {
    
    
	    .name = "klz-led",
	    .of_match_table = led_of_match,
     },
	.probe = led_driver_probe,
	.remove = led_driver_remove,
 };


module_platform_driver(led_driver);


MODULE_AUTHOR("klz <[email protected]>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("led driver of atk imx6ull");

Makefile:

KERNELDIR := /home/klz/linux/linux-4.1.15

CURRENT_PATH := $(shell pwd)
obj-m := led.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
copy:
	sudo cp *.ko /home/klz/linux/nfs/rootfs/lib/modules/4.1.15
	
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean	

执行make && make copy拷贝到根文件系统中,

3.设备树

在根结点设备树添加,注意屏蔽其他用到一样结点

/*klz 2021.1.5*/
	klzled{
    
    
		#address-cells = <1>;
		#size-cells    = <1>;
		compatible = "klz-led";
		status = "okay";
		
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_led>;
		
		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
	};

pinctrl下,注意屏蔽其他用到一样结点

 pinctrl_led: ledgrp {
    
    
            fsl,pins = <
            	MX6UL_PAD_GPIO1_IO03__GPIO1_IO03        0x10B0 /* LED0 */
            >;
        };

在Linux源码目录下执行

make dtbs

拷贝到根文件系统

cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb ../tftpboot/

4.测试

将编译出来 led.ko 拷贝到 rootfs/lib/modules/4.1.15 目录中,重启开发板,进入到目录 lib/modules/4.1.15 中,输入如下命令加载 led.ko 这个驱动模块

depmod //第一次加载驱动的时候需要运行此命令
modprobe led.ko //加载驱动模块
或者
insmod led.ko

在这里插入图片描述

驱动模块加载完成以后到/sys/bus/platform/drivers/目录下查看驱动是否存在,我们在led.c 中设置 led_driver (platform_driver 类型)的 name 字段为“ klz-led”,因此会在/sys/bus/platform/drivers/目录下存在名为“klz-led”这个文件
在这里插入图片描述同理,在/sys/bus/platform/devices/目录下也存在 led 的设备文件,也就是设备树中 klzled 这
个节点
在这里插入图片描述
驱动和模块都存在,当驱动和设备匹配成功以后就会输出如图
在这里插入图片描述
如果要卸载驱动的话输入如下命令即可:

rmmod led.ko

5.总结

1.使用sysfs_create_files会有警告,所以换成sysfs_create_group
2.sysfs_remove_group卸载在device卸载后面,不然会报空指针

猜你喜欢

转载自blog.csdn.net/weixin_43824344/article/details/120212078
今日推荐