admin管理员组

文章数量:1532160

2023年12月17日发(作者:)

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

下载LOFTER客户端

本来这是要作为ZedBoard Linux的第一个学习实例,不过由于一开始实在找不到内核中针对ZedBoard

GPIO具体操作的代码在哪里,所以只能先从OLED开始看起,在学习完OLED驱动之后有了不少发现,比如OLED驱动中就有使用GPIO的操作,后来发现这些操作都被Linux内核中的GPIOLIB库管理着,相关的文档在Documentation/中有介绍,通读一遍之后就会有不少发现的,相关的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配置截图:

1

可以看到MIO中真正作为GPIO口使用的也就只有MIO[0,7,9:15,50:51],我当时就有疑问:如果我在Linux中申请了这一部分被复用的GPIO,这会不会与正在复用的那些功能起冲突?(至少在MCU中有很多复用功能是在配置了GPIO方向之后才能正常复用的)后来看来一下zynq的UG585手册,找到了下面这张图才解决了问题:

2

可以看到所有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文件中可以找到配置相关注释:

#############################################################

# #

# GPIO Interface #

# #

#############################################################

………………………………

3

############################

# #

# 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

4

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";

5

};

/* 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目录下的中发现内核中自带了这样两个命令:

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;

6

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 %dn" $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文件中,我们可以找到相关的宏:

7

#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一样也是基于内存的文件系统:

8

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/, xgpiops:

zynq> mount -t debugfs debugfs /sys/kernel/debug

zynq> cat /sys/kernel/debug/gpio

GPIOs 0-117, platform/, 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。

9

最终要深入的话还是建议看内核源码和文档:

drivers/gpio/gpiolib.c

Documentation/

参考链接:

使用 /sys 文件系统访问 Linux 内核

linux那些事之sysfs

在 Linux 下用户空间与内核空间数据交换的方式

10

本文标签: 内核配置驱动操作设备