您的位置:首页 > 运维架构 > Linux

第四部分 linux led驱动代码分析

2016-06-16 22:36 597 查看
原文地址 :http://blog.csdn.net/woshidahuaidan2011/article/details/51695106

一、LED驱动

1、对led驱动的测试

对于向led这样的设备模型,系统认为所有的设备都是挂接在总线上的,而要使设备工作,就需要相应的驱动。设备模型会产生一个虚拟的文件系统——sysfs,它给用户提供了一个从用户空间去访问内核设备的方法,它在linux里的路径是/sys。如果要写程序访问sysfs,可以像读写普通文件一样来操作/sys目录下的文件。

对于led的移植不需要任何的修改,首先需要在make menuconfig里面选择:

Device Drivers >

 LED Support –

-* - LED Class  Support

<*> LED support forsansung s3c24xxGPRO  LEDs

下载到内核之后会在/sys/class/目录下有个leds目录,会看到led4 led5 led6 led7,事实上,在leds目录下执行ls  -l 可知,led4 led5 led6 led7分别是下/sys/devices/platform/s3c24xx_led.0/leds/led4,

/sys/devices/platform/s3c24xx_led.1/leds/led5,/sys/devices/platform/s3c24xx_led.1/leds/led6的软连接。

假设进入/sys/class/leds/led6目录 会看到目录下有:

brightness     max_brightness  subsystem       uevent

device          power           trigger

其中brightness就是控制等亮灭的文件,max_brightness可以控制亮灭程度的最大值假如设定是100就是0到100代表不同的亮度(需要单板硬件的支持),trigger是触发方式,cat trigger 打印出:[none]nand-disk backlight 这里就是代表党对nand读写时候等就会闪烁。至于其他的作用在内核源码包里面\Documentation

\leds目录下的txt文档有详细的说明。这里对brightness写入0或者1就是控制等的亮灭,可以执行:

cat > brightness   //向brightness写入数据

这里可以在控制台写入0或者1进行试验。

下面用代码用代码阐释控制led

写代码之前,首先,需要打开设备节点,此时用到open函数,close函数,read函数,write函数,首先open函数的原型是:

int open( const char * pathname, int flags);

                  int open( const char * pathname,int flags, mode_t mode);

用到该函数需要包含:

 #include<sys/types.h>

 #include<sys/stat.h>

#include<fcntl.h> 

返回值:成功返回新分配的文件描述符,出错返回-1并设置errno,这里errno 是记录系统的最后一次错误代码。代码是一个int型的值,不同的值代表不同的错误类型,具体的含义在errno.h中定义。

 

对于open函数,第一个是打开文件的路径,第二个参数打开模式(只读,只写,读写)分别对应的是:

O_RDONLY       只读模式 

O_WRONLY      只写模式 

O_RDWR          读写模式

这三个参数每次使用的时候只能使用其中的一个。其实对应的O_RDONLY 等只是个宏定义,对应的是具体的数值,然后系统很据其数值的不同做出相应的操作,当然还有其他可供选择的打开模式,可以选择以下的多个平且用“|”连接起来。

O_APPEND      每次写操作都写入文件的末尾 

O_CREAT        如果指定文件不存在,则创建这个文件 

O_EXCL         如果要创建的文件已存在,则返回 -1,并且修改 errno 的值

O_TRUNC        如果文件存在,并且以只写/读写方式打开,则清空文件全部内容 

O_NOCTTY       如果路径名指向终端设备,不要把这个设备用作控制终端。

O_NONBLOCK     如果路径名指向 FIFO(先入先出队列)/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)。

O_DSYNC      等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。 

O_RSYNC        read 等待所有写入同一区域的写操作完成后再进行

O_SYNC         等待物理 I/O 结束后再 write,包括更新文件属性的 I/O

第三个参数是创建文件用的,该参数仅在access=O_CREAT方式下使用,其可选有:

S_IFMT     0xF000   文件类型掩码                      

S_IFDIR     0x4000   目录                              

S_IFIFO     0x1000   FIFO 专用                         

S_IFCHR     0x2000   字符专用                          

S_IFBLK     0x3000   块专用                            

S_IFREG     0x8000   只为0x0000                        

S_IREAD     0x0100   可读                              

S_IWRITE    0x0080   可写                              

S_IEXEC     0x0040   可执行

以上参考:

http://www.linuxidc.com/Linux/2011-03/33170.htm

http://blog.163.com/njut_wangjian/blog/static/165796425201231483931869/

http://blog.sina.com.cn/s/blog_6c762d9501011voz.html

对于close函数,函数原型为:

int close(int fd);

包含在:#include <unistd.h>

fd就是打开文件得到的返回值;close的返回值:成功返回0,出错返回-1并设置errno。对于close函数,当进程结束的时候即使代码本身没有调用close函数来关闭文件,内核也会自动关闭文件的,但是一些长时间运行的文件,比如网络服务器就需要在代码中打开的文件描述符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。

以上参考:http://joe.is-programmer.com/posts/17463.html

对于write函数:

功能描述: 向文件写入数据。

所需头文件: #include<unistd.h>

函数原型:ssize_t write(int fd, void*buf, size_t count);

返回值:写入文件的字节数(成功);-1(出错)

功能:write 函数向 filedes 中写入 count 字节数据,数据来源为 buf 。返回值一般总是等于 count,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。对于普通文件,写操作始于 cfo (当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND
。)。如果打开文件时使用了O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo增加,增量为实际写入的字节数。

 

对于read函数:

功能描述: 从文件读取数据。

所需头文件: #include<unistd.h>

函数原型:ssize_tread(int fd,void *buf, size_t count);

参数解释: 

fd: 将要读取数据的文件描述词。

buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。

count: 表示调用一次read操作,应该读多少数量的字符。

返回值:返回所读取的字节数;0(读到EOF);-1(出错)。

以下几种情况会导致读取到的字节数小于 count :

A读取普通文件时,读到文件末尾还不够 count 字节。例如:如果文件只有 30 字节,而我们想读取 100字节,那么实际读到的只有 30 字节,read 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0 。

B从终端设备(terminal device)读取时,一般情况下每次只能读取一行。

C. 从网络读取时,网络缓存可能导致读取的字节数小于 count字节。

D. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 count 。

E. 从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。

以上参考:http://blog.sina.com.cn/s/blog_71d1a98701010s0v.html

 

 

 

这里创建led.c

/*led.c */

#include<stdint.h>

#include<string.h>

#include<fcntl.h>

#include<unistd.h>

#include<stdio.h>

#include<linux/input.h>

 

int main(intargc, char *argv[])/*argv[]是argc个参数,其中第0个参数是程序的全名,以后的参数 

        命令行后面跟的用户输入的参数*/

{

     int fil[3];  //open 3 files

    if ((argv[1][0]-48)>7){

               printf("请输入数字为于0到8之间! \n");

               return0;

     }

    

    *(fil+0) =open("/sys/class/leds/led4/brightness", O_RDWR);

    *(fil+1) =open("/sys/class/leds/led5/brightness", O_RDWR);

    *(fil+2) =open("/sys/class/leds/led6/brightness", O_RDWR);

    int i; 

    for(i=0;i<3;i++)

    {

                if(*(fil+i)<0)

                {

                  printf("can not openfile led%d.\n",*(fil+i)+4);

                  return -1;

                }

                intdata=((int)argv[1][0]-48)&(0x0001<<i);  //按位分析数据

               //printf("%d\n",data);

                data>0?write(*(fil+i),"1", 1):write(*(fil+i) ,"0", 1);  //判断写入数据

                close(*(fil+i));

    }

          

return 0;

}

接下来只需要执行arm-linux-gcc -o led led.c 就会生成led可执行文件,或者建立简单的makefile文件:

vi Makefile

CC=arm-linux-2440-gcc

led:led.c

        ${CC} -o $@  $<    #注意命令前需要空一格tab空格。

        cp led /work/root/work/   #拷贝到nfs文件系统里面(路径因人而异)

clean:

        rm -rf led

然后执行make会生成led可执行文件。然后将led拷贝到文件系统中

执行./led n n代表0(0b000),1(0b001),2 (0b010),3 4 5 6 7 共有8种可能,分别对应3个led的亮灭状态。假如输入的是大于8的的数字那么会提提示输入错误。

 

2、分析一下linux自带的led的驱动代码

 

在分析之前,本人自以为是的感觉应该先把字符设备驱动的框架首先做出基本的了解:

这里,内核使用的是分层的实现,并把驱动程序分成两部分(当然自己写的简单驱动程序可以只写到一个文件下)。一部分为设备,另一部分为驱动。一般情况下,移植的时候只需要填写设备文件中的设备信息,不需要关心驱动文件部分的实现。用图形可形象的表示:



对于led来说,其对应的上图中的设备信息在内核linux3.14.28/arch/arm/mach-s3c24xx/common-smdk2440.c(下面的分析不另外指出均是出自该文件)里有定义:

static struct s3c24xx_led_platdata smdk_pdata_led4 = {

.gpio           = S3C2410_GPF(4), 

.flags          = S3C24XX_LEDF_ACTLOW |S3C24XX_LEDF_TRISTATE,

.name                   = "led4",

.def_trigger          = "timer",

};

 

static struct s3c24xx_led_platdata smdk_pdata_led5 = {

.gpio           = S3C2410_GPF(5),

.flags          = S3C24XX_LEDF_ACTLOW |S3C24XX_LEDF_TRISTATE,

.name                   = "led5",

.def_trigger          = "nand-disk",

};

 

static struct s3c24xx_led_platdata smdk_pdata_led6 = {

.gpio           = S3C2410_GPF(6),

.flags          = S3C24XX_LEDF_ACTLOW |S3C24XX_LEDF_TRISTATE,

.name                   = "led6",

};

