ZedBoard Linux开发 --- GPIO驱动详解

时间:2023-02-04 17:53:10
本来这是要作为ZedBoard Linux的第一个学习实例,不过由于一开始实在找不到内核中针对ZedBoard GPIO具体操作的代码在哪里,所以只能先从OLED开始看起,在学习完OLED驱动之后有了不少发现,比如OLED驱动中就有使用GPIO的操作,后来发现这些操作都被Linux内核中的GPIOLIB库管理着,相关的文档在Documentation/gpio.txt中有介绍,通读一遍之后就会有不少发现的,相关的GPIOLIB库文件位于drivers/gpio/gpio-lib.c文件中,不过这部分文件只是提供了库函数,而真正在ZedBoard启动时进行GPIO注册管理的文件是drivers/gpio/gpio-xilinxps.c,可以在这个文件中找到这样一个宏定义:
 
 

#define XGPIOPS_NR_GPIOS 118

这里一共注册了118个GPIO口,看看Datasheet就知道这里的意思应该是MIO[0:53]+EMIO[54:117],也就是54个MIO加上64个EMIO,看到这里我还是有一些疑问,因为并不是所有的IO口都作为GPIO来使用的,有很大一部分是进行IO复用的,下面是我在XPS中的MIO配置截图:
ZedBoard Linux开发 --- GPIO驱动详解
 可以看到MIO中真正作为GPIO口使用的也就只有MIO[0,7,9:15,50:51],我当时就有疑问:如果我在Linux中申请了这一部分被复用的GPIO,这会不会与正在复用的那些功能起冲突?(至少在MCU中有很多复用功能是在配置了GPIO方向之后才能正常复用的)后来看来一下zynq的UG585手册,找到了下面这张图才解决了问题:
ZedBoard Linux开发 --- GPIO驱动详解
可以看到所有GPIO与其他复用的功能最后都是经过MIO网络路由到外部的GPIO端口的,也就是说即使在相应的GPIO寄存器中配置了GPIO的功能,那么这部分功能也不会生效!而配置这些复用功能的寄存器是在slcr(System Level Control Registers)寄存器中操作的,可以在UG585上找到这些寄存器具体的参数:
ZedBoard Linux开发 --- GPIO驱动详解
而在Digilent Linux内核中,slcr相关的文件可以在linux-digilent/arch/arm/mach-zynq/slcr.c中找到。另外除了MIO,还有
EMIO的配置,可以在上面的截图中看到XPS中配置了60个EMIO,并且在xps的ucf文件中可以找到配置相关注释:
 
   

#############################################################
#                                                           #
# GPIO Interface                                            #
#                                                           #
#############################################################
………………………………
############################
#                          #
# On-board OLED            #
#                          #
# Voltage control and      #
# Bitbanged SPI over GPIO  #
#                          #
############################
net processing_system7_0_GPIO<1> LOC = U11  | IOSTANDARD = LVCMOS33; # OLED-VBAT
net processing_system7_0_GPIO<2> LOC = U12  | IOSTANDARD = LVCMOS33; # OLED-VDD
net processing_system7_0_GPIO<3> LOC = U9   | IOSTANDARD = LVCMOS33; # OLED-RES
net processing_system7_0_GPIO<4> LOC = U10  | IOSTANDARD = LVCMOS33; # OLED-DC
net processing_system7_0_GPIO<5> LOC = AB12 | IOSTANDARD = LVCMOS33; # OLED-SCLK
net processing_system7_0_GPIO<6> LOC = AA12 | IOSTANDARD = LVCMOS33; # OLED-SDIN
############################
#                          #
# On-board LED's           #
#                          #
############################
net processing_system7_0_GPIO<7>  LOC = T22 | IOSTANDARD = LVCMOS33; # LD0
net processing_system7_0_GPIO<8>  LOC = T21 | IOSTANDARD = LVCMOS33; # LD1
net processing_system7_0_GPIO<9>  LOC = U22 | IOSTANDARD = LVCMOS33; # LD2
net processing_system7_0_GPIO<10> LOC = U21 | IOSTANDARD = LVCMOS33; # LD3
net processing_system7_0_GPIO<11> LOC = V22 | IOSTANDARD = LVCMOS33; # LD4
net processing_system7_0_GPIO<12> LOC = W22 | IOSTANDARD = LVCMOS33; # LD5
net processing_system7_0_GPIO<13> LOC = U19 | IOSTANDARD = LVCMOS33; # LD6
net processing_system7_0_GPIO<14> LOC = U14 | IOSTANDARD = LVCMOS33; # LD7
############################
#                          #
# On-board Slide Switches  #
#                          #
############################
net processing_system7_0_GPIO<15> LOC = F22 | IOSTANDARD = LVCMOS33; # SW0
net processing_system7_0_GPIO<16> LOC = G22 | IOSTANDARD = LVCMOS33; # SW1
net processing_system7_0_GPIO<17> LOC = H22 | IOSTANDARD = LVCMOS33; # SW2
net processing_system7_0_GPIO<18> LOC = F21 | IOSTANDARD = LVCMOS33; # SW3
net processing_system7_0_GPIO<19> LOC = H19 | IOSTANDARD = LVCMOS33; # SW4
net processing_system7_0_GPIO<20> LOC = H18 | IOSTANDARD = LVCMOS33; # SW5
net processing_system7_0_GPIO<21> LOC = H17 | IOSTANDARD = LVCMOS33; # SW6
net processing_system7_0_GPIO<22> LOC = M15 | IOSTANDARD = LVCMOS33; # SW7

