您的位置:首页 > 其它

基于led框架的驱动分析

2016-09-02 08:09 399 查看

基于led框架的驱动分析

本文的led驱动使用了内核提供的led框架接口,这种驱动实现与普通字符设备驱动有着本质的区别。此外还融合了platform和gpiolib,需要结合这两者来分析本驱动。

该驱动本质是:通过读写/sys/class/leds/xxx内的文件,触发led_classdev(设备体)内的函数,从而实现操作硬件。

整体关系:



1.led驱动框架接口的使用条件

如果要使用内核的框架来写驱动的话,必须要在menuconfig中添加框架模块,这样才能够调用框架接口函数

比如添加led的框架。Device Drivers —>LED Support 选Y —> LED Class Support 选Y

LED Class Support下还有很多板级的led支持。我们就不用去勾选了

2.led驱动框架接口的实现原理

led-class_comment.c这个文件提供了有关led驱动框架的接口,里面是led框架模块

leds_init是框架模块的加载函数,它主要负责创建led设备类。类的名字是“leds”,而类的本质是一个leds_class类型的结构体

static struct class *leds_class;//先定义指针

static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds");//创建、实例化leds类
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->suspend = led_suspend;
leds_class->resume = led_resume;
leds_class->dev_attrs = led_class_attrs;
return 0;
}


当“leds”这个类创建完后 ,我们就能够在/sys/class/中找到leds这个类,创建完类我们才能创建“属于这个类的设备”,可以认为类是创建设备的前提

这里主要是通过 leds_class->dev_attrs规定设备文件的种类和样式。dev_attrs是设备属性的意思,通过给它赋一个特殊的数据结构(里面包含了外设的硬件操作),我们就能够在应用层通过/sys/class/leds/xxx里的文件间接访问这个特殊的数据结构从而达到操作硬件的目的。代码为:

static struct device_attribute led_class_attrs[] = {
__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
__ATTR_NULL,
};


该结构体数组设置了led硬件操作的对象和方法。分析可知,led类设备的操作对象一共由3个brightness、max_brightness、trigger。意思是”led的亮灭状态“、”led最高亮度值“、”led闪烁状态“。对应的操作规则有读写,即show和store。除了led最高亮度值,它只能读不能写,因为它只是一个参数罢了,而不是可以操作硬件。这些操作规则内部其实调用了设备体led_classdev内的具体操作函数,也就是说当用户层试图写brightness这个对象时,会触发操作规则led_brightness_store。这个规则内部会调用我们设备体内的具体函数。由前面那副图可以很好的理解这个原理

3.驱动代码分析

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/leds.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <mach/gpio.h>
#include <plat/map-base.h>
#include <plat/map-s5p.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/*设个全局变量。用来给s5pv210_led_set传参*/
unsigned int gpio_num = 0;

/*设备体led_classdev内操作函数的具体实现*/

static void s5pv210_led_set(struct led_classdev *led_cdev,
enum led_brightness value)//参数value是枚举。0、127、255分别代表灭、半亮、全亮
{
if (value == 0) {
gpio_set_value(gpio_num, 1);
}else{
gpio_set_value(gpio_num, 0);
}
}

/*创建(实例化)设备体*/
static struct led_classdev x210led;

/*自留地格式...提供给probe和release函数..让它们可以解析platdata*/
extern struct s5pv210_led_platdata {
unsigned int         gpio;
unsigned int         flags;
char            *name;
char            *def_trigger;
};

/*platform驱动的probe(探测)函数与remove函数*/
static int s5pv210_led_probe(struct platform_device *pdev)
{

struct s5pv210_led_platdata *pdata = pdev->dev.platform_data;
int ret = -1;

gpio_num = pdata->gpio;

/*填充设备体。即led_classdev类型的结构体*/
x210led.name = pdata->name;
x210led.brightness = 0;//led的亮灭状态
x210led.brightness_set = s5pv210_led_set;//设置led的亮灭

/*注册一个属于led类的设备体*/
ret = led_classdev_register(NULL, &x210led);
if (ret < 0) {
printk(KERN_INFO "led_classdev_register failed\n");
goto out_err_0;
}

/*申请一个gpio引脚资源*/
ret = gpio_request(pdata->gpio, pdata->name);
if (ret){
printk(KERN_INFO "gpio_request failed\n");
goto out_err_1;
}
/*申请完后可以利用接口设置该gpio。也可以直接操作寄存器来设置*/
gpio_direction_output(pdata->gpio, 1);

return 0;

/*倒影式错误处理流程*/
out_err_1:
led_classdev_unregister(&x210led);

out_err_0:
return -EINVAL;

}

static int s5pv210_led_remove(struct platform_device *pdev)
{
struct s5pv210_led_platdata *pdata = pdev->dev.platform_data;
gpio_free(pdata->gpio);

led_classdev_unregister(&x210led);

return 0;
}

/*定义我们的platform_driver。注意name要和platform_device中相同*/
static struct platform_driver s5pv210_led_driver = {
.probe      = s5pv210_led_probe,
.remove     = s5pv210_led_remove,
.driver     = {
.name       = "s5pv210_led",
.owner      = THIS_MODULE,
},
};

/*模块与卸载加载函数*/
static int __init s5pv210_led_init(void)
{
return platform_driver_register(&s5pv210_led_driver);
}

static void __exit s5pv210_led_exit(void)
{
platform_driver_unregister(&s5pv210_led_driver);
}

module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

/*模块描述信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("taurenking");
MODULE_DESCRIPTION("S5PV210 LED driver");
MODULE_ALIAS("S5PV210 LED driver");


头文件解析

内核的头文件默认搜索路径在根目录下include文件夹内,所以#include这些头文件时路径的确认非常方便

当头文件不在根目录下include文件夹内呢?基本上这些头文件#include时都包含了符号链接。确定带有符号链接路径的方法是,如果路径不在include文件夹内,但是里面包含了mach、asm等字样,那么路径多半可以确认为
<asm/xxxx>、<mach/xxxx>


当我们实在无法确定带有符号链接路径时,我们只需要借鉴其他文件即可。比如我们用到了copy_from_user。我们知道它在uaccess.h中。但是不知道路径怎么写,这时只需要搜索一下同样调用“copy_from_user”的那些文件,直接抄他们的头文件路径即可

设备体led_classdev内操作函数的具体实现

值得注意的是。读led 状态的函数是不需要的,当APP尝试读取led状态时,系统会直接读取我们上次的写入值来返回给APP

这看似很不合理,如果led寄存器的值被意外改变了呢?但是这其实非常合理,因为led这种设备只是输出设备,寄存器的值不可能被外界改变。所以系统采用了这种简便的方法,反正这个框架只是给led用的,不可能给其他设备用。所以我们也不难得出结论,在其他有输入功能的设备框架(例如串口)中,是不可能省略掉”读取函数“的

此处函数还利用了gpiolib接口来操作了gpio的数据寄存器 ,其实不一定要用gpiolib,直接操作寄存器也行,关于gpiolib详见gpiolib详尽分析

probe和release函数,及其内部的各种注册操作

有关probe和release,详见platform总线驱动详尽分析

首先填充设备体,这个设备体就是我们定义的一个led_classdev类型结构体,它里面集成了所有的操作和参数,其实就相当普通字符设备驱动中的cdev及其内部的file_operations。只不过led_classdev针对的是属于“leds”类的设备,而cdev针对的是普通字符设备。由于 led_classdev_register内部并不会对这个结构体指针实例化,所以我们定义的时候还是定义结构体比较好

然后利用led_classdev_register注册这个设备体。led_classdev_register 就是框架给我们提供的接口,其第一个参数是父设备,第二个参数是指向led_classdev类型结构体的指针。经过了这一步 在/sys/class/leds/ 下就会自动出现一个目录,这个目录就是我们的设备文件目录。 对于我们这,/sys/class/leds/ 下就会出现特定led名字的目录,进入这个目录就会发现里面有各种文件、文件夹,如图



那么这些文件、文件夹从何而来?又代表了什么呢?其实这些文件的出现就是前文所说的dev_attrs的杰作 。只要用户创建注册的设备体使用了led框架的接口、属于leds这个类,那么设备体注册完生成的目录中就会自动包含这些文件、文件夹

由于用到了gpio,所以要先使用gpiolib向内核申请gpio资源 。关于gpiolib详见gpiolib详尽分析

创建(实例化)我们的platform_driver

这一步要绑定probe和release函数

注意name要和platform_device中相同,这样才能platform总线正确匹配两者

和模块相关的代码

模块加载和卸载函数中分别调用 platform_driver_register和platform_driver_unregiste注册我们的驱动

和模块本身有关的知识详见内核驱动的本质——模块
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  框架 内核 硬件