s3c2440 LED驱动分析

        这个开发板已经很久没有动了,这一次辞职后想来想去还是选择去做驱动吧。以前写的那些驱动代码早就不知道哪里去了,当然更不记得了。所以现在从头开始学习,也顺便记录下笔记;


原理

        首先看看LED的电路图:

        s3c2440 LED驱动分析_第1张图片

        不难看出,LED1==GPB5   LED2==GPB6   LED3==GPB7    LED4==GPB8

        然后就去看看IO端口图:

        s3c2440 LED驱动分析_第2张图片

        要设置的非常简单,就是把GPBCON设置为输出,GPBDAT设置为0时,则灯亮;设置为1时,则灯灭;


相关知识点

        其实上一篇中s3c2440系统自带的管脚宏和函数已经分析过那些引脚宏;

        S3C2410_GPB(5):表示GPB5的虚拟地址;

        s3c2410_gpio_setpin(S3C2410_GPB(5), 0):表示给GPB5数据位的虚拟地址上设置为0;其实就是GPBDAT中对应位设置为0;

        s3c2410_gpio_cfgpin(S3C2410_GPB(5), S3C2410_GPIO_OUTPUT):表示给GPB5控制位的虚拟地址上设置为输出;其实就是GPBCON中对应位设置为输出状态;


下面涉及的结构体和函数都在  include/cdev.h中可以找到

字符设备结构体cdev:     

struct cdev {
	struct kobject kobj;
	struct module *owner;// 模块属于谁
	const struct file_operations *ops;// 对应的操作函数
	struct list_head list;
	dev_t dev;// 设备号
	unsigned int count;
};

struct cdev *cdev_alloc(void);为字符设备结构体申请内存;

void cdev_init(struct cdev *, const struct file_operations *);把字符设备结构体和实现的操作函数绑定起来;

int cdev_add(struct cdev *, dev_t, unsigned);把设备号和字符设备结构体绑定起来,其实这里就相当于把我们自己实现的操作函数和设备号对接起来。上层应用打开的文件是根据设备号,也就是说上层操作的仅仅是和设备号有关系,而要上层应用去使用我们自己设计的函数,就必须把实现的函数和设备号绑定起来。这样他们之间就有关系了。(语言表达有限,自己可以体会下)

void cdev_del(struct cdev *);把字符设备卸载下来;

void cdev_put(struct cdev *p);对字符设备结构体内存的释放;(我程序中使用到这个却是说未定义,但在源代码cdev.h中却能找到这个函数,不知道为什么会这样)

还有些函数等使用到的时候再去分析;


实现代码

驱动实现代码

#include
 #include
 #include
 #include
 #include
 #include
 #include
 
 #define MAJOR_NUM 250 // 主设备号
 #define MINOR_NUM 0   // 次设备号
 #define DEV_NAME "yzh_led_test" //驱动名称,加载完后用命令 cat /proc/devices可以查看到这个
 
 #define led_on      1
 #define led_off     2
 #define all_led_on  3
 #define all_led_off 4
 
 static unsigned long led_IO_port[]=
 {
     S3C2410_GPB(5),
     S3C2410_GPB(6),
     S3C2410_GPB(7),
     S3C2410_GPB(8),
 };
  
 static struct cdev *cdevp = NULL; // 字符设备结构体cdev
 static int devNum;
 
 int myOpen(struct inode *inode, struct file *file)
 {
     printk(KERN_INFO"in myOpen() will open led!\n");
 
     if (file->f_mode & FMODE_READ)
         printk(KERN_INFO"file is readonly!\n");
     else if (file->f_mode & FMODE_WRITE)
         printk(KERN_WARNING"file is writeonly!\n");
     else
         printk(KERN_DEBUG"file is read and write!\n");
 
     return 0;
 }
 
 static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
 {
     int i;
    
     if (0 > arg || 4 < arg)
         return -EINVAL;

    printk(KERN_INFO"cmd:%u, ledNum:%ld\n", cmd, arg);
     switch(cmd){
         case led_on:
             s3c2410_gpio_setpin(led_IO_port[arg], 0);
             break;
 
         //case led_off:
         case 5:
             s3c2410_gpio_setpin(led_IO_port[arg], 1);
             break;
 
         case all_led_on:
             for (i = 0; i < 4; i++)
                 s3c2410_gpio_setpin(led_IO_port[i], 0);
             break;
 
         case all_led_off:
             for (i = 0; i < 4; i++)
                 s3c2410_gpio_setpin(led_IO_port[i], 1);
             break;
 
         default:
             return -EINVAL;
     }
     return 0;
 }

struct file_operations fops =
 {
     .owner = THIS_MODULE,
     .open  = myOpen,
     .ioctl = led_ioctl,
 };
 
 static int __init myDrive_init(void)
 {
     int ret, i;
     dev_t allocDevNum;
 
     printk(KERN_INFO"led init!\n");
 
     devNum = MKDEV(MAJOR_NUM, MINOR_NUM);//根据主设备号和次设备号得到设备号
     ret = register_chrdev_region(devNum, 1, DEV_NAME);// 静态申请设备号,设备号不变
     if (ret < 0){
         printk(KERN_INFO"register_chrdev_region error!\n");
         ret = alloc_chrdev_region(&allocDevNum, 0, 1, DEV_NAME);// 动态申请设备号,设备号是系统分配的
         if (ret < 0){
             printk(KERN_INFO"alloc_chrdev_region error!\n");
             return -EINVAL;
         }
         devNum = allocDevNum;
     }

     printk(KERN_INFO"devNum:%d, major:%d, minor:%d\n", devNum, MAJOR(devNum), MINOR(devNum));
 
     cdevp = cdev_alloc();
     cdev_init(cdevp, &fops);
     cdevp->owner = THIS_MODULE;
 
     ret = cdev_add(cdevp, devNum, 1);
     if (ret){
         printk(KERN_NOTICE"Error %d adding cdev", ret);
         return -EINVAL;
     }
 
     for (i = 0; i < 4; i++){// 初始化LED  GPBCON和熄灭所有灯
         s3c2410_gpio_cfgpin(led_IO_port[i], S3C2410_GPIO_OUTPUT);
         s3c2410_gpio_setpin(led_IO_port[i], 1);
     }
     return 0;
 }
 
 static void __exit myDrive_exit(void)
 {
     printk(KERN_INFO"myDrive exit!\n");
     cdev_del(cdevp);
     unregister_chrdev_region(devNum, 1);
 }
 module_init(myDrive_init);
 module_exit(myDrive_exit);
 MODULE_LICENSE("Dual BSD/GPL");
        上面的代码都通过测试的,我用ioctl(fd, 2, n)总是报错误地址,所以在代码中我用 5 来替换 led_off(其实这是2)。其他的就没问题,当然这是这基本的一个代码,至于流水灯,跑马灯啊,有兴趣就自己去实现,这里就给个点灯的代码思路;


编译和测试

        编译驱动有两种方法,可以参考上一篇blog:驱动的两种编译方法;我两种方法都实验过,我们可以先用动态加载,然后测试,最后确定好了,再把该驱动放到内核中;

        首先把 .ko文件用nfs共享给开发板,开发板中insmod xx.ko,把驱动加载进内核,用命令 cat /proc/devices可以查看到加载的驱动,用lsmod也可以看到。当然我们要的是主设备号。

        接下来就在 /dev/下创建一个设备文件,用来对应你的驱动。 mknod /dev/myLed  c  250  0;根据主设备号在/dev下创建个驱动对应的设备文件。我们测试的时候就是操作这个文件的;

下面看看测试代码:

#include
 #include
 #include
 #include
 
 int main(int argc, char* argv[])
 {
     int flag, ledNum, ret;
     int fd = open("/dev/charDevice", O_RDWR);
     if (fd < 0){ 
         printf("open fild!\n");
         printf("errno:%d\n", errno);
         return -1; 
     }   
     
     sscanf(argv[1], "%d", &flag);
     sscanf(argv[2], "%d", &ledNum);
     
     printf("fd:%d, argv[1]--flag:%d, argv[2]--ledNum:%d\n\n", fd, flag, ledNum);
     ret = ioctl(fd, flag, ledNum);
     if (ret < 0){ 
         printf("error in ioctl, errno:%d\n", errno);
     }   
     return 0;
 }

        可能有些多余的语句,那是我开始用来调试驱动的。这里有个errno比较好用,当出错时,可以查看下errno的错误码,方便调试;


动态创建设备文件

        在上面测试的时候需要手动在dev下创建一个字符设备驱动对应的设备文件,这样不够灵活,也比较麻烦。我们可以在驱动中调用函数就可以创建对应的设备文件(因为你静态编译到内核中时,不可能每次使用这个设备都要手动的去mknod,再个一般非专业的就知道操作文件)。
        在linux/device.h头文件中有几个函数;
       struct  class 结构体,在内核源代码中找不到这个结构体定义的。只能在devices.txt中找到对这个结构体的说明
struct class { ...
	int  (*suspend)(struct device *dev, pm_message_t state);
	int  (*resume)(struct device *dev); };
        不过我查了下资料,有一篇blog提到找到了这个结构体: http://blog.csdn.net/bingqingsuimeng/article/details/7929283;我不知道是不是我内核版本问题。
        static struct class *myClass = class_create(THIS_MODULE, "yzh_led_test"); // 创建一个设备类,第二个参数是你的驱动名称
        struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);第一个参数是上面定义的设备类,第二个参数是父设备,第三个参数是自己的设备号,第四个参数是设备的数据,后面的是可变参数列表;

        对应的有创建就会有销毁函数:
        device_destroy(myClass, MKDEV(MAJOR(devNum), 0));// 销毁设备文件
        class_destroy(myClass);// 销毁设备类

下面看下我代码中修改的部分:
创建部分(注意顺序):
 myClass = class_create(THIS_MODULE, "yzh_led_test");// 定义了个本地全局变量,static struct class *myClass = NULL;
 
     cdevp = cdev_alloc();
     cdev_init(cdevp, &fops);
     cdevp->owner = THIS_MODULE;
 
     ret = cdev_add(cdevp, devNum, 2); // 记住这里是创建两个设备,个人理解:这个count是该驱动将管理的设备个数
     if (ret){
         printk(KERN_NOTICE"Error %d adding cdev", ret);
         return -EINVAL;
     }   
 
     for (i = 0; i < 2; i++){// 创建两个设备文件,通过次设备号来区分
         device_create(myClass, NULL, MKDEV(MAJOR(devNum), i), NULL, "yzhLedTest%d",  i);
     }   
销毁部分(注意顺序):
    cdev_del(cdevp); 
     device_destroy(myClass, MKDEV(MAJOR(devNum), 0));// 要销毁两个设备文件,根据次设备号不同来释放
     device_destroy(myClass, MKDEV(MAJOR(devNum), 1));
     class_destroy(myClass); // 销毁设备类
     unregister_chrdev_region(devNum, 1);// 销毁设备号,因为上面设备文件中关联了设备号,所以要最后释放设备号
        这时候当驱动加载完后在/dev目录下就会自动创建两个文件:yzhLedTest0 和 yzhLedTest1;两个文件都关联到了驱动;     
       上面都是我自己实验过的,如果有什么不正确的地方,希望大家指正,谢谢!!


        当然也可以用write()函数来实现点灯操作,或者其他方法,总之都是大同小异。     


        转载请注明作者和原文出处,原文地址: http://blog.csdn.net/yuzhihui_no1/article/details/45116185
        若有不正确之处,望大家指正,共同学习!谢谢!!!


你可能感兴趣的:(Linux驱动)