这里的processing_system7_0_GPIO指的的就是EMIO,所以可以看到EMIO[1:6]是用的OLED,而在GPIO寄存器配置中:
GPIO Bank0, MIO[0:31]
GPIO Bank1, MIO[32:53]
GPIO Bank2, EMIO[0:31]
GPIO Bank3, EMIO[32:63]
是顺序排列的,所以上面这里的EMIO[1:6]对应的就是GPIO[55:60],然后我们可以在devicetree源文件中找到oled的配置:
 
 

zed_oled {
compatible = "dglnt,pmodoled-gpio" ;
/* GPIO Pins */
vbat - gpio = <& gpiops 55 0 >;
vdd - gpio = <& gpiops 56 0 >;
res - gpio = <& gpiops 57 0 >;
dc - gpio = <& gpiops 58 0 >;
/* SPI-GPIOs */
spi - bus - num = < 2 >;
spi - speed - hz = < 4000000 >;
spi - sclk - gpio = <& gpiops 59 0 >;
spi - sdin - gpio = <& gpiops 60 0 >;
};

这里面的GPIO的号码正好对应了刚才计算出来的数字,到这里也就能理解这些号码的意义了:-)(这里需要事先设置好GPIO控制器,可以在前面找到gpio-controller字段)。
但是看到这里不禁又有了另一个疑问,为什么设备树只有oled的配置,而没有led和switch的配置,这也是当初我找不到内核在哪里操作led的原因。有一次无意间在ZedBoard_Linux_Design的doc目录下的DemoFeatures.txt中发现内核中自带了这样两个命令:
 
 

 SWITCHES / LEDS : Scripts are included for writing to the LEDs and reading
 the state of the switches . To read the state of the switches , run the 
 command :
 
   read_sw
 
  It will return the state of the switches as both hexadecimal and decimal .
 A script for changing the state of the LEDs is also included . To turn all
  8 LEDs on , run one of the following two commands :
 
   write_led 255
   write_led 0xFF

然后再仔细一看ramdisk中read_sw,write_led里面的内容:
 
 

thinki@G31T - M2 : $ cat write_led 
#!/bin/sh
value=$(($1));
if [ $value - ge 0 ]; then
        for i in 0 1 2 3 4 5 6 7 ;
        do
                led = $ (( $i + 61 ));
                echo $ (( $value & 0x01 )) > /sys/ class / gpio / gpio$led / value ;
                value = $ (( $value / 2 ));
        done ;
fi ;


 
 

thinki@G31T - M2 : $ cat read_sw 
#!/bin/sh
value=0;
for i in 0 1 2 3 4 5 6 7;
do
        sw=$((76-$i));
        sw_tmp=`cat /sys/class/gpio/gpio$sw/value`;
        value=$(($value*2));
        value=$(($value+$sw_tmp));
done;
printf "0x%x %d\n" $value $value;

可以看到这里直接操作了sysfs下面的gpio class进行LED的写和Switch的读,但是这些目录以及相关的文件并不是凭空而来的,最后在ramdisk的etc/init.d/rcS中可以找到这样一段脚本:
 
 

echo "++ Exporting LEDs & SWs"
for i in 0 1 2 3 4 5 6 7 ;  
do
        sw = $ (( $i + 69 ));
        led = $ (( $i + 61 ));
        echo $sw > /sys/ class / gpio / export ;
        echo $led > /sys/ class / gpio / export ;
        echo out > /sys/ class / gpio / gpio$led / direction ;
done ;

这才是这些目录真正的开端,启动时配置了这些,你只要Google一下sysfs gpio就可以找到一大堆东西,这里我简要介绍一下,也就是linux下gpio支持sysfs空间的操作,也就是可以绕过创建设备节点进行此操作,不过首先需要开启内核对sysfs的支持以及gpiolib对sysfs的支持,相关的代码依旧在drivers/gpio/gpiolib.c文件中,我们可以找到相关的宏:
 
 

#ifdef CONFIG_GPIO_SYSFS

这个宏下面的内容都是针对SYSFS相关的支持,这里涉及的知识比较多,需要理解Linux 2.6的设备模型相关的知识。如果是直观的操作的话,你只需要在/sys/class/gpio下进行echo XXX > export操作,不过这里XXX必须是内核支持的gpio号,比如内核启动之后在rcS脚本中echo的就是[61:68]和[69:76],这样就会在/sys/class/gpio目录下创建gpioXXX目录,然后我们可以设置led对应的gpioXXX目录下的direction属性为out就能够设置GPIO为输出,最后只需要像write_led脚本中那样向/sys/class/gpio/gpioXXX/value echo 0或者1就可以了,最终你会看到LED灯点亮!
当然你也可以使用类似于pmodoled驱动的形式,最终以/dev目录下的设备节点来操作底层硬件,这样的话就需要注册platform驱动并且通过设备树中的节点进行匹配,可以在设备树源文件最后添加下面的配置信息:
 
  

emio - oled {
compatible = "dglnt,emioled-gpio" ;
/* GPIO Pins */
ld0 - gpio = <& gpiops 61 0 >;
ld1 - gpio = <& gpiops 62 0 >;
ld2 - gpio = <& gpiops 63 0 >;
ld3 - gpio = <& gpiops 64 0 >;
ld4 - gpio = <& gpiops 65 0 >;
ld5 - gpio = <& gpiops 66 0 >;
ld6 - gpio = <& gpiops 67 0 >;
ld7 - gpio = <& gpiops 68 0 >;
};

有时间我把我写的代码贴出来晒晒!
在platform驱动中的probe函数中来进行cdev的初始化与file_operations的设置,具体可以参考pmodoled驱动中的代码。
同时gpiolib还支持debugfs,可以查看哪些GPIO口被哪些设备分配了,不过使用之前需要先挂载debugfs,它与sysfs一样也是基于内存的文件系统:
 
 

mount - t debugfs debugfs / sys / kernel / debug  

然后在板子上输出gpio文件的信息:
 
   

zynq > mount - t debugfs debugfs / sys / kernel / debug                                               
zynq > cat / sys / kernel / debug / gpio                                                               
GPIOs 0 - 117 , platform / e000a000 . gpio , xgpiops :                                                  
zynq > mount - t debugfs debugfs / sys / kernel / debug                                               
zynq > cat / sys / kernel / debug / gpio                                                               
GPIOs 0 - 117 , platform / e000a000 . gpio , xgpiops :                                                  
 gpio - 7   ( mmc_led             ) out lo                                                        
 gpio - 55   ( OLED VBat           ) out lo                                                        
 gpio - 56   ( OLED VDD             ) out lo                                                        
 gpio - 57   ( OLED_RESET           ) out hi                                                        
 gpio - 58   ( OLED_D / C             ) out hi                                                        
 gpio - 59   ( spi_gpio . 2           ) out lo                                                        
 gpio - 60   ( spi_gpio . 2           ) out lo                                                        
 gpio - 61   ( sysfs               ) out lo                                                        
 gpio - 62   ( sysfs               ) out lo                                                        
 gpio - 63   ( sysfs               ) out lo                                                        
 gpio - 64   ( sysfs               ) out lo                                                        
 gpio - 65   ( sysfs               ) out lo                                                        
 gpio - 66   ( sysfs               ) out lo                                                        
 gpio - 67   ( sysfs               ) out lo                                                        
 gpio - 68   ( sysfs               ) out lo                                                        
 gpio - 69   ( sysfs               ) in  hi                                                        
 gpio - 70   ( sysfs               ) in  lo                                                        
 gpio - 71   ( sysfs               ) in  lo                                                        
 gpio - 72   ( sysfs               ) in  hi                                                        
 gpio - 73   ( sysfs               ) in  hi                                                        
 gpio - 74   ( sysfs               ) in  lo                                                        
 gpio - 75   ( sysfs               ) in  hi                                                        
 gpio - 76   ( sysfs               ) in  lo     

可以看到其中GPIO[55:60]被pmodoled驱动分配了,而GPIO[61:76]则是被sysfs分配了,最前面的GPIO[7]则是被linux的led驱动分配了,代码的位置是在drivers/leds/leds-gpio.c。
最终要深入的话还是建议看内核源码和文档:

drivers/gpio/gpiolib.c
Documentation/gpio.txt

参考链接: