fl2440——驱动学习-LED驱动程序代码分析

1、驱动入口函数

接下来从Linux驱动的入口函数module_init(s3c_led_init);开始学习。可以看到这是由s3c_led_init开始。函数如下:

static int __init s3c_led_init(void)   /*一般用__init修饰的变量或者函数会编译到专门的一个段里面去,这个段的数据和函数只有在kernel初始化的时候会被调用,以后一定不会被使用,kernel可能会在以后的某个时候释放掉这个段所占用的内存,给别的地方使用*/
{
    int                    result;
    dev_t                  devno;
/*进行硬件初始化。若初始化失败,在内核缓冲区打印错误信息,并返回-ENODEV */
    if( 0 != s3c_hw_init() )
    {
        printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
        return -ENODEV;
    }

/*若硬件初始化成功,分配主、次设备号 */
    if (0 != dev_major) /*静态申请主、次设备号 */
    {
        devno = MKDEV(dev_major, 0);
        result = register_chrdev_region (devno, dev_count, DEV_NAME);
    }
    else/*动态申请未被占用的主、次设备号 */
    {
        result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);
        dev_major = MAJOR(devno);
    }

/*若设备号申请失败,在内核缓冲区打印错误信息,并返回-ENODEV */
    if (result < 0)
    {
        printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);
        return -ENODEV;
    } 
    printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);

/*动态为struct cdev 分配内存空间。若申请分配失败,打印错误信息,返回-ENOMEM */
    if(NULL == (led_cdev=cdev_alloc()) )
    {
        printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
        unregister_chrdev_region(devno, dev_count);
        return -ENOMEM;
    }

    led_cdev->owner = THIS_MODULE;/*把自己编写的模块插入内核,使其成为内核的一部分*/
    cdev_init(led_cdev, &led_fops);/* 初始化struct cdev内核字符设备*/

result = cdev_add(led_cdev, devno, dev_count);/*初始化struct cdev后,把它添加到系统中。传入 cdev 结构的指针,起始设备编号,以及设备编号范围。这时/dev下才会长出设备节点。*/

/*若设备注册失败,打印错误信息,并跳转到ERROR */
    if (0 != result)
    {   
        printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result); 
        goto ERROR;
    }

/*设备注册成功,打印相关信息*/
    printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n",DEV_NAME,dev_major,DRV_MAJOR_VER,DRV_MINOR_VER,DRV_REVER_VER);
    return 0;


ERROR:
    printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
    cdev_del(led_cdev);
    unregister_chrdev_region(devno, dev_count);
    return result;
}

1.1file_operations led_fops结构体

在系统内部,I/O设备的存取操作通过特定的的入口来进行,而这组特定的入口由驱动程序来提供的。通常这组设备驱动的接口是由结构体file_operations向系统说明的。这个结构体就像一个桥梁一样。

static struct file_operations led_fops = /*led_fops告诉系统在进行LED驱动操作时该进行哪些系统调用*/
{
    .owner = THIS_MODULE,   /*owner设置为THIS_MODULE 。这个是一个在 <linux/module.h> 中定义的宏*/
    .open = led_open,  /*打开设备*/
    .release = led_release, /*卸载设备*/
    .unlocked_ioctl = led_ioctl,
};

小结:可以看出大致注册设备的流程:硬件初始化=>申请设备的主、次设备号=>定义led_fops=>将设备插入内核,初始化(由struct cdev结构体完成)=>设备注册成功,出现设备节点。
其中,主、次设备号的申请有静态和动态两种方式。我们在申请的时候尽量选择动态申请,避免静态申请而导致的与原有设备号发生冲突。
struct cdev结构体。它是在<linux/cdev.h>里的,定义如下:

struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops;
        struct list_head list;
        dev_t dev;
        unsigned int count;
};

1.cdev 结构体中的 dev_t 成员定义了设备号,为 32 位,其中高 12 位为主设备号,低 20 位为次设备号。
其中,struct kobject 是内嵌的 kobject 对象;
struct module 是所属模块;
struct file_operations 为文件操作结构体。

