使用內核LED框架搭建驅動 ——led_classdev_register


#include <linux/init.h>            // __init   __exit
#include <linux/module.h>      // module_init  module_exit
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>

#include <asm/io.h>     //writel

#include <linux/ioport.h>    //request_mem_region
​
#include <asm/string.h> 

#include <linux/leds.h>

#define GPJ0_REGBASE 0xE0200240
typedef struct GPJ0REG
{
    volatile unsigned int gpj0Con;
    volatile unsigned int gpj0Dat;
    
}gpj0_reg_t;
gpj0_reg_t *pgpj0_reg =NULL;

struct led_classdev    cdev1;
struct led_classdev cdev2;
struct led_classdev cdev3;

void s5pv210_led1_set(struct led_classdev *led_cdev,enum led_brightness brightness);
void s5pv210_led2_set(struct led_classdev *led_cdev,enum led_brightness brightness);
void s5pv210_led3_set(struct led_classdev *led_cdev,enum led_brightness brightness);

void s5pv210_led1_set(struct led_classdev *led_cdev,enum led_brightness brightness)
{
    printk(KERN_INFO "s5pv210_led1_set successful %d\n",brightness);
    if(brightness == LED_OFF)
    {
        writel((readl(&pgpj0_reg->gpj0Con)&0xff0fffff),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Con)|0x00100000),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Dat)|(0x01<<5)),&pgpj0_reg->gpj0Dat);
    }
    else
    {
        writel((readl(&pgpj0_reg->gpj0Con)&0xff0fffff),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Con)|0x00100000),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Dat)&(~(0x01<<5))),&pgpj0_reg->gpj0Dat);
        
    }
}    
void s5pv210_led2_set(struct led_classdev *led_cdev,enum led_brightness brightness)
{
    printk(KERN_INFO "s5pv210_led2_set successful %d\n",brightness);
    if(brightness == LED_OFF)
    {
        writel((readl(&pgpj0_reg->gpj0Con)&0xfff0ffff),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Con)|0x00010000),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Dat)|(0x01<<4)),&pgpj0_reg->gpj0Dat);
    }
    else
    {
        writel((readl(&pgpj0_reg->gpj0Con)&0xfff0ffff),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Con)|0x00010000),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Dat)&(~(0x01<<4))),&pgpj0_reg->gpj0Dat);
        
    }
}    
void s5pv210_led3_set(struct led_classdev *led_cdev,enum led_brightness brightness)
{
    printk(KERN_INFO "s5pv210_led3_set successful %d\n",brightness);
    if(brightness == LED_OFF)
    {
        writel((readl(&pgpj0_reg->gpj0Con)&0xffff0fff),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Con)|0x00001000),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Dat)|(0x01<<3)),&pgpj0_reg->gpj0Dat);
    }
    else
    {
        writel((readl(&pgpj0_reg->gpj0Con)&0xffff0fff),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Con)|0x00001000),&pgpj0_reg->gpj0Con);
        writel((readl(&pgpj0_reg->gpj0Dat)&(~(0x01<<3))),&pgpj0_reg->gpj0Dat);
        
    }
    
}                      
static int __init s5pv210_led_init(void)
{
    int ret = -1;
    printk(KERN_INFO "s5pv210_led_init successful \n");
    cdev1.brightness_set = s5pv210_led1_set;
    cdev1.name = "led1";    
    ret = led_classdev_register(NULL, &cdev1);
    if (ret < 0) 
    {
        printk(KERN_WARNING "led_classdev_register fail \n");
        goto reg_err1;
    }
    
    cdev2.brightness_set = s5pv210_led2_set;
    cdev2.name = "led2";    
    ret = led_classdev_register(NULL, &cdev2);
    if (ret < 0) 
    {
        printk(KERN_WARNING "led_classdev_register fail \n");
        goto reg_err2;
    }
    
    cdev3.brightness_set = s5pv210_led3_set;
    cdev3.name = "led3";    
    ret = led_classdev_register(NULL, &cdev3);
    if (ret < 0) 
    {
        printk(KERN_WARNING "led_classdev_register fail \n");
        goto reg_err3;
    }
    
    if (!request_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t), "gpj0_reg")) 
    {
        ret =  -EBUSY;
        goto mem_err;
    }
    pgpj0_reg = ioremap(GPJ0_REGBASE, sizeof(gpj0_reg_t));
    
    return 0;

mem_err:
    led_classdev_unregister(&cdev3);
    
reg_err3:
    led_classdev_unregister(&cdev2);
    
reg_err2:
    led_classdev_unregister(&cdev1);
    
reg_err1:
    return ret;
}

static void __exit s5pv210_led_exit(void)
{    
    printk(KERN_INFO "s5pv210_led_exit successful \n");
    iounmap(pgpj0_reg);
    release_mem_region(GPJ0_REGBASE,sizeof(gpj0_reg_t));
    
    led_classdev_unregister(&cdev1);
    led_classdev_unregister(&cdev2);
    led_classdev_unregister(&cdev3);
    
}




module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx這種宏作用是用來添加模塊描述信息
MODULE_LICENSE("GPL");                // 描述模塊的許可證
MODULE_AUTHOR("musk");                // 描述模塊的作者
MODULE_DESCRIPTION("x210 LED driver");    // 描述模塊的介紹信息
MODULE_ALIAS("led_driver");            // 描述模塊的別名信息
View Code

一. 內核LED框架介紹:

    1.1. 在內核中相關文件

        1.1.1. 驅動框架規定的LED這種硬件的驅動框架在:drivers/leds目錄下

        1.1.2. led-class.c和led-core.c,這兩個文件是內核提供的,他們統一描述了內核中所有廠家的不同LED硬件的相同部分的邏輯。

        1.1.3. leds-xxxx.c,是由不同廠商的驅動工程師編寫添加的,廠商驅動工程師結合自己公司的硬件的不同情況來對LED進行操作,使用內核提供的接口來和驅動框架進行交互,最終實現驅動的功能。

    1.2. 使用LED框架和之前使用寫的LED驅動區別(register_chrdev)

        1.2.1. LED框架中相關最終去創建一個屬於/sys/class/leds這個類的一個設備。如何在這個類下有brightness      max_brightness   power           subsystem       uevent等文件來操作硬件

        1.2.2. 之前寫的LED驅動通過file_operations結構體綁定相關函數來操作硬件

        1.2.3. 這兩中方式是並列的。驅動開發者可以選擇其中任意一種方式來開發驅動。

二. 分析led-class.c文件

    2.1. subsys_initcall & module_init函數

        2.1.1. subsys_initcall是一個宏,定義在linux/init.h中。經過對這個宏進行展開,發現這個宏的功能是:將其聲明的函數放到一個特定的段:.initcall4.init。

            subsys_initcall

        __define_initcall("4",fn,4)

        2.1.2. 分析module_init宏,可以看出它將函數放到了.initcall6.init段中。

            module_init

        __initcall

    device_initcall

__define_initcall("6",fn,6)

        2.1.3. 內核在啟動過程中需要順序的做很多事,內核如何實現按照先后順序去做很多初始化操作。內核的解決方案就是給內核啟動時要調用的所有函數歸類,然后每個類按照一定的次序去調用執行。這些分類名就叫.initcalln.init。n的值從1到8。內核開發者在編寫內核代碼時只要將函數設置合適的級別,這些函數就會被鏈接的時候放入特定的段,內核啟動時再按照段順序去依次執行各個段即可。

    2.2. 經過分析,可以看出,subsys_initcall和module_init的作用是一樣的,只不過前者所聲明的函數要比后者在內核啟動時的執行順序更早。

    2.3. led_class_attrs數組

        2.3.1. 什么是attribute,對應將來/sys/class/leds/目錄里的內容,一般是文件和文件夾。這些文件其實就是sysfs開放給應用層的一些操作接口(非常類似於/dev/目錄下的那些設備文件)

        2.3.2. attribute有什么用,作用就是讓應用程序可以通過/sys/class/leds/目錄下面的屬性文件來操作驅動進而操作硬件設備。

        2.3.3. attribute其實是另一條驅動實現的路線。有區別於之前講的file_operations那條線。

    2.4. leds_init函數

        2.4.1. 函數內部調用

            leds_init

                class_create

        2.4.1. 此函數在/sys/class目錄下創建leds類文件

static int __init leds_init(void)
{
    leds_class = class_create(THIS_MODULE, "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;
}
View Code

    2.5. led_classdev結構體

        2.5.1. LED驅動框架中最終是通過該結構體與硬件關聯起來

struct led_classdev {
    const char        *name;
    int             brightness;
    int             max_brightness;
    int             flags;

    /* Lower 16 bits reflect status */
#define LED_SUSPENDED        (1 << 0)
    /* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME    (1 << 16)

    /* Set LED brightness level */
    /* Must not sleep, use a workqueue if needed */
    void        (*brightness_set)(struct led_classdev *led_cdev,
                      enum led_brightness brightness);
    /* Get LED brightness level */
    enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

    /* Activate hardware accelerated blink, delays are in
     * miliseconds and if none is provided then a sensible default
     * should be chosen. The call can adjust the timings if it can't
     * match the values specified exactly. */
    int        (*blink_set)(struct led_classdev *led_cdev,
                     unsigned long *delay_on,
                     unsigned long *delay_off);

    struct device        *dev;
    struct list_head     node;            /* LED Device list */
    const char        *default_trigger;    /* Trigger to use */

#ifdef CONFIG_LEDS_TRIGGERS
    /* Protects the trigger data below */
    struct rw_semaphore     trigger_lock;

    struct led_trigger    *trigger;
    struct list_head     trig_list;
    void            *trigger_data;
#endif
};
View Code

三.相關代碼分析led_classdev_register

     3.1. 函數內部關系

        led_classdev_register

    device_create

    3.2. led_classdev_register函數分析

        3.2.1. led_classdev_register這個函數其實就是去創建一個屬於leds這個類的一個設備。其實就是去注冊一個設備。所以這個函數其實就是led驅動框架中內核開發者提供給SoC廠家驅動開發者的一個注冊驅動的接口。

        3.2.2. 當我們使用led驅動框架去編寫驅動的時候,這個led_classdev_register函數的作用類似於我們之前使用file_operations方式去注冊字符設備驅動時的register_chrdev函數

/**
 * led_classdev_register - register a new object of led_classdev class.
 * @parent: The device to register.
 * @led_cdev: the led_classdev structure for this device.
 */
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))
        return PTR_ERR(led_cdev->dev);

#ifdef CONFIG_LEDS_TRIGGERS
    init_rwsem(&led_cdev->trigger_lock);
#endif
    /* add to the list of leds */
    down_write(&leds_list_lock);
    list_add_tail(&led_cdev->node, &leds_list);
    up_write(&leds_list_lock);

    if (!led_cdev->max_brightness)
        led_cdev->max_brightness = LED_FULL;

    led_update_brightness(led_cdev);

#ifdef CONFIG_LEDS_TRIGGERS
    led_trigger_set_default(led_cdev);
#endif

    printk(KERN_DEBUG "Registered led device: %s\n",
            led_cdev->name);

    return 0;
}
View Code

    3.3. led_classdev_unregister函數分析

/**
 * led_classdev_unregister - unregisters a object of led_properties class.
 * @led_cdev: the led device to unregister
 *
 * Unregisters a previously registered via led_classdev_register object.
 */
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

    device_unregister(led_cdev->dev);

    down_write(&leds_list_lock);
    list_del(&led_cdev->node);
    up_write(&leds_list_lock);
}
View Code

四. 實驗效果

[root@musk210 driver_test]# cd /sys/class/leds/led1/
[root@musk210 led1]# ls
brightness      max_brightness  power           subsystem       uevent
[root@musk210 led1]# echo 1 > brightness 
[ 3193.332848] s5pv210_led1_set successful 1
[root@musk210 led1]# ls
brightness      max_brightness  power           subsystem       uevent
[root@musk210 led1]# echo 0 > brightness 
[ 6081.310443] s5pv210_led1_set successful 0
[root@musk210 led1]# cd ../
[root@musk210 leds]# ls
led1    led2    led3    mmc0::  mmc1::  mmc2::  mmc3::
[root@musk210 leds]#
View Code

    4.1. 我們寫的驅動確實工作了,被加載了,/sys/class/leds/目錄下確實多出來了一個表示設備的文件夾。文件夾里面有相應的操控led硬件的2個屬性brightness和max_brightness

    4.2. led-class.c中brightness方法有一個show方法和store方法,這兩個方法對應用戶在/sys/class/leds/led1/brightness目錄下直接去讀寫這個文件時實際執行的代碼。

        4.2.1. 當我們show brightness時,實際就會執行led_brightness_show函數

        4.2.1. 當我們echo 1 > brightness時,實際就會執行led_brightness_store函數

    4.3. show方法實際要做的就是讀取LED硬件信息,然后把硬件信息返回給我們即可。所以show方法和store方法必要要會去操控硬件。但是led-class.c文件又屬於驅動框架中的文件,它本身無法直接讀取具體硬件,因此在show和store方法中使用函數指針的方式調用了struct led_classdev結構體中的相應的讀取/寫入硬件信息的方法。

 

參考《朱老師.課件_5.4.驅動框架入門之LED》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM