驅動程序實例(一):LED設備驅動程序( platform + cdev)

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

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

字符設備的cdev接口分析,詳見Linux字符設備驅動(一):cdev接口linux

硬件接口:函數

  CPU:s5pv210;post

  LED的GPIO:GPIO_J0_3 ~ GPIO_J0_5;測試

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

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");

2. led_driver.c

led_driver_init():模塊加載函數
  platform_driver_register()將驅動對象模塊註冊到平臺總線
  led_probe()探測函數,提取相應的信息
    platform_get_resource()獲取設備資源
    request_mem_region()、ioremap()虛擬內存映射
    readl()、write()初始化硬件設備
    cdev_alloc()申請cdev內存
    cdev_init()初始化cdev對象,將cdev與fops結構體綁定
    alloc_chrdev_region()申請設備號
    cdev_add()註冊LED設備對象,將cdev添加至系統字符設備鏈表中
    class_create()建立設備類
    device_create()建立設備文件code

#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>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/ioctl.h>

#define LED_IOC_MAGIC  'l'                  //ioctl幻數  
#define LED_IOC_MAXNR    2                  //ioctl最大命令序數
#define    LED_ON    _IO(LED_IOC_MAGIC, 0)   //ioctl自定義命令
#define    LED_OFF    _IO(LED_IOC_MAGIC, 1)

#define DEVNAME "led"        //設備名稱

static int led_major = 0;    //主設備號
static int led_minor = 0;    //次設備號
const  int led_count = 1;    //次設備數量

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

static gpj0_reg_t *pGPIOREG = NULL;

static dev_t led_devnum;                 //設備號
static struct cdev *led_cdev = NULL;     //設備對象

static struct class *led_cls = NULL;     //設備類
static struct device *led_dev = NULL;    //設備


//LED設備的ioctl函數實現
static int led_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
    int reg_value = 0;
    
    
    //檢測命令的有效性
    if (_IOC_TYPE(cmd) != LED_IOC_MAGIC) 
        return -EINVAL;
    if (_IOC_NR(cmd) > LED_IOC_MAXNR) 
        return -EINVAL;
    
    
    //根據命令,執行相應的硬件操做
    switch(cmd) 
    {
        case LED_ON:
                reg_value = readl(&pGPIOREG->gpj0dat); 
                reg_value &= ~((1 << 3) | (1 << 4) | (1 << 5));
                writel(&pGPIOREG->gpj0dat, reg_value);
        break;
        
        case LED_OFF:
                reg_value = readl(&pGPIOREG->gpj0dat); 
                reg_value |= (1 << 3) | (1 << 4) | (1 << 5);
                writel(&pGPIOREG->gpj0dat, reg_value);
        break;
        
        default:  
            return -EINVAL;
    }
    
    return 0;
}

static int led_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

//LED設備的write函數實現
static ssize_t led_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
    char kbuf[20] = {0};
    int reg_value = 0;
    
    memset(kbuf, 0, sizeof(kbuf));
    if (copy_from_user(kbuf, user_buf, count))
    {
        return -EFAULT;
    }
    
    if (kbuf[0] == '0')
    {
        reg_value = readl(&(pGPIOREG->gpj0dat)); 
        reg_value |= (1 << 3) | (1 << 4) | (1 << 5);
        writel(reg_value, &(pGPIOREG->gpj0dat));
    }
    else
    {
        reg_value = readl(&(pGPIOREG->gpj0dat));  
        reg_value &= ~((1 << 3) | (1 << 4) | (1 << 5));
        writel(reg_value, &(pGPIOREG->gpj0dat));
    }
    
    return 1;
}

//定義並初始化LED設備的操做集
static const struct file_operations led_ops = 
{
    .owner      = THIS_MODULE,
    .open       = led_open,
    .write      = led_write,
    .ioctl      = led_ioctl,
    .release    = led_release,
};


//LED設備的probe函數實現
static int led_probe(struct platform_device *pdev)
{
    struct resource *res_led = NULL;
    int ret = -1;
    int reg_value = 0;
    int i = 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->gpj0con)); 
    reg_value |= (1 << (3*4)) | (1 << (4*4)) | (1 << (5*4));
    writel(reg_value, &(pGPIOREG->gpj0con));
    
    /***************************建立接口(cdev)***************************/
    //申請cdev內存
    led_cdev = cdev_alloc();
    if (led_cdev == NULL)
    {
        ret = -ENOMEM;
        goto ERR_STEP1;
    }
    
    //初始化led_cdev(將led_cdev於led_ops關聯)
    cdev_init(led_cdev, &led_ops);
    
    //申請設備號
    ret = alloc_chrdev_region(&led_devnum, led_minor, led_count, DEVNAME);
    if (ret < 0)
    {
        goto ERR_STEP1;
    }
    
    //註冊LED設備對象(將cdev添加至系統字符設備鏈表中)
    ret = cdev_add(led_cdev, led_devnum, led_count);
    if (ret < 0)
    {
        goto ERR_STEP2;
    }
    
    //建立設備類
    led_cls = class_create(THIS_MODULE, DEVNAME);
    if (IS_ERR(led_cls)) 
    {
        ret = PTR_ERR(led_cls);
        goto ERR_STEP3;
    }
    
    //在設備類下建立設備文件
    led_major = MAJOR(led_devnum);
    for(i = led_minor; i < (led_count + led_minor); i++)
    {
        led_dev = device_create(led_cls, NULL, MKDEV(led_major, i), NULL, "%s%d", DEVNAME, i);
        if(IS_ERR(led_dev))
        {
            ret = PTR_ERR(led_dev);
            goto ERR_STEP4;
        }
    }
    
    return 0;

    /*******************************倒映式錯誤處理*******************************/
ERR_STEP4:
    for(--i; i >= led_minor; i--)
    {
        device_destroy(led_cls, MKDEV(led_major, i));
    }
    class_destroy(led_cls);
    
ERR_STEP3:
    cdev_del(led_cdev);
    
ERR_STEP2:
    unregister_chrdev_region(led_devnum, led_count);
    
ERR_STEP1:
    iounmap(pGPIOREG);    
    
ERR_STEP:
    release_mem_region(res_led->start, resource_size(res_led));
            
    return ret;     
}
    
int led_remove(struct platform_device *pdev)
{
    int i = 0;
    
    //刪除設備文件
    for(i = led_minor; i < (led_count + led_minor); i++)
    {
        device_destroy(led_cls, MKDEV(led_major, i));
    }
    
    //刪除設備類
    class_destroy(led_cls);
    //刪除設備對象
    cdev_del(led_cdev);
    //註銷設備號
    unregister_chrdev_region(led_devnum, led_count);
    //釋放內存
    iounmap(pGPIOREG);
    return 0;
}

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

//註冊LED驅動
static int __init led_driver_init(void)
{
    return platform_driver_register(&led_drv);
}

//註銷LED驅動
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");

3. 測試所用應用程序

運行應用程序以前,需確保上述兩個模塊(device、driver)被裝載。運行結果代表LED設備能被應用程序操做。orm

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>

#define    FILE_NAME    "/dev/led0"

#define    LED_ON    _IO(LED_IOC_MAGIC, 0)
#define    LED_OFF    _IO(LED_IOC_MAGIC, 1)

char WriteBuf[30];
char ReadBuf[30];
char ScanBuf[30];

int main(void)
{
    int fd = -1;
    int i = 0;
    
    //打開設備文件
    if ((fd = open(FILE_NAME, O_RDWR)) < 0)
    {
        printf("%s open error\n", FILE_NAME);
        return -1;
    }
    
    while (1)
    {
        memset(ScanBuf, 0, sizeof(ScanBuf));
        
        printf("please input data for LED\n");
        if (scanf("%s", ScanBuf))
        {
            //打開LED設備
            if (!strcmp(ScanBuf, "on"))
            {
                write(fd, "1", 1);
            }
            
            //關閉LED設備
            else if (!strcmp(ScanBuf, "off"))
            {
                write(fd, "0", 1);
            }
            
            //閃爍LED設備
            else if (!strcmp(ScanBuf, "flash"))
            {
                for (i=5; i>0; i--)
                {
                    ioctl(fd, LED_ON);
                    sleep(1);
                    
                    ioctl(fd, LED_OFF);
                    sleep(1);
                }
            }
else { break; } } } close(fd); return 0; }
相關文章
相關標籤/搜索