上面填充的三个结构体,定义了3个led(实际有四个,另一个省略)的设备信息,这些信息包含设备的名字;设备的引脚对应(注意,这里函数S3C2410_GPF(n)返回的是一个标号,linux为了方便管理,把每一个GPIO引脚都一一设定了对应的标号);标志(低电平有效也就是低电平灯亮,三态无效);最后一个定义的是触发方式,在Common.txt(documentation\devicetree\bindings\leds)有对触发方式的说明,并列除了当前的几种触发方式:

    "backlight" – 受帧缓存器系统的控制,led此时作为背光灯LED             

    "default-on" – 此时led亮灭受对应LED引脚的控制           

    "heartbeat" –led收到其控制频率的双倍的闪动

    "ide-disk" - LED 指示磁盘有活动

    "timer" - LED 以设定的频率闪动

继续往下看有:

static struct platform_device smdk_led4 = {

.name                   = "s3c24xx_led",

.id               = 0,

.dev            ={

           .platform_data= &smdk_pdata_led4,

},

};

 

static struct platform_device smdk_led5 = {

.name                   = "s3c24xx_led",

.id               = 1,

.dev            = {

           .platform_data= &smdk_pdata_led5,

},

};

 

static struct platform_device smdk_led6 = {

.name                   = "s3c24xx_led",

.id               = 2,

.dev            = {

           .platform_data= &smdk_pdata_led6,

},

};

这里定义了平台设备信息,一会代码就是把这个结构体放到平台设备信息的链表里的,这里的三个led的平台设备名称为s3c24xx_led,该名字用作sys/device下显示的目录名,这个名字很重要,在接下来的设备信息与驱动的识别暗号就是这个名字了;接下来是id号码(id就是分别给这具有相同平台设备名字的几个灯一个不同的编号,只有一个设备的话一般填-1);然后就是一个设备结构体,这里只填写该结构体的platform_data信息,对于led,在其总线平台设备中给出了platform_data,在结构体device中,platform_data就是linux为了适应不同的单板(比如单板的封装、硬件的引脚的连线等不同),告诉系统本单板是如何连线等硬件结构的。比如led通过platform_data指出了其连接led的引脚,工作状态,led单个设备的名字和触发方式等信息。系统通过调用dev_get_platdata函数来获取platform_data的信息。从c语言定义上来理解platform_data的话,其定义 
void       *platform_data,也就是一个void类型的指针,可以指向任何类型,因此可以理解为platform_data可以是人为任意定义的东西,这个东西只是让驱动设计者传递驱动程序想要传入进去的信息而已。

接着往后看:

static structplatform_device __initdata *smdk_devs[] = {

。。。。。。。。。省略。。。。。。。

&smdk_led4,

&smdk_led5,

&smdk_led6,

&smdk_led7,

。。。。。。。。。省略。。。。。。。

 

};

接下来就是定义了一个platform_device类型的数组smdk_devs[],并把刚才为led填写的platform_device类型的结构体smdk_led4,smdk_led5,
smdk_led6 ,smdk_led7统一放到该数组里,这样做的只是方便管理,之所以说方便管理是因为当程序想要把所有的设定的平台设备告诉内核去注册的时候不用一个一个的去告诉它,直接把smdk_devs[]传递给内核就好。有点物以类聚的感觉。__initdata这句话是说这是初始化有关的数据,初始化完毕后这个存放这些数据的内存可以被释放。

接下来继续往后看:

static const struct gpiosmdk_led_gpios[] = {

{ S3C2410_GPF(4), GPIOF_OUT_INIT_HIGH, NULL },

{ S3C2410_GPF(5), GPIOF_OUT_INIT_HIGH, NULL },

{ S3C2410_GPF(6), GPIOF_OUT_INIT_HIGH, NULL },

{ S3C2410_GPF(7), GPIOF_OUT_INIT_HIGH, NULL },

};

这个就是比较直观的东西了,当定义s3c24xx_led_platdata实体时候,用到了S3C2410_GPF(n),这里跟上面smdk_devs[]的思想有点类似,就是把想要申请的引脚,统一放到一块,以便后面想内核统一配置并初始化引脚。

接下来继续

void __initsmdk_machine_init(void)

{

/* Configure the LEDs (even if we have no LED support)*/

 

int ret = gpio_request_array(smdk_led_gpios,

                                 ARRAY_SIZE(smdk_led_gpios));

if (!WARN_ON(ret < 0))

           gpio_free_array(smdk_led_gpios,ARRAY_SIZE(smdk_led_gpios));

 

if (machine_is_smdk2443())

           smdk_nand_info.twrph0 = 50;

 

s3c_nand_set_platdata(&smdk_nand_info);

 

platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs));

 

s3c_pm_init();

}

