OpenRisc-18-or1200下linux简单gpio字符设备驱动

来源:互联网 发布:深圳号码数据 编辑:程序博客网 时间:2024/05/22 05:02
每周开始上班那一天都特别累,当然,我的宗旨是不上班一定要比上班累~年轻人嘛,多出去走走,所以我现在的工作很轻松,每天想着每周工作两天就够了。

        好了,休息五天的第一次,公司没安排事情做就自己瞎搞咯,找后勤阿姨们吹吹水,调杯咖啡喝,上网看看新闻,基本上第一天上午我就是这样浪费的,现在下午的第一天,对了,最近研发新招来了三个研究生,两个是我们学校的师兄,但是对不起也要说一句了,真的好屌丝喔,怎么会沦落来和我这种级别的混了咧,好歹也是研究生们啊,哥······不吐槽了,有时候真蛮无语的,当然实际上现实生活没和他们谈过怎么会来这公司,不过现在和他们两个还蛮聊得来~

        好~废话不多说了,前几次我们把linuxboot通了,这所谓的移植也只是个名头而且,真正的移植有时间可以去看看\arch\openrisc下的移植代码,强调我现在也是在用而已,按我的理解的话不是真正意义的开发。

        那就继续先用这吧,好,现在回来《or1200软件环境搭建》的过程来,那时我们在虚拟机cross compilelrzlsz文件,这是因为什么,因为现在我们只是boot通了内核,除了UART这个外设之外我们在内核或模块程序中有关于ipcores的驱动,所以,我们想在这个最简单的内核上调试程序的时,至少在网卡调通之前我们能用串口下载程序代码,不幸中的万幸了,基于串口的通信协议来传输文件,而lrzlsz就是串口的z-mode协议的实现。

        好,既然如此,把lrzlsz扔到openrisc-3.1\arch\openrisc\support\initramfs\bin目录下

        回到ipcores上面,在opencores的网站上找到simple_gpio这个工程,下载下来。

        添加到SOC上,按照我自己做的SOC,地址设置为0x91000000

        千万千万记得,把板上的LED灯资源绑到GPIO Controller的端口上

        Tcl脚本文件

        现在打开openrisc目录下device-tree文件openrisc-3.1\arch\openrisc\boot\dts


        把最后关于simple_gpio的设备描述加上去

        然后,按照《or1200移植linux》的过程重新编译linux源码,生成uImage即可。

        重新download uImage启动之后,可在\bin目录下找到lrzlsz

        当输出lrz的时候会弹出传输文件框


        现在可以随意找个文件测试一下板子的当前设置的baudrate下能不能无错传输。

        对了,又想起一件事,上一届来公司的应届的学生,来了十几个,也就是我们这批了,有个兄弟前几天跟我说他辞职回桂林了,然后我就问题辞职你要干嘛,他就说回去跟一个小研发团队做项目创业去~估计3月底就撤了~至于什么项目这里就帮他保密一下了,但是我觉得做出来,推得早的话还蛮有市场的,这里也预祝下小朱能升级到朱总,哪天想起我就把我招过去当小弟就够了~呵呵~~~

        好,基本步骤就此为止,现在转入到gpio字符驱动代码当中去。

        参考宋宝华老师的《Linux设备驱动开发详解》第6章——字符设备驱动。


        根据我自己的理解总结一下编写字符驱动模块的一般步骤:

        1.根据自己编程习惯选择包含cdev的自定义结构体或直接使用cdev结构体。

        2.例化file_operations结构体,然后填充文件操作的有关成员函数,并根据自己要求编写有关成员函数操作。

        3.编写模块加载函数,包括io资源申请注册,中断号申请注册,设备号申请注册,内存申请,注册字符设备,

        4.编写模块卸装函数,加载模块的逆操作。

        5.封装成2.6内核的驱动设备模型platform机制,包括编写platform_driver模块加载函数和卸装函数,填充platform_driver结构体的proberemovesuspendresume等成员,编写各成员函数。

        6.编写应用层的测试代码。


        好,到资源栏下载我自己编写的simple gpio controller的字符驱动,对照一面的一般步骤一步步看代码

        1.cdev结构体,这里我选择编写包含cdev的自定义结构体

[cpp] view plaincopyprint?
  1. struct simple_gpio{  
  2.     void __iomem *base;  
  3.     struct cdev gpio_cdev;  
  4. };  

        2.file_operations结构体

        例化file_operations类,gpio controller operation

[cpp] view plaincopyprint?
  1. struct file_operations gpio_ctl_ops = {  
  2.     .owner =  THIS_MODULE,   
  3.     .read =  gpio_read,   
  4.     .write =  gpio_write,   
  5.     .unlocked_ioctl = gpio_ioctl,   
  6.     .open =  gpio_open,   
  7.     .release =  gpio_release,  
  8. };  

        编写file_operation成员函数,只实现openioctl函数

[cpp] view plaincopyprint?
  1. /******************************************* for file operations *******************************************/  
  2.   
  3. int gpio_open(struct inode *inode, struct file *file){  
  4.     struct simple_gpio *gpio;  
  5.     gpio = container_of(inode->i_cdev, struct simple_gpio, gpio_cdev);  
  6.     file->private_data = gpio;  
  7.     return 0;  
  8. }  
  9.   
  10. int gpio_release(struct inode *inode, struct file *file){  
  11.     return 0;  
  12. }  
  13.   
  14. ssize_t gpio_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops){  
  15.     return count;  
  16. }  
  17.   
  18. ssize_t gpio_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops){  
  19.     return count;  
  20. }  
  21.   
  22. long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long data){  
  23.     struct simple_gpio *gpio = file->private_data;     
  24.     switch(cmd){  
  25.         case LED_ON:  
  26.             simple_gpio_write8(gpio, SIMPLE_GPIO_DAT, 0x00);  
  27.             break;  
  28.                   
  29.         case LED_OFF:  
  30.             simple_gpio_write8(gpio, SIMPLE_GPIO_DAT, 0xff);  
  31.             break;  
  32.                       
  33.         default:  
  34.             printk(KERN_ALERT"led control : no cmd run  [ --kernel-- ]\n");  
  35.             return (-EINVAL);  
  36.     }     
  37.     return 0;                         
  38. }  

        3.模块加载函数

        这里说明下,函数大部分流程我放在platform driverprobe函数中实现

[cpp] view plaincopyprint?
  1. /******************************************* for char device driver *******************************************/  
  2.   
  3. static int __devinit simple_gpio_setup(struct simple_gpio *gpio){  
  4.     cdev_init(&gpio->gpio_cdev, &gpio_ctl_ops);  
  5.     gpio->gpio_cdev.owner = THIS_MODULE;  
  6.     register_chrdev_region(MKDEV(GPIO_MAJOR, GPIO_MINOR), 1, "simple_gpio");  
  7.     return cdev_add(&gpio->gpio_cdev, MKDEV(GPIO_MAJOR, GPIO_MINOR), 1);  
  8. }  
  9.   
  10. /******************************************* for platform device driver *******************************************/  
  11.   
  12. static int __devinit simple_gpio_probe(struct platform_device *pdev){  
  13.     int ret;  
  14.     struct simple_gpio *gpio;  
  15.     struct resource *io_res, *irq_res;    
  16.   
  17.     /* get resources info*/  
  18.     io_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
  19.     if (!io_res)  
  20.         return -ENODEV;  
  21.   
  22.     irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);  
  23.     if (!irq_res)  
  24.         return -ENODEV;  
  25.       
  26.     /* request memery for simple_gpio */  
  27.     gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);  
  28.     if (!gpio)  
  29.         return -ENOMEM;  
  30.       
  31.     if (!devm_request_mem_region(&pdev->dev, io_res->start,  
  32.                      resource_size(io_res), pdev->name)){  
  33.         dev_err(&pdev->dev, "Memory region busy\n");  
  34.         return -EBUSY;  
  35.     }  
  36.       
  37.     /* map io memery to kenel space */    
  38.     gpio->base = devm_ioremap_nocache(&pdev->dev, io_res->start,  
  39.                      resource_size(io_res));  
  40.     if (!gpio->base){  
  41.         dev_err(&pdev->dev, "Unable to map registers\n");  
  42.         return -EIO;  
  43.     }  
  44.       
  45.     /* register simple_gpio char device */  
  46.     simple_gpio_setup(gpio);  
  47.   
  48.     /* set outputs and light leds */  
  49.     simple_gpio_led_init(gpio);  
  50.       
  51.     /* register interrupt */  
  52.     ret = devm_request_irq(&pdev->dev, irq_res->start, simple_gpio_isr, 0,  
  53.                    pdev->name, gpio);  
  54.     if(ret){  
  55.         dev_err(&pdev->dev, "Cannot claim IRQ\n");  
  56.         return ret;  
  57.     }  
  58.       
  59.     /* save struct gpio as device private data */  
  60.     platform_set_drvdata(pdev, gpio);  
  61.       
  62.     /* mount into sysfs */  
  63.     gpio_class = class_create(THIS_MODULE, "gpio_class");  
  64.     device_create(gpio_class, NULL, MKDEV(GPIO_MAJOR, GPIO_MINOR), NULL, "led");  
  65.       
  66.     return 0;  
  67. }     


        4.模块卸装函数

        与加载函数相同,流程放在platform driverremove函数中实现

[cpp] view plaincopyprint?
  1. /******************************************* for char device driver *******************************************/  
  2. static void __devexit simple_gpio_clean(struct simple_gpio *gpio){  
  3.     unregister_chrdev_region(MKDEV(GPIO_MAJOR, GPIO_MINOR), 1);  
  4.     cdev_del(&gpio->gpio_cdev);  
  5. }  
  6.   
  7. /******************************************* for platform device driver *******************************************/  
  8.   
  9. static int __devexit simple_gpio_remove(struct platform_device* pdev){  
  10.     struct simple_gpio *gpio = platform_get_drvdata(pdev);  
  11.       
  12.     /* extinguish leds */  
  13.     simple_gpio_led_exit(gpio);  
  14.       
  15.     /* remove data */     
  16.     platform_set_drvdata(pdev, NULL);  
  17.       
  18.     /* unregister simple_gpio char device */  
  19.     simple_gpio_clean(gpio);  
  20.       
  21.     device_destroy(gpio_class, MKDEV(GPIO_MAJOR, GPIO_MINOR));  
  22.     class_destroy(gpio_class);  
  23.       
  24.     return 0;  
  25. }  

        

        5.封装成platform机制

        对于这个步骤,基本上是一个固定的格式,个人理解就是通用的字符设备驱动套进去platform机制,至于这个机制,很多blog都有解释,这里就不详细再说明了,主要是platform_device,platform_driverbus三者之间的关系,platform_driver有一系列的操作函数,platform_device对设备的属性描述。

[cpp] view plaincopyprint?
  1. #define simple_gpio_suspend NULL  
  2. #define simple_gpio_resume  NULL  
  3.   
  4. static struct of_device_id simple_gpio_match[] = {  
  5.     { .compatible = "opencores,simple_gpio", },  
  6.     {},  
  7. };  
  8. MODULE_DEVICE_TABLE(of, simple_gpio_match);  
  9.   
  10. /* work with hotplug and coldplug */  
  11. MODULE_ALIAS("platform:simple_gpio");  
  12.   
  13. static struct platform_driver simple_gpio_driver = {  
  14.     .probe = simple_gpio_probe,  
  15.     .remove = __devexit_p(simple_gpio_remove),  
  16.     .suspend = simple_gpio_suspend,  
  17.     .resume  = simple_gpio_resume,        
  18.     .driver = {  
  19.         .owner = THIS_MODULE,  
  20.         .name = "simple_gpio",  
  21.         .of_match_table = simple_gpio_match,  
  22.     },  
  23. };  
  24.   
  25. static int __init simple_gpio_init(void){  
  26.     return platform_driver_register(&simple_gpio_driver);  
  27. }  
  28.   
  29. static void __exit simple_gpio_exit(void){  
  30.     platform_driver_unregister(&simple_gpio_driver);  
  31. }  

        6.测试文件

        在linux应用层去做文件打开、读写、关闭操作相信学C的时候就应该有深刻的理解,这里的我们在驱动上没有实现readwrite函数的具体操作,只实现了ioctl的操作,所以测试文件很简单,目的是看到LED灯闪烁的现象,所以只是简单打开设备文件,执行在驱动中定义好的命令而已。

[cpp] view plaincopyprint?
  1. //------------------------------------- main ---------------------------------------------  
  2. int main(void)  
  3. {  
  4.         int fd;  
  5.     int ret;  
  6.     char *i;  
  7.   
  8.         printf("\nstart simple_gpio_led_driver test ! \n\n");  
  9.         sleep(1);  
  10.         fd = open(DEVICE_NAME, O_RDWR);   
  11.     printf("fd = %d\n",fd);  
  12.         if (fd == -1){  
  13.                 printf("open device %s error !\n",DEVICE_NAME);  
  14.         }  
  15.         else{  
  16.         while(1){     
  17.             ioctl(fd,LED_OFF);  
  18.             printf ("leds is off ! \n");  
  19.             sleep(1);//sleep for 1s  
  20.             ioctl(fd,LED_ON);  
  21.             printf ("leds is on ! \n");  
  22.             sleep(1);  
  23.         }  
  24.             // close   
  25.         ret = close(fd);  
  26.         printf ("ret=%d\n",ret);  
  27.         printf ("close gpio_led_driver test\n");  
  28.         }  
  29.         return 0;  
  30. }  

        至于代码中有少量的注释,或者大家可以自己理解理解,当是自学的过程,主要还是参考宋宝华老师的书,有问题的话留言大家交流交流。


        现在把simple_gpio目录挂到虚拟机里,然后在terminalmake,得到设备驱动模块文件

        然后在terminal中编译simple_led.c文件

        or32-linux-gcc -o simple_led simple_led.c


        最后需要的是两个文件,simple_gpio_driver.kosimple_led


        提取出来,然后在SecureCRT中输入lrz


        将这两个文件添加到发送列表中,然后确定发送,


        然后更改simple_led的文件属性


        然后就可以加载module和运行测试代码


        如果顺利的话,在加载模块的同时板子的上LEDs会同时light up


        对应加载模块后的内核信息


        用modinfo命令可以看到在编写字符驱动时的模块信息


        然后运行测试代码


        在运行simple_led后,板子上LED 不断light up or down,同时在控制台上到有相应的提示信息输出。


        到此,关于GPIO Controller的字符设备驱动就介绍到这里,如果目前想在驱动方面打发打发时间的话就可以啃相关的学习书籍······所以说目前我在公司就是这样打发时间的了~谁叫公司就养我在这又不给活我干咧~加油吧~

        下一次写点什么也不知道了,好像还研究了frame buffI2C一段时间,下次看看有没有能力总结经验出来,现在还是努力地先学,目前在看txj老师录的关于RAW-OS的培训视频,学,找回激情,保持激情。