ZedBoard Linux开发 --- GPIO驱动详解
本来这是要作为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,可以在这个文件中找到这样一个宏定义:
可以看到MIO中真正作为GPIO口使用的也就只有MIO[0,7,9:15,50:51],我当时就有疑问:如果我在Linux中申请了这一部分被复用的GPIO,这会不会与正在复用的那些功能起冲突?(至少在MCU中有很多复用功能是在配置了GPIO方向之后才能正常复用的)后来看来一下zynq的UG585手册,找到了下面这张图才解决了问题:
可以看到所有GPIO与其他复用的功能最后都是经过MIO网络路由到外部的GPIO端口的,也就是说即使在相应的GPIO寄存器中配置了GPIO的功能,那么这部分功能也不会生效!而配置这些复用功能的寄存器是在slcr(System Level Control Registers)寄存器中操作的,可以在UG585上找到这些寄存器具体的参数:
而在Digilent Linux内核中,slcr相关的文件可以在linux-digilent/arch/arm/mach-zynq/slcr.c中找到。另外除了MIO,还有
EMIO的配置,可以在上面的截图中看到XPS中配置了60个EMIO,并且在xps的ucf文件中可以找到配置相关注释:
#define XGPIOPS_NR_GPIOS 118
这里一共注册了118个GPIO口,看看Datasheet就知道这里的意思应该是MIO[0:53]+EMIO[54:117],也就是54个MIO加上64个EMIO,看到这里我还是有一些疑问,因为并不是所有的IO口都作为GPIO来使用的,有很大一部分是进行IO复用的,下面是我在XPS中的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-VBATnet processing_system7_0_GPIO<2> LOC = U12 | IOSTANDARD = LVCMOS33; # OLED-VDDnet processing_system7_0_GPIO<3> LOC = U9 | IOSTANDARD = LVCMOS33; # OLED-RESnet processing_system7_0_GPIO<4> LOC = U10 | IOSTANDARD = LVCMOS33; # OLED-DCnet processing_system7_0_GPIO<5> LOC = AB12 | IOSTANDARD = LVCMOS33; # OLED-SCLKnet 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; # LD0net processing_system7_0_GPIO<8> LOC = T21 | IOSTANDARD = LVCMOS33; # LD1net processing_system7_0_GPIO<9> LOC = U22 | IOSTANDARD = LVCMOS33; # LD2net processing_system7_0_GPIO<10> LOC = U21 | IOSTANDARD = LVCMOS33; # LD3net processing_system7_0_GPIO<11> LOC = V22 | IOSTANDARD = LVCMOS33; # LD4net processing_system7_0_GPIO<12> LOC = W22 | IOSTANDARD = LVCMOS33; # LD5net processing_system7_0_GPIO<13> LOC = U19 | IOSTANDARD = LVCMOS33; # LD6net processing_system7_0_GPIO<14> LOC = U14 | IOSTANDARD = LVCMOS33; # LD7############################# ## On-board Slide Switches ## #############################net processing_system7_0_GPIO<15> LOC = F22 | IOSTANDARD = LVCMOS33; # SW0net processing_system7_0_GPIO<16> LOC = G22 | IOSTANDARD = LVCMOS33; # SW1net processing_system7_0_GPIO<17> LOC = H22 | IOSTANDARD = LVCMOS33; # SW2net processing_system7_0_GPIO<18> LOC = F21 | IOSTANDARD = LVCMOS33; # SW3net processing_system7_0_GPIO<19> LOC = H19 | IOSTANDARD = LVCMOS33; # SW4net processing_system7_0_GPIO<20> LOC = H18 | IOSTANDARD = LVCMOS33; # SW5net processing_system7_0_GPIO<21> LOC = H17 | IOSTANDARD = LVCMOS33; # SW6net 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 readingthe state of the switches . To read the state of the switches , run thecommand :read_swIt 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 all8 LEDs on , run one of the following two commands :write_led 255write_led 0xFF
然后再仔细一看ramdisk中read_sw,write_led里面的内容:
thinki@G31T - M2 : $ cat write_led#!/bin/shvalue=$(($1));if [ $value - ge 0 ]; thenfor i in 0 1 2 3 4 5 6 7 ;doled = $ (( $i + 61 ));echo $ (( $value & 0x01 )) > /sys/ class / gpio / gpio$led / value ;value = $ (( $value / 2 ));done ;fi ;
thinki@G31T - M2 : $ cat read_sw#!/bin/shvalue=0;for i in 0 1 2 3 4 5 6 7;dosw=$((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 ;dosw = $ (( $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 / debugzynq > cat / sys / kernel / debug / gpioGPIOs 0 - 117 , platform / e000a000 . gpio , xgpiops :zynq > mount - t debugfs debugfs / sys / kernel / debugzynq > cat / sys / kernel / debug / gpioGPIOs 0 - 117 , platform / e000a000 . gpio , xgpiops :gpio - 7 ( mmc_led ) out logpio - 55 (OLED VBat ) out logpio - 56 (OLED VDD ) out logpio - 57 (OLED_RESET ) out higpio - 58 (OLED_D / C ) out higpio - 59 (spi_gpio . 2 ) out logpio - 60 (spi_gpio . 2 ) out logpio - 61 (sysfs ) out logpio - 62 (sysfs ) out logpio - 63 (sysfs ) out logpio - 64 (sysfs ) out logpio - 65 (sysfs ) out logpio - 66 (sysfs ) out logpio - 67 (sysfs ) out logpio - 68 (sysfs ) out logpio - 69 (sysfs ) in higpio - 70 (sysfs ) in logpio - 71 (sysfs ) in logpio - 72 (sysfs ) in higpio - 73 (sysfs ) in higpio - 74 (sysfs ) in logpio - 75 (sysfs ) in higpio - 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
参考链接: