找回密码
 注册
关于网站域名变更的通知
查看: 546|回复: 3
打印 上一主题 下一主题

linux学习之路_or1200下linux简单gpio字符设备驱动(上)

[复制链接]
  • TA的每日心情

    2019-11-20 15:22
  • 签到天数: 2 天

    [LV.1]初来乍到

    跳转到指定楼层
    1#
    发表于 2021-7-26 13:28 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

    EDA365欢迎您登录!

    您需要 登录 才可以下载或查看,没有帐号?注册

    x
    9 e8 x% Y! D& Z+ m: T: u  Z% y
            前几次我们把linux给boot通了,这所谓的移植也只是个名头而且,真正的移植有时间可以去看看\arch\openrisc下的移植代码,强调我现在也是在用而已,按我的理解的话不是真正意义的开发。! j0 `! S" n8 H. Q+ c+ v3 g+ m
    : o7 ?0 h. Y( n& o2 p8 K
            那就继续先用这吧,好,现在回来《or1200软件环境搭建》的过程来,那时我们在虚拟机cross compile了lrz和lsz文件,这是因为什么,因为现在我们只是boot通了内核,除了UART这个外设之外我们在内核或模块程序中有关于ipcores的驱动,所以,我们想在这个最简单的内核上调试程序的时,至少在网卡调通之前我们能用串口下载程序代码,不幸中的万幸了,基于串口的通信协议来传输文件,而lrz和lsz就是串口的z-mode协议的实现。9 _- ^# ?2 G/ x* g! X' ~- R( c7 r
    2 R( m6 [) l; M& L4 S. [
            好,既然如此,把lrz和lsz扔到openrisc-3.1\arch\openrisc\support\initramfs\bin目录下8 _) z% p2 q" A+ B: d

    6 N# |0 H2 k. m
    . @' z0 h( m4 a% v# `; K" `, Z% b! Z2 P( H
            回到ipcores上面,在opencores的网站上找到simple_gpio这个工程,下载下来。  I5 B! W4 T) f, l& o2 F

    1 e/ `' ^1 I( o4 L0 v2 r) Z
    % \& I! z: M. B" J8 o; q+ {
    / i- i* r! w0 U4 r; ^1 E+ z$ U        添加到SOC上,按照我自己做的SOC,地址设置为0x91000000
    1 `5 V. r5 {+ g5 F4 j# K; E8 m! R' e0 P8 q5 X" q  ?, ~5 g- z

    ' t; C. r, |& x/ `7 \5 v! F* C
    * ]) g: G6 P" P/ t5 k        千万千万记得,把板上的LED灯资源绑到GPIO Controller的端口上1 D5 i! P! A3 R/ T
    " W3 Y7 A3 \; r# [/ J: R
    0 z( n  `! _" j1 ^. \7 ?
    : A& G0 O" ~+ |$ b. I& f4 \3 K" J
            Tcl脚本文件
    1 ]: ~- a. p% o) C& G# B' I. x( f0 r2 w, _$ N% |
    " A; k/ M1 }5 I% g
    1 Q; y& E  ], ~8 ~. [! Z4 n
            现在打开openrisc目录下device-tree文件openrisc-3.1\arch\openrisc\boot\dts,
    + \/ @  S7 x/ Q% O1 F
    # p; R5 R4 |) Z* X
    # M( ?! s6 x3 c) S
    8 E, z) R$ F2 k# \        把最后关于simple_gpio的设备描述加上去) d; i* E3 d# T4 I6 w
    ( Z+ M4 m' l4 @* {
            然后,按照《or1200移植linux》的过程重新编译linux源码,生成uImage即可。
    ; f8 G. }6 Y1 z6 I6 l3 ]
    , W  b. |* j: z: b2 M        重新download uImage启动之后,可在\bin目录下找到lrz和lsz3 h+ c" @9 U$ W3 x( B. u

    1 P( ]( }9 x9 p5 f8 _: V   P+ K/ W. k. g1 w% D" w6 g* O4 m

    * U- T2 Z2 E2 L. _% y1 `3 j! d* x        当输出lrz的时候会弹出传输文件框
    ! a' k/ x" K6 q) b. o) v" J! I2 M
    . r0 P2 w; n9 y9 V$ F# f
    7 x/ p  h, [: V# T8 U  [. [% [3 r& C3 U* `6 q2 }
            现在可以随意找个文件测试一下板子的当前设置的baudrate下能不能无错传输。: t; q3 Q5 @. k0 `6 G& q
    ' R. [- ]2 r) o1 g
            对了,又想起一件事,上一届来公司的应届的学生,来了十几个,也就是我们这批了,有个兄弟前几天跟我说他辞职回桂林了,然后我就问题辞职你要干嘛,他就说回去跟一个小研发团队做项目创业去~估计3月底就撤了~至于什么项目这里就帮他保密一下了,但是我觉得做出来,推得早的话还蛮有市场的,这里也预祝下小朱能升级到朱总,哪天想起我就把我招过去当小弟就够了~呵呵~~~
    ' N3 |, K3 U9 K, d" V6 g# w6 d# K) A  |: G6 E- Z! v9 T- {
            好,基本步骤就此为止,现在转入到gpio字符驱动代码当中去。4 G6 g- t6 H7 B( Z2 A4 f
    ) n: n! c4 x# ~* c. T9 w
            参考宋宝华老师的《Linux设备驱动开发详解》第6章——字符设备驱动。
    - h  I( U% T9 G+ ^* V5 j0 A% P. [* ^

    8 I- d: C! V0 q0 `4 w7 z; {; Y6 {: a5 O& a) [# }+ Z( ?- n
            根据我自己的理解总结一下编写字符驱动模块的一般步骤:! w+ J1 C( P2 I) e# H( z- R
    9 m& j6 E  e& B6 {% p: |3 Y, i
            1.根据自己编程习惯选择包含cdev的自定义结构体或直接使用cdev结构体。" @8 Z3 \. ]4 a- b' T
    3 P" x" j9 g, g$ Y
            2.例化file_operations结构体,然后填充文件操作的有关成员函数,并根据自己要求编写有关成员函数操作。
    : ~1 H$ W& h3 d) M+ ?) b3 ^! b4 N- c' g9 S" A& @0 N
            3.编写模块加载函数,包括io资源申请注册,中断号申请注册,设备号申请注册,内存申请,注册字符设备,
    ( P1 }- Z( L, \
      J7 Z. [# @9 W' R        4.编写模块卸装函数,加载模块的逆操作。! f  y6 A$ M! I" U7 I7 w* S- T

    9 U$ _# _$ @2 E' {5 e        5.封装成2.6内核的驱动设备模型platform机制,包括编写platform_driver模块加载函数和卸装函数,填充platform_driver结构体的probe,remove,suspend,resume等成员,编写各成员函数。
    $ o3 P- z: A! c# ~  R
    # f/ F6 j! c( n& q        6.编写应用层的测试代码。
    - W# o1 x0 X8 f: l
    . W* d0 z9 @6 ?& u        好,到资源栏下载我自己编写的simple gpio controller的字符驱动,对照一面的一般步骤一步步看代码$ [: W+ i( D: _" d* S$ w8 u4 ~0 o
    2 E2 h, \; u$ A- ^! n! C1 n

    7 f7 R4 ^& l4 z, U% k( b
    6 V3 }* `1 v5 h& I, R' `1 q        1.cdev结构体,这里我选择编写包含cdev的自定义结构体9 M* ?' _; j' E; W4 e/ Q0 }
    0 N, `! n6 N% j3 u) y
    • struct simple_gpio{
    •     void __iomem *base;
    •     struct cdev gpio_cdev;
    • };
      ! ]; F6 N6 ~. w
    / p( w1 d( R; z/ L! W
    * |: ?/ `) M" r, {
            2.file_operations结构体
    # u& D% `/ r7 o, G2 s
    / C+ T$ o" P- Y: p        例化file_operations类,gpio controller operation
    4 b6 T) l/ x* s3 D" k+ m
    5 V# D$ H3 u$ M+ F
    • struct file_operations gpio_ctl_ops = {
    •         .owner =  THIS_MODULE,
    •         .read =  gpio_read,
    •         .write =  gpio_write,
    •         .unlocked_ioctl = gpio_ioctl,
    •         .open =  gpio_open,
    •         .release =  gpio_release,
    • };
      1 s( n& I+ d$ A! n5 l/ v

      f! j$ j' G0 [4 B+ R0 R# z2 @& d2 ?, F( q6 i" G
            编写file_operation成员函数,只实现open和ioctl函数
    1 F, `8 w; E7 {1 E# i0 z
    3 g0 `& ^" K# `. O
    • /******************************************* for file operations *******************************************/
    • int gpio_open(struct inode *inode, struct file *file){
    •         struct simple_gpio *gpio;
    •         gpio = container_of(inode->i_cdev, struct simple_gpio, gpio_cdev);
    •         file->private_data = gpio;
    •         return 0;
    • }
    • int gpio_release(struct inode *inode, struct file *file){
    •         return 0;
    • }
    • ssize_t gpio_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops){
    •         return count;
    • }
    • ssize_t gpio_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops){
    •         return count;
    • }
    • long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long data){
    •         struct simple_gpio *gpio = file->private_data;
    •         switch(cmd){
    •                 case LED_ON:
    •                         simple_gpio_write8(gpio, SIMPLE_GPIO_DAT, 0x00);
    •                         break;
    •                 case LED_OFF:
    •                         simple_gpio_write8(gpio, SIMPLE_GPIO_DAT, 0xff);
    •                         break;
    •                 default:
    •                         printk(KERN_ALERT"led control : no cmd run  [ --kernel-- ]\n");
    •                         return (-EINVAL);
    •         }
    •         return 0;
    • }0 N- E! w& y- A
         ) r: Q6 d) h' Z1 a
    7 s" \3 f5 L2 c" M) ~% X, j
            3.模块加载函数7 p) e+ [) a5 X; J( K! F

    ! Y5 U7 {' i5 V, \3 w        这里说明下,函数大部分流程我放在platform driver的probe函数中实现' y( E/ ~- Y- T" x* b, a$ E

    * u) |1 j% p# r8 k4 W1 P
    • /******************************************* for char device driver *******************************************/
    • static int __devinit simple_gpio_setup(struct simple_gpio *gpio){
    •     cdev_init(&gpio->gpio_cdev, &gpio_ctl_ops);
    •     gpio->gpio_cdev.owner = THIS_MODULE;
    •     register_chrdev_region(MKDEV(GPIO_MAJOR, GPIO_MINOR), 1, "simple_gpio");
    •     return cdev_add(&gpio->gpio_cdev, MKDEV(GPIO_MAJOR, GPIO_MINOR), 1);
    • }
    • /******************************************* for platform device driver *******************************************/
    • static int __devinit simple_gpio_probe(struct platform_device *pdev){
    •     int ret;
    •     struct simple_gpio *gpio;
    •     struct resource *io_res, *irq_res;
    •     /* get resources info*/
    •     io_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    •     if (!io_res)
    •         return -ENODEV;
    •     irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    •     if (!irq_res)
    •         return -ENODEV;
    •     /* request memery for simple_gpio */
    •     gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
    •     if (!gpio)
    •         return -ENOMEM;
    •     if (!devm_request_mem_region(&pdev->dev, io_res->start,
    •                      resource_size(io_res), pdev->name)){
    •         dev_err(&pdev->dev, "Memory region busy\n");
    •         return -EBUSY;
    •     }
    •     /* map io memery to kenel space */
    •     gpio->base = devm_ioremap_nocache(&pdev->dev, io_res->start,
    •                      resource_size(io_res));
    •     if (!gpio->base){
    •         dev_err(&pdev->dev, "Unable to map registers\n");
    •         return -EIO;
    •     }
    •     /* register simple_gpio char device */
    •     simple_gpio_setup(gpio);
    •     /* set outputs and light leds */
    •     simple_gpio_led_init(gpio);
    •     /* register interrupt */
    •     ret = devm_request_irq(&pdev->dev, irq_res->start, simple_gpio_isr, 0,
    •                    pdev->name, gpio);
    •     if(ret){
    •         dev_err(&pdev->dev, "Cannot claim IRQ\n");
    •         return ret;
    •     }
    •     /* save struct gpio as device private data */
    •     platform_set_drvdata(pdev, gpio);
    •     /* mount into sysfs */
    •     gpio_class = class_create(THIS_MODULE, "gpio_class");
    •     device_create(gpio_class, NULL, MKDEV(GPIO_MAJOR, GPIO_MINOR), NULL, "led");
    •     return 0;
    • }9 n1 r( q. Y2 a+ V7 }0 c  P7 w

    , O- |: s1 ^- ~0 B5 L. t7 K8 k& s8 \' [9 h9 x# G' \& H' x7 e/ f- ~$ A

    4 b* a) i' L) c( u# t        4.模块卸装函数
    0 a$ c! ~0 n! H# D& ?
    ) L0 a; H) Q: N& E8 X8 g8 g* D        与加载函数相同,流程放在platform driver的remove函数中实现" [; ^3 k: o9 U. d- \3 i9 k$ \, F

    ) _9 f+ b% |3 H: y. j
    • /******************************************* for char device driver *******************************************/
    • static void __devexit simple_gpio_clean(struct simple_gpio *gpio){
    •     unregister_chrdev_region(MKDEV(GPIO_MAJOR, GPIO_MINOR), 1);
    •     cdev_del(&gpio->gpio_cdev);
    • }
    • /******************************************* for platform device driver *******************************************/
    • static int __devexit simple_gpio_remove(struct platform_device* pdev){
    •     struct simple_gpio *gpio = platform_get_drvdata(pdev);
    •     /* extinguish leds */
    •     simple_gpio_led_exit(gpio);
    •     /* remove data */
    •     platform_set_drvdata(pdev, NULL);
    •     /* unregister simple_gpio char device */
    •     simple_gpio_clean(gpio);
    •     device_destroy(gpio_class, MKDEV(GPIO_MAJOR, GPIO_MINOR));
    •     class_destroy(gpio_class);
    •     return 0;
    • }
      0 |/ [6 T5 f: j# K8 d

    ) |) ^$ U* ?4 d! O
    + P1 @$ [1 ^5 N  L, ]$ u+ \        5.封装成platform机制& [0 A3 \! j1 \8 U% C6 Y
    1 D" \$ Y4 [5 ?& g) O- d* B3 ~
            对于这个步骤,基本上是一个固定的格式,个人理解就是通用的字符设备驱动套进去platform机制,至于这个机制,很多blog都有解释,这里就不详细再说明了,主要是platform_device,platform_driver,bus三者之间的关系,platform_driver有一系列的操作函数,platform_device对设备的属性描述。
    / k( i. [& j/ T6 ^5 n9 l* @
    % ]* J  Z( a$ X2 G4 W; S7 N& d
    • #define simple_gpio_suspend NULL
    • #define simple_gpio_resume  NULL
    • static struct of_device_id simple_gpio_match[] = {
    •     { .compatible = "opencores,simple_gpio", },
    •     {},
    • };
    • MODULE_DEVICE_TABLE(of, simple_gpio_match);
    • /* work with hotplug and coldplug */
    • MODULE_ALIAS("platform:simple_gpio");
    • static struct platform_driver simple_gpio_driver = {
    •     .probe = simple_gpio_probe,
    •     .remove = __devexit_p(simple_gpio_remove),
    •     .suspend = simple_gpio_suspend,
    •     .resume  = simple_gpio_resume,
    •     .driver = {
    •         .owner = THIS_MODULE,
    •         .name = "simple_gpio",
    •         .of_match_table = simple_gpio_match,
    •     },
    • };
    • static int __init simple_gpio_init(void){
    •     return platform_driver_register(&simple_gpio_driver);
    • }
    • static void __exit simple_gpio_exit(void){
    •     platform_driver_unregister(&simple_gpio_driver);
    • }9 s+ Q6 ?- T- j- b3 ?+ ?& t# v" D; k+ b

    1 S' V& S7 y, \3 T5 w) R0 z6 O# |- ~3 T/ Z% }8 N/ f

    * n4 k3 n% d8 v) [0 t: t0 C        6.测试文件' p- h7 w# ]4 c
    : H: ]' _/ k& T' k9 v7 d6 m
            在linux应用层去做文件打开、读写、关闭操作相信学C的时候就应该有深刻的理解,这里的我们在驱动上没有实现read和write函数的具体操作,只实现了ioctl的操作,所以测试文件很简单,目的是看到LED灯闪烁的现象,所以只是简单打开设备文件,执行在驱动中定义好的命令而已。/ T  R; Q, c! }+ o

    8 J; l' P4 C1 _( E1 D2 v9 D, R8 }
    • //------------------------------------- main ---------------------------------------------
    • int main(void)
    • {
    •         int fd;
    •     int ret;
    •     char *i;
    •         printf("\nstart simple_gpio_led_driver test ! \n\n");
    •         sleep(1);
    •         fd = open(DEVICE_NAME, O_RDWR);
    •     printf("fd = %d\n",fd);
    •         if (fd == -1){
    •                 printf("open device %s error !\n",DEVICE_NAME);
    •         }
    •         else{
    •         while(1){
    •             ioctl(fd,LED_OFF);
    •             printf ("leds is off ! \n");
    •             sleep(1);//sleep for 1s
    •             ioctl(fd,LED_ON);
    •             printf ("leds is on ! \n");
    •             sleep(1);
    •         }
    •             // close
    •         ret = close(fd);
    •         printf ("ret=%d\n",ret);
    •         printf ("close gpio_led_driver test\n");
    •         }
    •         return 0;
    • }
      , [, f1 c% e8 A7 Z& }
    $ {  y/ E! \% i5 ?# F

    5 y5 |+ a! X3 i* T& f        至于代码中有少量的注释,或者大家可以自己理解理解,当是自学的过程,主要还是参考宋宝华老师的书,有问题的话留言大家交流交流。
  • TA的每日心情

    2019-11-29 15:37
  • 签到天数: 1 天

    [LV.1]初来乍到

    2#
    发表于 2021-7-26 14:50 | 只看该作者
    or1200下linux简单gpio字符设备驱动

    该用户从未签到

    3#
    发表于 2021-7-26 14:50 | 只看该作者
    or1200下linux简单gpio字符设备驱动

    该用户从未签到

    4#
    发表于 2021-7-26 14:51 | 只看该作者
    or1200下linux简单gpio字符设备驱动
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    推荐内容上一条 /1 下一条

    EDA365公众号

    关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

    GMT+8, 2025-11-24 03:56 , Processed in 0.171875 second(s), 26 queries , Gzip On.

    深圳市墨知创新科技有限公司

    地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

    快速回复 返回顶部 返回列表