这个是初始化函数,就是对本文件(common-smdk2440.c)定义的结构体进行统一的初始化,首先int ret = gpio_request_array(smdk_led_gpios,ARRAY_SIZE(smdk_led_gpios));是初始化smdk_led_gpios[]里刚才放进去的led所对应的引脚(这里的初始化,是指要把需要初始化的引脚放到平台设备上去);if
(!WARN_ON(ret < 0))  gpio_free_array(smdk_led_gpios, ARRAY_SIZE(smdk_led_gpios))这一句是在说当申请要初始化的引脚是初始化失败后,将刚才预要初始化的引脚释放掉(还原成原来的样子);if (machine_is_smdk2443())   smdk_nand_info.twrph0 = 50这一句是判断板子的cpu是否为2443,假如是2443的话要设定对nand是使能写入信号的时间为50纳秒(单看这句话对led的驱动没什么作用);s3c_nand_set_platdata(&smdk_nand_info)这一句对将nand的信息报告给系统,这里跟led没关系,不继续分析;platform_add_devices(smdk_devs,ARRAY_SIZE(smdk_devs));这句话是把刚才设定的所有的平台设备信息(包括led设定的几个platform_device结构体)报告给系统去注册,这里游戏要说一下,平台设备的注册函数为int
platform_device_register(structplatform_device *pdev) 与之对应的平台设备卸载函数为void platform_device_unregister(struct platform_device*pdev);s3c_pm_init()这一句是跟电源管理有关,假如板子支持的话一般要加上它(这个跟led无关,就不在去分析)。

 到这里有关led的平台设备信息就介绍完毕,现做出简短的总结出有关平台设备的两点:

⑴一个驱动包含两部分,一个为平台设备信息,其主要包含一些要写驱动程序的硬件或者自定义信息;另一个就是真正的驱动函数;

⑵在平台设备信息中,可以自定义有关需要写驱动的必要的物理信息(线路连接情况,触发情况等一切编写驱动程序索要了解的信息)。然后将定义的平台设备注册到内核,就是调用platform_add_devices(platform_device_register)函数添加平台设备到系统。

 

上面把平台设备信息介绍完毕,接下来介绍一下平台驱动,也就是真正的驱动代码了,对于led来说,设备驱动代码在Leds-s3c24xx.c(drivers\leds),现在一句一句的顺序分析:

在Leds-s3c24xx.c (drivers\leds)文件中,自文件下方的代码向上去分析:

首先看到的是几个宏

MODULE_AUTHOR("BenDooks <ben@simtec.co.uk>");   //驱动代码的作者

MODULE_DESCRIPTION("S3C24XXLED driver");  //驱动代码描述信息

MODULE_LICENSE("GPL");                        //驱动代码描遵循协议

MODULE_ALIAS("platform:s3c24xx_led");//驱动代码的别名

这几个宏就是主要是说明信息,没什么值得深究的

继续向上看:

module_platform_driver(s3c24xx_led_driver);

对于这个宏,感觉有必要进行深究,先看宏module_platform_driver被定义在Platform_device.h (include\linux)     中:

/* module_platform_driver()- Helper macro for drivers that don't do

 * anything special in module init/exit.  This eliminates a lot of

 * boilerplate. Each module may only use this macro once, and

 * calling it replaces module_init() andmodule_exit()

 */

#definemodule_platform_driver(__platform_driver) \

module_driver(__platform_driver, platform_driver_register, \

                    platform_driver_unregister)

首先先看上面内核对于该宏的注释:module_platform_driver()是为了减少样板文件而定义的,其跟module init/exit作用是一样的,每次要注册驱动的时候,只需要使用该宏,该宏就会自动去替换module_init()和module_exit()功能(这可以从宏定义看出来,他们三个宏之间就是替代关系,实际,module_init()和module_exit()宏分别调用platform_driver_register和platform_driver_unregister,在这里这一个就调用了这两个函数)。platform_driver_register为驱动注册函数,也就是说驱动代码是通过该函数去申请驱动的,每当加载驱动(insmod或者modprobe)的时候,就会执行该函数;与之对应的是platform_driver_unregister卸载函数,在卸载驱动(rmmod)的时候,调用的就是这个函数。

 

通过宏module_platform_driver(s3c24xx_led_driver)可知,该宏调用的是s3c24xx_led_driver结构体,看一下这个结构体:

static struct platform_driver s3c24xx_led_driver = {

      .probe           = s3c24xx_led_probe,

      .remove        = s3c24xx_led_remove,

      .driver           = {

             .name            = "s3c24xx_led",

             .owner          = THIS_MODULE,

      },

};

可以看到,这个定义有三个定义:.probe= s3c24xx_led_probe,这个是告诉内核,当加载驱动的时候就去调用这个探测函数s3c24xx_led_probe,就是说,当你加载驱动之后,你看的加载驱动后的所用的作用都是在该函数中实现的;同样的道理,当你卸载驱动的时候执行的就是.remove对应的s3c24xx_led_remove函数了;然后下面是初始化的的driver结构体,这个结构体其中一个重要的定义就是name,在之前的平台设备信息定义了也初始化了driver结构体,可以看到两次初始化的name是相同的,内核就是依赖这个name将平台设备信息去平台驱动对应的,也就是说,内核就比较name的具体字符串来确定是否注册驱动函数的,同时,驱动函数也是依靠比较该name该获取编写驱动要了解的电气特性的(这真正做到了分层的思想);下面的这个是.owner
= THIS_MODULE,其是个宏定义: #define THIS_MODULE (&__this_module)。是一个struct module变量,代表当前模块,可以通过THIS_MODULE宏来引用模块的struct
module结构,比如使用THIS_MODULE->state可以获得当前模块的状态。详情可以参考:

http://blog.csdn.net/a954423389/article/details/6101369

 

刚才上面有提到当函数加载的时候就需要调用s3c24xx_led_probe函数,该函数是整个驱动的核心函数,接下来分析一下该函数:

static int s3c24xx_led_probe(structplatform_device *dev)

{

      struct s3c24xx_led_platdata *pdata =dev_get_platdata(&dev->dev);

/***********************************************************************************

这一句是获取struct s3c24xx_led_platdata定义的 smdk_pdata_led4的信息,也就是定义引脚对应、触发方式、子设备信息的名字(LEDn)、标志灯等信息。

*************************************************************************************/

struct s3c24xx_gpio_led *led;

      int ret;

 

      led =devm_kzalloc(&dev->dev, sizeof(struct s3c24xx_gpio_led),    

                       GFP_KERNEL);  

/************************************************************************************

devm_kzalloc(返回值是void *)函数是为struct s3c24xx_led_platdata数据结构分配内存。每当driver
probe一个具体的device实例的时候,都需要建立一些私有的数据结构来保存该device的一些具体的硬件信息(对于led驱动来说,这个数据结构就是struct
s3c24xx_led_platdata)。在过去,驱动工程师多半使用kmalloc或者kzalloc来分配内存,但这会带来一些潜在的问题。例如:在初始化过程中,有各种各样可能的失败情况,这时候就依靠driver工程师小心的撰写代码,释放之前分配的内存。当然,初始化过程中,除了memory,driver会为probe的device分配各种资源,例如IRQ号,io
memory map、DMA等等。当初始化需要管理这么多的资源分配和释放的时候,很多驱动程序都出现了资源管理的issue。而且,由于这些issue是异常路径上的issue,不是那么容易测试出来,更加重了解决这个issue的必要性。内核解决这个问题的模式(所谓解决一类问题的设计方法就叫做设计模式)是Devres,即devm(device
resource management)软件模块。其核心思想就是资源是设备的资源,那么资源的管理归于device,也就是说不需要driver过多的参与。当device和driver
detach的时候,device会自动的释放其所有的资源。devm_kzalloc假如成功分配出内存会返回申请内存的地址指针,假如分配失败的话会返回NULL。

参考:http://www.wowotech.net/linux_kenrel/pin-controller-driver.html

*************************************************************************************/

      if (led == NULL) {

             dev_err(&dev->dev,"No memory for device\n");

             return -ENOMEM;

      }

 

      platform_set_drvdata(dev,led);

/************************************************************************************

platform_set_drvdata函数是跟platform_get_drvdata搭配使用的,其中platform_set_drvdata是把数据保存起来,以便整个设备平台可用(有点像把一个一个函数声明为外部函数,本工程的其他文件就可以使用该文件似的),platform_get_drvdata是获取保存的数据,以本代码为例。platform_set_drvdata(dev,
led);就是把刚才申请的struct s3c24xx_led_platdata内存的地址指针保存到dev指示的平台设备上(s3c24xx_led),这样一来,只要是在s3c24xx_led平台设备上的函数,只需要调用platform_get_drvdata就可以得到保存的申请的struct
s3c24xx_led_platdata内存的地址指针。

 

**************************************************************************************/      

 

 

      led->cdev.brightness_set= s3c24xx_led_set;

      led->cdev.default_trigger= pdata->def_trigger;

      led->cdev.name =pdata->name;

      led->cdev.flags |=LED_CORE_SUSPENDRESUME; //#define LED_CORE_SUSPENDRESUME       (1 << 16)

 

      led->pdata = pdata;

/*************************************************************************************

这些事在给定义的s3c24xx_gpio_led *led赋值,包括灯的亮度的设置,触发方式,名称的设置,因为这是设计到的是对led进行的设置上的自定义处理,不需要深度的去了解他。仅知道这是对led的处置的部分就可以。这里面的s3c24xx_led_set函数就是对led亮灭控制设置的函数。也就是当改变或者brightness的值的时候,驱动就是调用该函数进行对led等的控制的。s3c24xx_led_set函数比较简单,可自行调取查看,不再赘述。

*************************************************************************************/

 

 

 

      ret =devm_gpio_request(&dev->dev, pdata->gpio, "S3C24XX_LED");

      if (ret < 0)

             return ret;

/*************************************************************************************

ret = devm_gpio_request(&dev->dev,pdata->gpio, "S3C24XX_LED");这个函数是为管理设备申请gpio引脚,参数为要申请的设备、需要申请的引脚号、和需要申请的引脚设备的名字,假如申请成功则返回0。假如申请的引脚作为GPIO,当设备和驱动分离的时候,被申请的引脚会被自动的释放;但是假如申请的引脚作为功能引脚的时候,需要调用devm_gpio_free()释放掉设置的功能引脚。

*************************************************************************************/

 

      /* no point in having apull-up if we are always driving */

 

      s3c_gpio_setpull(pdata->gpio,S3C_GPIO_PULL_NONE);

/*************************************************************************************

s3c_gpio_setpull设置是否需要上拉电阻,这里设置的是引脚不需要上拉功能

*************************************************************************************/

 

 

      if (pdata->flags &S3C24XX_LEDF_TRISTATE)

             gpio_direction_input(pdata->gpio);

      else

             gpio_direction_output(pdata->gpio,

                    pdata->flags& S3C24XX_LEDF_ACTLOW ? 1 : 0);

/*************************************************************************************

这里比较简单,就是把相应的引脚根据判断情况设置为输入或者输出,单单从这两句看,就是吧对应led的引脚设置为输入。(实际上,当编程对brightness操作的时候,驱动代码调用的是s3c24xx_led_set函数,来设定数输出还是输入是输出高电平还是低电平)

*************************************************************************************/

 

 

      /* register our new leddevice */

 

      ret =led_classdev_register(&dev->dev, &led->cdev);

      if (ret < 0)

             dev_err(&dev->dev,"led_classdev_register failed\n");

/*************************************************************************************

通过调用led_classdev_register调用这个函数就在目录/sys/class/leds创建子目录led_cdev->name和属性文件brightness,这个函数有必要在下文深究一下具体的含义。

*************************************************************************************/

      return ret;

}

led_classdev_register 函数存放在Led-class.c (drivers\leds)中,该函数注册一个新的设备类对象

int led_classdev_register(struct device *parent, struct led_classdev*led_cdev)

{

      led_cdev->dev =device_create(leds_class, parent, 0, led_cdev,

                                 "%s", led_cdev->name);

      if(IS_ERR(led_cdev->dev))

             returnPTR_ERR(led_cdevled->dev);

/*************************************************************************************

device_create函数跟class_create函数是配对使用的,class_create会在sys/class目录生成相应的类,在本例中会生成leds目录,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。

*************************************************************************************/

 

 

#ifdef CONFIG_LEDS_TRIGGERS

      init_rwsem(&led_cdev->trigger_lock);

#endif

      /* add to the list ofleds */

      down_write(&leds_list_lock);

      list_add_tail(&led_cdev->node,&leds_list);

      up_write(&leds_list_lock);

/*************************************************************************************

这里是将led加链表。

*************************************************************************************/

 

      if(!led_cdev->max_brightness)

             led_cdev->max_brightness= LED_FULL;

/*************************************************************************************

设置亮度的最大值。

*************************************************************************************/

 

      led_update_brightness(led_cdev);

/*************************************************************************************

更新当改变brightness时候,该函数就会起作用,该函数实际调用的就是s3c24xx_led_set。

*************************************************************************************/

 

 

      INIT_WORK(&led_cdev->set_brightness_work,set_brightness_delayed);

/*************************************************************************************

将set_brightness_work加入工作队列,工作队列可以把工作推后,交给一个内核线程去执行,也就是说有set_brightness_work由其他的线程去执行,执行函数为set_brightness_delayed

有关linux内核中断可参考:http://www.linuxidc.com/Linux/2012-12/76191.htm

*************************************************************************************/

 

      init_timer(&led_cdev->blink_timer);

      led_cdev->blink_timer.function= led_timer_function;

      led_cdev->blink_timer.data= (unsigned long)led_cdev;

/*************************************************************************************

上面是有关led闪烁的的控制,这里没用到该功能,不介绍。

*************************************************************************************/

 

#ifdef CONFIG_LEDS_TRIGGERS

      led_trigger_set_default(led_cdev);

#endif

/*************************************************************************************

这里是在判断内核是否开启led触发器的功能,假如设置的话,就去执行led_trigger_set_default函数来设置led触发器,本程序没有开启,不介绍。

*************************************************************************************/

 

 

      dev_dbg(parent,"Registered led device: %s\n",

                    led_cdev->name);

 

      return 0;

}

EXPORT_SYMBOL_GPL(led_classdev_register);

/*************************************************************************************

EXPORT_SYMBOL_GPL导出的符号可以被其他模块使用,不过使用之前一定要声明一下。EXPORT_SYMBOL_GPL()只适用于包含GPL许可权的模块。

 

*************************************************************************************/

对于Led-class.c (drivers\leds)文件的介绍,先暂停一下,继续跳转到Leds-s3c24xx.c (drivers\leds)文件中,刚才介绍的是s3c24xx_led_probe函数,是在平台驱动初始化的时候调用的,与之对应的是平台驱动卸载函数s3c24xx_led_remove函数:

 

static int s3c24xx_led_remove(struct platform_device *dev)

{

      struct s3c24xx_gpio_led*led = pdev_to_gpio(dev);

/*************************************************************************************

pdev_to_gpio 这个函数实际是调用的platform_get_drvdata函数,上面在介绍s3c24xx_led_probe函数有提及到platform_set_drvdata函数,这个是把之前保存的数据取出来。

 

*************************************************************************************/

      led_classdev_unregister(&led->cdev);

/*************************************************************************************

与probe函数对应的是,这里调用led_classdev_unregister函数,用来删除设备节点。具体看一下这个函数。

*************************************************************************************/

 

 

      return 0;

}

 

接下来下面所出现的函数均是定义在Led-class.c (drivers\leds)文件里面:

void led_classdev_unregister(struct led_classdev *led_cdev)

{

#ifdef CONFIG_LEDS_TRIGGERS

      down_write(&led_cdev->trigger_lock);

      if (led_cdev->trigger)

             led_trigger_set(led_cdev,NULL);

      up_write(&led_cdev->trigger_lock);

#endif

 

      cancel_work_sync(&led_cdev->set_brightness_work);

/*************************************************************************************

删除工作队列,之前把任务set_brightness_work放到工作队列

*************************************************************************************/

      /* Stop blinking */

      led_stop_software_blink(led_cdev);

      led_set_brightness(led_cdev,LED_OFF);

/*************************************************************************************

关闭闪烁(假如支持),关闭led

*************************************************************************************/

 

      device_unregister(led_cdev->dev);

/*************************************************************************************

取消注册设备

*******************************************************************************

      down_write(&leds_list_lock);

      list_del(&led_cdev->node);

      up_write(&leds_list_lock);

/*************************************************************************************

删除列表

*******************************************************************************

 

}

可以看到在一般情况下,在驱动加载的时候注册的东西一定要在驱动卸载的时候取消注册,在该文件下,还有对于子系统初始化的的两个宏:

subsys_initcall(leds_init);

module_exit(leds_exit);

这里的subsys_initcall的相当于module_init宏的作用,只是subsys_initcall的优先级比module_init优先级要高,其定义在Init.h(include\linux)    中。

可以看到他们会调用leds_init和leds_exit先看下led_init:

static int __init leds_init(void)

{

      leds_class =class_create(THIS_MODULE, "leds");

/*************************************************************************************

上面在介绍led_classdev_register函数的时候,有提到device_create函数跟class_create函数是配对使用的,class_create会在sys/class目录生成相应的类,在本例中会生成leds目录。

*************************************************************************************/

      if (IS_ERR(leds_class))

             returnPTR_ERR(leds_class);

      leds_class->pm =&leds_class_dev_pm_ops;

/*************************************************************************************

电源管理部分,电源管理结构体dev_pm_ops,这里只定义了.suspend = led_suspend,.resume= led_resume,

.suspend代表进入低功耗状态,不处理数据;resume代表进入活跃状态,唤醒函数。

*************************************************************************************/

 

      leds_class->dev_groups= led_groups;

/*************************************************************************************

这里稍微有点复杂,这是在设置属性,led_groups这个连续调用了好几个函数,中间的函数可以直接跟过去去看,这里只写出来最后的部分:

static struct attribute *led_class_attrs[] = {

      &dev_attr_brightness.attr,

      &dev_attr_max_brightness.attr,

      NULL,

};

这里可以看到有dev_attr_brightness和dev_attr_max_brightness,接下来一下这两个变量(函数)怎么产生的:

在Led-class.c(drivers\leds)中有定义:

static DEVICE_ATTR_RW(brightness);

static DEVICE_ATTR(max_brightness, 0444,led_max_brightness_show, NULL);

DEVICE_ATTR_RW和 DEVICE_ATTR是两个宏,他们定义在Device.h (include\linux)中

 

#define DEVICE_ATTR_RW(_name) \

      structdevice_attribute dev_attr_##_name = __ATTR_RW(_name)

#define DEVICE_ATTR(_name, _mode, _show,_store) \

      structdevice_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

 

通过这两个宏替换后就成为:

dev_attr_ brightness=__ATTR_RW(brightness)

dev_attr_max_brightness=__ATTR(max_brightness,0444, led_max_brightness_show, NULL)

既然这样,继续向下看,在Sysfs.h (include\linux)中有定义:

#define __ATTR_RW(_name) __ATTR(_name,(S_IWUSR | S_IRUGO),              \

                     _name##_show, _name##_store)

 

#define __ATTR(_name, _mode, _show, _store) {                        \

      .attr= {.name = __stringify(_name), .mode = _mode },          \

      .show     = _show,                                          \

      .store      = _store,                                          \

}

再次宏定义替换得到:

dev_attr_ brightness=__ATTR(_name, (S_IWUSR |S_IRUGO), _name##_show, _name##_store)

                 ={

                           .attr= {.name = __stringify(brightness), .mode =(S_IWUSR | S_IRUGO) },

.show    = brightness _show,                                      \

                           .store      = brightness _store,   

}

dev_attr_ max_brightness={

                           .attr= {.name = __stringify(max_brightness), .mode =0444 },

.show    = max_brightness _show,                                    \

                           .store      = max_brightness _store, 

}

通过跟中代码,可以看到:

最终的 __stringify(x)被宏定义为#x

所以得到最后的结果就是:

dev_attr_ brightness ={

                           .attr= {.name =”brightness”, .mode =6444 },

.show    = brightness _show,                                      \

                           .store      = brightness _store,   

}

dev_attr_ max_brightness={

                           .attr= {.name = “max_brightness”, .mode =0444 },

.show    = max_brightness _show,                                    \

                           .store      = max_brightness _store, 

}

这下在理解一下#define __ATTR(_name, _mode, _show, _store)其中四个参数的名字

-name:名字,对于本例led,就是在对应的LEDn文件下的max_brightness和brightness文件,下面就以brightness为例子介绍

mode:设置该文件的权限,对于本例brightness的权限就是6444

show:这是指当对brightness进行读操作的时候,调用该函数

store:这是指当对brightness进行写操作的时候,调用该函数

 

参考:http://blog.csdn.net/zclongembedded/article/details/8689099

*************************************************************************************/

 

      return 0;

}

在初始化函数中,提到在设置led_groups的时候,会调用这两个函数:

brightness _show,brightness_show ,max_brightness _show和 max_brightness _store函数,这些函数定义在Led-class.c (drivers\leds)中(以brightness _show,brightness_show为例):

 

static ssize_t brightness_show(struct device *dev,

             structdevice_attribute *attr, char *buf)

{

      struct led_classdev*led_cdev = dev_get_drvdata(dev);

/*************************************************************************************

获取数据

*******************************************************************************/

      /* no lock needed forthis */

      led_update_brightness(led_cdev);

/*************************************************************************************

led_update_brightness函数是在调用s3c24xx_led_set(Leds-s3c24xx.c (drivers\leds))函数,该函数会通过设定在brightness文件的数据,点亮或者熄灭led。

*******************************************************************************/

 

 

      return sprintf(buf,"%u\n", led_cdev->brightness);

/*************************************************************************************

字符串整合brightness的值

*******************************************************************************/

 

}

 

static ssize_t brightness_store(struct device *dev,

             structdevice_attribute *attr, const char *buf, size_t size)

{

      struct led_classdev*led_cdev = dev_get_drvdata(dev);

      unsigned long state;

      ssize_t ret = -EINVAL;

/*************************************************************************************

获取数据。

*******************************************************************************/

      ret = kstrtoul(buf, 10,&state);

      if (ret)

             return ret;

/*************************************************************************************

kstrtoul 函数定义在Kstrtox.c (lib),将字符串转成成longlong型数据

参数含义:将buf的存放的字符串,转还成10位整数,存放到&state里面。成功返回0

*******************************************************************************/

 

      if (state == LED_OFF)

             led_trigger_remove(led_cdev);

/*************************************************************************************

熄灯

*******************************************************************************/

 

      __led_set_brightness(led_cdev,state);

 

/*************************************************************************************

调用s3c24xx_led_set(Leds-s3c24xx.c (drivers\leds))函数,该函数会通过设定在brightness文件的数据,点亮或者熄灭led。*******************************************************************************/
      return size;

}

刚才上面的介绍的只是led子系统初始化的代码,下面的led子系统注销的代码,比较简单:

static void __exit leds_exit(void)

{

   class_destroy(leds_class);

}

其中class_destroy调用的class_unregister函数,class_unregister函数跟class_create()是成对的,其为注销由class_create()注册的类,其中在Class.c(drivers\base)有定义:

void class_unregister(structclass *cls)

{

   pr_debug("device class '%s': unregistering\n",cls->name);

   remove_class_attrs(cls);

   kset_unregister(&cls->p->subsys);

}

remove_class_attrs删除那个类下的设置过属性的那几个文件max_brightness文件brightness等,

kset_unregister就是删除kobject。这得kobject就是对应的LEDn和它的父类LEDS目录,也就是说LEDS是一个kobject,LEDS是其目录下的led4 led5 led6 led7的父类是LEDS;kset是所有同一大类的kobject的集合,也就是在sysfs的下的目录,可见在sysfs下的的kset有:

1)Block:在系统中发现的每个块设备在该目录下对应一个子目录。每个子目录中

又包含一些属性文件,它们描述了这个块设备的各方面属性,如:设备大小。

2)Bus:在内核中注册的每条总线在该目录下对应一个子目录,如: ide pci scsi usbpcmcia 其中每个总线目录内又包含两个子目录:devices和drivers ,devices目录包含了在整个系统中发现的属于该总线类型的设备,drivers目录包含了注册到该总线的所有驱动。

3)Class:将设备按照功能进行的分类,如/sys/class/net目录下包含了所有网络接口。

4)Devices:包含系统所有的设备。

5)Kernel:内核中的配置参数

6)Module:系统中所有模块的信息

7)Firmware:系统中的固件

8)Fs:描述系统中的文件系统

9)Power:系统中电源选项

上面的9条摘录自(http://www.linuxidc.com/Linux/2012-05/60757.htm

网络上有附图可以表示kobject和kset的关系:



上图摘录自:http://www.cnblogs.com/leaven/archive/2010/04/24/1719191.html

 

有关led的操作先到这里,后续假如有需要会再次分析。

 

参考:http://www.cnblogs.com/leaven/archive/2010/04/24/1719191.html

http://blog.csdn.net/liuhaoyutz/article/details/13502557

http://blog.chinaunix.net/uid-27664726-id-3334662.html

http://www.linuxidc.com/Linux/2012-05/60757.htm

http://blog.sina.com.cn/s/blog_69dd1a0901010cr5.html

http://blog.chinaunix.net/uid-11319766-id-3253414.html

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息