驱动程序实例(一):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; }