驅動程序實例(二):LED設備驅動程序( platform + /sys接口)

結合以前對Linux內核的platform總線 ,以及對Linux內核的LED設備的驅動框架的分析,本文將編寫基於platform總線與/sys接口的LED設備的實例代碼並對其進行分析。html

platform總線分析,詳見Linux platform驅動模型linux

字符設備的cdev接口分析,詳見Linux字符設備驅動框架(一):Linux內核的LED設備驅動框架數據結構

硬件接口:框架

  CPU:s5pv210;函數

  LED的GPIO:GPIO_J0_3 ~ GPIO_J0_5;post

  LED的工做方式:低電平亮,高電平滅。學習

使用Linux內核的LED驅動框架以前,需確保Linux內核支持LED驅動框架。進入Linux內核的配置界面menuconfig進行設置,具體配置以下:測試

  Device Drivers  --->spa

    [*] LED Support  --->    指針

本文將以兩種形式編寫LED驅動代碼:

(1)將led_device和led_driver分別寫成兩個單獨的文件模塊;(這種作法適合學習使用)

(2)將led_device集成至Linux內核的/arch/arm/mach-s5pv210/mach-x210.c中,再將led_driver編寫爲獨立模塊。(這種作法更接近於實戰的驅動程序,更好的使用設備樹)

1.第一種寫法

1.1 led_device.c

本文將設備信息寫成一個模塊的形式,須要時加載該模塊便可。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>

//定義並初始化LED設備的相關資源
static struct resource led_resource = 
{
    .start = 0xE0200240,
    .end = 0xE0200240 + 8 - 1,
    .flags = IORESOURCE_MEM,
};

//定義並初始化LED設備信息
static struct platform_device led_dev = 
{
    .name = "led",             //設備名稱
    .id = -1,                  //設備數量,-1表示只有一個設備
    .num_resources = 1,        //資源數量
    .resource = &led_resource, //資源指針
    .dev = 
    {
        .release = led_release,
    },
};

//註冊LED設備
static int __init led_device_init(void)
{
    return platform_device_register(&led_dev);
}

//註銷LED設備
static void __exit led_device_exit(void)
{
    platform_device_unregister(&led_dev);
}

module_init(led_device_init);
module_exit(led_device_exit);

MODULE_AUTHOR("Lin");
MODULE_DESCRIPTION("led device for x210");
MODULE_LICENSE("GPL");

1.2 led_driver.c

led_driver_init():模塊加載函數
  platform_driver_register()將驅動對象模塊註冊到平臺總線
  led_probe()探測函數,提取相應的信息
    platform_get_resource()獲取設備資源
    request_mem_region()、ioremap()虛擬內存映射
    readl()、write()初始化硬件設備
    kzalloc()申請led_classdev內存
    led_classdev_register()註冊LED設備

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <linux/leds.h>

//定義GPIO_J0寄存器變量
typedef struct GPJ0REG
{
    volatile unsigned int gpj0con;
    volatile unsigned int gpj0dat;
}gpj0_reg_t;

static gpj0_reg_t *pGPIOREG = NULL;
static struct led_classdev *led_cdev = NULL;

//LED設備實際的硬件操做函數
void led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness)
{
    int reg_value = 0;
    
    //向LED設備屬性文件brightness寫入0,LED設備滅
    if (brightness == LED_OFF)
    {
        reg_value = readl(&(pGPIOREG->gpj0dat)); 
        reg_value |= (1 << 3) | (1 << 4) | (1 << 5);
        writel(reg_value, &(pGPIOREG->gpj0dat));
    }
    //不然,LED設備亮
    else
    {
        reg_value = readl(&(pGPIOREG->gpj0dat)); 
        reg_value &= ~((1 << 3) | (1 << 4) | (1 << 5));
        writel(reg_value, &(pGPIOREG->gpj0dat));
    }    
}

static int led_probe(struct platform_device *pdev)
{
    struct resource *res_led = NULL;
    int ret = -1;
    int reg_value = 0;
    
    /********************************申請資源*********************************/
    //獲取資源
    res_led = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res_led) 
    {
        return -ENOMEM;
    }
    
    //動態內存映射
    if (!request_mem_region(res_led->start, resource_size(res_led), "GPIOJ0"))
    {
        return -EBUSY;
    }
    
    pGPIOREG = ioremap(res_led->start, resource_size(res_led));
    if (pGPIOREG ==  NULL) 
    {
        ret = -ENOENT;
        goto ERR_STEP;
    }
    
    /********************************設備初始化********************************/
    //初始化資源,設置GPIO爲輸出模式
    reg_value = readl(&(pGPIOREG->gpj0dat)); 
    reg_value |= (1 << (3*4)) | (1 << (4*4)) | (1 << (5*4));
    writel(reg_value, &(pGPIOREG->gpj0dat));
    
    /********************************建立接口**********************************/
    //申請led_classdev內存
    led_cdev = kzalloc(sizeof(struct led_classdev), GFP_KERNEL);
    if (led_cdev == NULL)
    {
        ret = -ENOMEM;
        goto ERR_STEP1;
    }
    
    led_cdev->name = pdev->name;
    led_cdev->brightness_set = led_brightness_set;
    
    //註冊LED設備
    ret = led_classdev_register(NULL, led_cdev);
    if (ret) 
    {
        goto ERR_STEP2;
    }
    
    return 0;
    
    /******************************倒映式錯誤處理********************************/
ERR_STEP2:
    kfree(led_cdev);
    
ERR_STEP1:
    iounmap(pGPIOREG);    
    
ERR_STEP:
    release_mem_region(res_led->start, resource_size(res_led));
            
    return ret;
        
}
    
static int led_remove(struct platform_device *pdev)
{
    led_classdev_unregister(led_cdev); //註銷LED設備
    iounmap(pGPIOREG);                   //釋放內存
    kfree(led_cdev);                   //釋放內存
    
    return 0;
}

//定義並初始化LED驅動信息
static struct platform_driver led_drv = 
{
    .driver = 
    {
        .name  = "led",
        .owner = THIS_MODULE,
    },
    .probe = led_probe,
    .remove = led_remove,
};

static int __init led_driver_init(void)
{
    return platform_driver_register(&led_drv);
}

static void __exit led_driver_exit(void)
{
    platform_driver_unregister(&led_drv);
}

module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_AUTHOR("Lin");
MODULE_DESCRIPTION("led driver for x210");
MODULE_LICENSE("GPL");

2.第二種寫法

2.1 device

在/kernel/arch/arm/mach-s5pv210/include/mach目錄下,創建一個leds_gpio.h文件,並填充以下內容。

#ifndef __ASM_ARCH_LEDSGPIO_H
#define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h"

#define S3C24XX_LEDF_ACTLOW    (1<<0)        /* LED is on when GPIO low */
#define S3C24XX_LEDF_TRISTATE    (1<<1)      /* tristate to turn off */

//定義一個LED設備的數據結構
struct s3c24xx_led_platdata 
{
    unsigned int     gpio;
    unsigned int     flags;
    char            *name;
    char            *def_trigger;
};
#endif

在/kernel/arch/arm/mach-s5pv210/mach-x210.c下,添加以下內容,並添加對leds_gpio.h的包含。

/* LEDS */

static struct s5pv210_led_platdata s5pv210_led1_pdata = {
    .name        = "xled1",
    .gpio        = S5PV210_GPJ0(3),
    .flags       = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
    .def_trigger = "heartbeat",
};

static struct s5pv210_led_platdata s5pv210_led2_pdata = {
    .name        = "xled2",
    .gpio        = S5PV210_GPJ0(4),
    .flags       = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
    .def_trigger = "heartbeat",
};

static struct s5pv210_led_platdata s5pv210_led3_pdata = {
    .name        = "xled3",
    .gpio        = S5PV210_GPJ0(5),
    .flags       = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
    .def_trigger = "heartbeat",
};

static struct platform_device s5pv210_led1 = {
    .name      = "s5pv210_led",
    .id        = 1,
    .dev       = {
        .platform_data    = &s5pv210_led1_pdata,
    },
};

static struct platform_device s5pv210_led2 = {
    .name      = "s5pv210_led",
    .id        = 2,
    .dev       = {
        .platform_data    = &s5pv210_led2_pdata,
    },
};

static struct platform_device s5pv210_led3 = {
    .name      = "s5pv210_led",
    .id        = 3,
    .dev       = {
        .platform_data   = &s5pv210_led3_pdata,
    },
};

將LED設備信息集成至smdkc110_devices,內核初始化時smdkc110_devices中的設備將被註冊進內核。

static struct platform_device *smdkc110_devices[] __initdata = 
{
    ......
    &s5pv210_led1,
    &s5pv210_led2,
    &s5pv210_led3,
}; 

2.2 driver

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/gpio.h>
#include <linux/slab.h>

#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/leds-gpio.h>

#define X210_LED_OFF    1            // X210中LED是正極接電源,負極節GPIO
#define X210_LED_ON     0            // 因此1是滅,0是亮

struct s5pv210_gpio_led 
{
    struct led_classdev          cdev;
    struct s5pv210_led_platdata  *pdata; 
};

static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
    return platform_get_drvdata(dev);
}

static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
    return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}

//LED設備硬件操做函數
static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness value)
{
    struct s5pv210_gpio_led *led = to_gpio(led_cdev);
    struct s5pv210_led_platdata *pd = led->pdata;

    if (value == LED_OFF)
    {
        gpio_set_value(pd->gpio, X210_LED_OFF);
    }
    else
    {
        gpio_set_value(pd->gpio, X210_LED_ON);
    }

}


static int s5pv210_led_remove(struct platform_device *dev)
{
    struct s5pv210_gpio_led *led = pdev_to_gpio(dev);

    led_classdev_unregister(&led->cdev);//註銷LED設備
    kfree(led);                         //釋放內存
    gpio_free(led->pdata->gpio);        //釋放GPIO

    return 0;
}

static int s5pv210_led_probe(struct platform_device *dev)
{
    struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
    struct s5pv210_gpio_led *led;
    int ret;
    
    /*****************************申請GPIO資源******************************/
    ret = gpio_request(pdata->gpio, pdata->name);
    if (ret) 
    {
        printk(KERN_ERR "gpio_request failed, ret = %d.\n", ret);
        return -EIO;
    } 
    
    /************************申請,並初始化GPIO資源*************************/
    // 設置爲輸出模式,而且默認輸出1讓LED燈滅
    gpio_direction_output(pdata->gpio, 1);

    
    /********************************建立接口********************************/
    led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
    if (led == NULL) {
        dev_err(&dev->dev, "No memory for device\n");
        return -ENOMEM;
    }

    platform_set_drvdata(dev, led);//將led存入dev->p->driver_data

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

    //註冊LED設備
    ret = led_classdev_register(&dev->dev, &led->cdev);
    if (ret < 0) {
        dev_err(&dev->dev, "led_classdev_register failed\n");
        kfree(led);
        return ret;
    }

    return 0;
}

//定義並初始化驅動信息
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_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); MODULE_DESCRIPTION("S5PV210 LED driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:s5pv210_led");

3. 測試

裝載上述兩個模塊(device、driver)以後,進入/sys/class/leds目錄下,會發現多了一個led文件夾。進入led文件夾,會看到LED設備的屬性文件。

(1)向屬性文件brightness寫入0,"  echo  0  >  brightness ",LED設備滅;

(2)向屬性文件brightness寫入1,"  echo  1  >  brightness ",LED設備亮;

(3)讀取屬性文件max_brightness," cat  max_brightness ",獲取LED設備的最大亮度值。

相關文章
相關標籤/搜索