2.使用以下宏可以从 dev_t 获得主设备号和次设备号:
引用

     MAJOR (dev_t dev);
     MINOR (dev_t dev);

3 . 而使用下面宏可以通过主设备号和次设备号生成 dev_t :

MKDEV (int major, int minor);

4.在运行时动态的获得一个独立的 cdev 结构,可以如下这么做:

struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;

5.在分配好 cdev 结构后,就用 cdev_init() 函数对其初始化:

void cdev_init (struct cdev *cdev, struct file_operations *fops)

6.cdev 中的 owner 要设置为 THIS_MOULE。
7.cdev 结构体设置完毕,最后一步就是要把这事告诉给内核,使用下面的函数:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

2、驱动退出函数

module_exit(s3c_led_exit); module_exit指定退出函数,完成一些收尾工作。函数如下:

static void __exit s3c_led_exit(void)/*卸载该模块*/
{
    dev_t devno = MKDEV(dev_major, dev_minor);

    s3c_hw_term();

    cdev_del(led_cdev);/* 释放cdev占用的内存*/
    unregister_chrdev_region(devno, dev_count);/* 释放原先申请的设备号*/

    printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n",DEV_NAME,DRV_MAJOR_VER,DRV_MINOR_VER,DRV_REVER_VER);

    return ;
}

3、硬件初始化函数

在驱动入口函数中,有对硬件进行初始化的函数s3c_hw_init(),下面学一学它。函数如下:

static int s3c_hw_init(void) /*硬件初始化函数*/
{
    int          i;
    volatile unsigned long  gpb_con, gpb_dat, gpb_up;

/*申请内存。这里的内存为开发板的物理内存,对应着与LED的相关的寄存器。这里标识了起始地址,内存长度(大小),名字。若出错则返回。*/    
    if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))
    {
        return -EBUSY;
    }

/*开始建立物理内存到虚拟内存的映射。内核启动后,操作的都是虚拟内存,如果要操作物理内存,就使用ioremap建立映射关系*/
    if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )
    {
        release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); /*申请的内存有可能有别的程序在占用,此时停止申请。*/
        return -ENOMEM;
    }

    for(i=0; i<dev_count; i++)
    {
        /* Set GPBCON register, set correspond GPIO port as input or output mode  */
        gpb_con = s3c_gpio_read(GPBCON_OFFSET); /*读GPBCON的值*/
        gpb_con &= ~(0x3<<(2*led[i]));   /* 清零相关位 */
        gpb_con |= GPIO_OUTPUT<<(2*led[i]); /*设置LED GPIO为OUTPUT模式*/
        s3c_gpio_write(gpb_con, GPBCON_OFFSET);/*写寄存器GPBCON的值*/

        /* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable  */
        gpb_up = s3c_gpio_read(GPBUP_OFFSET);
        //gpb_up &= ~(0x1<<led[i]); /* Enable pull up resister */
        gpb_up |= (0x1<<led[i]);  /* Disable pull up resister */
        s3c_gpio_write(gpb_up, GPBUP_OFFSET);

        /* Set GPBDAT register, set correspond GPIO port power level as high level or low level */
        gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
        //gpb_dat &= ~(0x1<<led[i]); /* This port set to low level, then turn LED on */
        gpb_dat |= (0x1<<led[i]);  /* This port set to high level, then turn LED off */
        s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
    }

    return 0;
}

3、LED清除函数

使用完毕后,要进行相应的清理工作。由s3c_hw_term(void)完成。函数如下:

static void s3c_hw_term(void)
{
    int                     i;
    volatile unsigned long  gpb_dat;

    for(i=0; i<dev_count; i++)
    {
        gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
        gpb_dat |= (0x1<<led[i]);  /* Turn LED off */
        s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
    }

    release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);/*释放申请的内存*/
    iounmap(s3c_gpb_membase);/*解除映射关系*/
}

4、LED的一些操作

4.1设备控制函数ioctl()

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。这里的ioctl()要接受从用户空间传过来的参数。

static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int which = (int)file->private_data; /*获取次设备号*/

/*由cmd传来的命令码,来执行LED的开或关操作*/
    switch (cmd)
    {
        case LED_ON:

            turn_led(which, LED_ON);
            break;

        case LED_OFF:
            turn_led(which, LED_OFF);
            break;

        default:
            printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
            print_help();
            break;
    }

    return 0;
}

4.2 led_open函数

static int led_open(struct inode *inode, struct file *file) /*在用户空间调用open函数,就会调用led_open,打开相应的设备节点*/
{
    int minor = iminor(inode); /* iminor函数可以获取设备节点的次设备号,由次设备号即可对该设备进行操作*/

    file->private_data = (void *)minor;

    printk(KERN_DEBUG "/dev/led%d opened.\n", minor);
    return 0;
}

4.3 led_release函数

static int led_release(struct inode *inode, struct file *file) /*在用户空间调用close函数,就会调用led_release,关闭节点*/
{
    printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));

    return 0;
}

4.3LED的开、关函数

static void turn_led(int which, unsigned int cmd)
{
    volatile unsigned long  gpb_dat;

    gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);

    if(LED_ON == cmd)
    {
        gpb_dat &= ~(0x1<<led[which]); /*  Turn LED On */
    }
    else if(LED_OFF == cmd)
    {
        gpb_dat |= (0x1<<led[which]);  /*  Turn LED off */
    }

    s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}

最后,头文件,宏和一些变量的定义如下:

#include <linux/module.h>   /* Every Linux kernel module must include this head */
#include <linux/init.h>     /* Every Linux kernel module must include this head */
#include <linux/kernel.h>   /* printk() */
#include <linux/fs.h>       /* struct fops */
#include <linux/errno.h>    /* error codes */
#include <linux/cdev.h>     /* cdev_alloc()  */
#include <asm/io.h>         /* ioremap()  */
#include <linux/ioport.h>   /* request_mem_region() */

#include <asm/ioctl.h>      /* Linux kernel space head file for macro _IO() to generate ioctl command  */
#ifndef __KERNEL__
#include <sys/ioctl.h>      /* User space head file for macro _IO() to generate ioctl command */
#endif



#define DRV_AUTHOR               "TangBin<tangbinmvp@gmail.com>"
#define DRV_DESC                  "S3C24XX LED driver"

#define DEV_NAME                  "led"
#define LED_NUM                   4

/* Set the LED dev major number */
//#define LED_MAJOR                 79
#ifndef LED_MAJOR
#define LED_MAJOR                 0
#endif

#define DRV_MAJOR_VER             1
#define DRV_MINOR_VER             0
#define DRV_REVER_VER             0

#define DISABLE                   0
#define ENABLE                    1

#define GPIO_INPUT                0x00
#define GPIO_OUTPUT               0x01


#define PLATDRV_MAGIC             0x60
#define LED_OFF                   _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON                    _IO (PLATDRV_MAGIC, 0x19)

#define S3C_GPB_BASE              0x56000010
#define GPBCON_OFFSET             0
#define GPBDAT_OFFSET             4
#define GPBUP_OFFSET              8
#define S3C_GPB_LEN               0x10        /* 0x56000010~0x56000020  */

int led[LED_NUM] = {5,6,8,10};  /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */

static void __iomem *s3c_gpb_membase;


#define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase)
#define s3c_gpio_read(reg)       __raw_readl((reg)+s3c_gpb_membase)


int dev_count = ARRAY_SIZE(led);
int dev_major = LED_MAJOR;
int dev_minor = 0;
int debug = DISABLE;

static struct cdev      *led_cdev;

到此,代码分析完毕,接下来就可以编写应用程序放到开发板来测试一下了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值