1,简介
1.1,概述
GPIO 是可编程的通用输入/输出(General Purpose Input/Output)外设。该组件是一个 APB 从设备。GPIO 控制外部 I/O 接口的输出数据和方向。它还可以通过内存映射寄存器读取外部接口上的数据。GPIO 支持以下功能:
- 32 位 APB 总线宽度
- 32 个独立可配置的信号
- 每个信号都有单独的数据寄存器和数据方向寄存器
- 可以为每个信号或每个信号的每个位进行软件控制
- 可配置的中断模式
1.2,结构图
模块描述:
APB 接口
APB 接口实现了 APB 从设备的操作。其数据总线宽度为 32 位。
端口 I/O 接口
与 I/O 接口之间的外部数据接口。
中断检测
与中断控制器之间的中断接口。
1.3,功能描述
1.3.1,操作
控制模式(软件)
在软件控制下,信号的数据和方向控制来自数据寄存器(GPIO_SWPORTA_DR)和方向控制寄存器(GPIO_SWPORTA_DDR)。 外部 I/O 端口的方向由对端口 a 数据方向寄存器(GPIO_SWPORTA_DDR)的写入控制。写入此内存映射寄存器的数据映射到 GPIO 外设的输出信号 GPIO_PORTA_DDR 上。此输出信号控制外部 I/O 端口的方向。
写入到端口a 数据寄存器(GPIO_SWPORTA_DR)的数据驱动 I/O 端口的输出缓冲区。外部数据在外部数据信号 GPIO_EXT_PORTA 上输入。读取外部信号寄存器(GPIO_EXT_PORTA)显示信号上的值,不受方向的影响。该寄存器是只读的,意味着不能从 APB 软件接口进行写入。
读取外部信号
GPIO_EXT_PORTA 外部信号上的数据始终可以读取。通过对内存映射寄存器 GPIO_EXT_PORTA 进行 APB 读取来读取外部 GPIO 信号上的数据。
对 GPIO_EXT_PORTA 寄存器进行 APB 读取将得到与 GPIO_EXT_PORTA 信号上相同的值。
中断
端口 A 可以被编程为在信号的任何位上接受外部信号作为中断源。中断的类型可以通过以下设置之一进行编程:
- 活动高电平和电平
- 活动低电平和电平
- 上升沿
- 下降沿
- 上升沿和下降沿都有
中断可以通过编程 GPIO_INTMASK 寄存器进行屏蔽。在屏蔽之前(称为原始状态)和屏蔽之后可以读取中断状态。
中断被合并成单个中断输出信号,其极性与单个中断相同。为了屏蔽组合中断,必须屏蔽所有单个中断。单个组合中断没有自己的屏蔽位。
每当端口 A 被配置为中断时,数据方向必须设置为输入。如果数据方向寄存器被重新编程为输出,则任何未决的中断不会丢失。但是,不会产生新的中断。
对于边沿检测中断,ISR 可以通过向对应位的 GPIO_PORTA_EOI 寄存器写入 1 来清除中断以禁用中断。此写入还会清除中断状态和原始状态寄存器。对 GPIO_PORTA_EOI 寄存器的写入不会对电平敏感中断产生影响。如果电平敏感中断导致处理器中断,则 ISR 可以轮询 GPIO_INT_RAWSTATUS 寄存器,直到中断源消失,或者在退出 ISR 之前向 GPIO_INTMASK 寄存器写入以屏蔽中断。如果 ISR 在退出之前没有屏蔽或禁用中断,则电平敏感中断将反复请求中断,直到在源处清除中断。
消抖操作
端口 A 已经配置包括消抖功能的中断特性。外部信号可以经过消抖以消除小于外部消抖时钟周期的任何杂波。
当使用消抖时钟(pclk)对输入中断信号进行消抖时,信号必须保持活动状态至少两个消抖时钟周期才能确保被注册。任何小于一个消抖时钟周期的输入脉冲宽度都会被抖动。介于一个和两个消抖时钟周期之间的脉冲宽度可能会或可能不会传播,这取决于它与消抖时钟的相位关系。如果输入脉冲跨越两个上升沿的消抖时钟,则它会被注册。如果仅跨越一个上升沿,则不会被注册。
将中断信号同步到系统时钟
中断信号在内部同步到 pclk。对于边沿检测信号,必须同步到pclk。对于电平敏感的中断,同步是可选的,并且在软件控制下(GPIO_LS_SYNC)。
1.3.2,编程
编程注意事项
- 从未使用的位置或寄存器中未使用的位始终返回零。在 APB 中没有错误机制。
- 在启用端口 A 上的中断之前,应完成对 GPIO 寄存器的编程,以实现中断功能、边沿敏感中断或电平敏感中断,以及中断极性,以防止中断线到中断控制器的杂波。
- 向中断清除寄存器写入会清除检测到的边沿中断,对电平敏感中断没有影响。
芯片中 GPIO 的层次结构
GPIO0/GPIO1/GPIO2/GPIO3/GPIO4 都位于 PD_LOGIC 子系统中。
1.4,寄存器描述
本节描述了设计中的控制/状态寄存器。软件应使用 32 位访问读取和写入这些寄存器。有 5 个 GPIO(GPIO0 ~ GPIO4),每个 GPIO 都有相同的寄存器组。因此,5 个 GPIO 的寄存器组具有 5 个不同的基地址。
1.4.1,寄存器摘要
1.4.2,详细寄存器描述
GPIO_SWPORTA_DR
地址:操作基地址 + 偏移量(0x0000)
1.5,接口描述
1.6,应用注意事项
设置 GPIO 方向的步骤
- 将 GPIO_SWPORT_DDR[x] 写为 1,将此 GPIO 设置为输出方向;将 GPIO_SWPORT_DDR[x] 写为 0,将此 GPIO 设置为输入方向。
- 默认 GPIO 方向为输入方向。
设置 GPIO 电平的步骤
- 将 GPIO_SWPORT_DDR[x] 写为 1,将此 GPIO 设置为输出方向。
- 将 GPIO_SWPORT_DR[x] 写为 v,设置此 GPIO 的值。
获取 GPIO 电平的步骤
- 将 GPIO_SWPORT_DDR[x] 写为 0,将此 GPIO 设置为输入方向。
- 从 GPIO_EXT_PORT[x] 中读取以获取 GPIO 的值。
设置 GPIO 作为中断源的步骤
- 将 GPIO_SWPORT_DDR[x] 写为 0,将此 GPIO 设置为输入方向。
- 将 GPIO_INTTYPE_LEVEL[x] 写为 v1,并将 GPIO_INT_POLARITY[x] 写为 v2 以设置中断类型。
- 将 GPIO_INTEN[x] 写为 1 以启用 GPIO 的中断。
注意:请先将 iomux 切换到 GPIO 模式!
2,RK3308 GPIO
GPIO, 全称 General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。 RK3308 有 5 组 GPIO bank:GPIO0~GPIO4,每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分。所有的 GPIO 在上电后的初始状态都是输入模式,可以通过软件设为上拉或下拉,也可以设置为中断脚,驱动强度都是可编程的。 每个 GPIO 口除了通用输入输出功能外,还可能有其它复用功能。每个 GPIO 口的驱动电流、上下拉和重置后的初始状态都不尽相同。
RK3308 以及RK系列 的 GPIO 驱动是在以下 pinctrl 文件中实现的:
kernel/drivers/pinctrl/pinctrl-rockchip.c
其核心是填充 GPIO bank 的方法和参数,并调用 gpiochip_add 注册到内核中。
i2c0: i2c@ff040000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xff040000 0x0 0x1000>;
clocks = <&cru SCLK_I2C0>, <&cru PCLK_I2C0>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
跟复用控制相关的是 pinctrl- 开头的属性:
pinctrl-names 定义了状态名称列表: default (i2c 功能) 和 gpio 两种状态。
pinctrl-0 定义了状态 0 (即 default)时需要设置的 pinctrl: &i2c0_xfer
pinctrl-1 定义了状态 1 (即 gpio)时需要设置的 pinctrl: &i2c0_gpio
RK_FUNC_1,RK_FUNC_GPIO 的定义在 kernel/include/dt-bindings/pinctrl/rk.h 中:
#define RK_FUNC_GPIO 0
#define RK_FUNC_1 1
#define RK_FUNC_2 2
#define RK_FUNC_3 3
#define RK_FUNC_4 4
#define RK_FUNC_5 5
#define RK_FUNC_6 6
#define RK_FUNC_7 7
另外,像 “1 11”,”1 12” 这样的值是有编码规则的,编码方式与上一小节 “输入输出” 描述的一样,”1 11” 代表 GPIO1_B3,”1 12” 代表 GPIO1_B4。
在复用时,如果选择了 default (即 i2c 功能),系统会应用 i2c0_xfer 这个 pinctrl,最终将针脚切换成对应的 i2c 功能;而如果选择了 gpio ,系统会应用 i2c0_gpio 这个 pinctrl,将针脚还原为 GPIO 功能。
我们看看 i2c 的驱动程序 kernel/drivers/i2c/busses/i2c-rockchip.c 是如何切换复用功能的:
static int rockchip_i2c_probe(struct platform_device *pdev)
{
struct rockchip_i2c *i2c = NULL; struct resource *res;
struct device_node *np = pdev->dev.of_node; int ret;//
...
i2c->sda_gpio = of_get_gpio(np, 0);
if (!gpio_is_valid(i2c->sda_gpio)) {
dev_err(&pdev->dev, "sda gpio is invalid\n");
return -EINVAL;
}
ret = devm_gpio_request(&pdev->dev, i2c->sda_gpio, dev_name(&i2c->adap.dev));
if (ret) {
dev_err(&pdev->dev, "failed to request sda gpio\n");
return ret;
}
i2c->scl_gpio = of_get_gpio(np, 1);
if (!gpio_is_valid(i2c->scl_gpio)) {
dev_err(&pdev->dev, "scl gpio is invalid\n");
return -EINVAL;
}
ret = devm_gpio_request(&pdev->dev, i2c->scl_gpio, dev_name(&i2c->adap.dev));
if (ret) {
dev_err(&pdev->dev, "failed to request scl gpio\n");
return ret;
}
i2c->gpio_state = pinctrl_lookup_state(i2c->dev->pins->p, "gpio");
if (IS_ERR(i2c->gpio_state)) {
dev_err(&pdev->dev, "no gpio pinctrl state\n");
return PTR_ERR(i2c->gpio_state);
}
pinctrl_select_state(i2c->dev->pins->p, i2c->gpio_state);
gpio_direction_input(i2c->sda_gpio);
gpio_direction_input(i2c->scl_gpio);
pinctrl_select_state(i2c->dev->pins->p, i2c->dev->pins->default_state);
...
}
下面是常用的复用 API 定义:
#include <linux/pinctrl/consumer.h>
struct device {
//...
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
//...
};
struct dev_pin_info {
struct pinctrl *p;
struct pinctrl_state *default_state;
#ifdef CONFIG_PM
struct pinctrl_state *sleep_state;
struct pinctrl_state *idle_state;
#endif
};
struct pinctrl_state * pinctrl_lookup_state(struct pinctrl *p, const char *name);
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);
3,GPIO 计算方式
RK3308 gpio
例如想要用到GPIO1_A0(gpio的第2pin),那么这个引脚对应系统中的gpio num便是32,GPIO1_A5对应num是37,GPIO1_B5对应的是45。
这里拿GPIO1_A0和GPIO1_B5举例:
GPIO1_A0 num = 1×32 + 0× 8 + 0 = 32
GPIO1_B5 num = 1×32 + 1×8 + 5 = 45
GPIO1 那就对应 1 × 32 ,GPIO2对应2 × 32
A0或者B5,英文字母和阿拉伯数字拆开来看:A为0,B为1,C为2, D为3;
阿拉伯数字就代表最后加的数字
例如:GPIO3_A5, 需控制 gpio101 (32*3+0*8+5 = 101)
gpio 输出
# echo 101 > /sys/class/gpio/export #export the pin # echo out > /sys/class/gpio/gpio101/direction #set direction, out or in
开启关闭
# echo 1 > /sys/class/gpio/gpio101/value #on # echo 0 > /sys/class/gpio/gpio101/value #off
4,GPIO 调试接口
Debugfs 文件系统目的是为开发人员提供更多内核数据,方便调试。 这里 GPIO 的调试也可以用 Debugfs 文件系统,获得更多的内核信息。GPIO 在 Debugfs 文件系统中的接口为 /sys/kernel/debug/gpio
,可以这样读取该接口的信息:
root@xxxxxx:/home/rk3308 # cat /sys/kernel/debug/gpio
GPIOs 0-31, platform/pinctrl, gpio0:
gpio-1 ( |? ) out hi
gpio-2 ( |reset ) out lo
gpio-5 ( |spk-ctl ) out lo
gpio-21 ( |vbus_host ) out hi
GPIOs 32-63, platform/pinctrl, gpio1:
GPIOs 64-95, platform/pinctrl, gpio2:
GPIOs 96-127, platform/pinctrl, gpio3:
GPIOs 128-159, platform/pinctrl, gpio4:
gpio-135 ( |bt_default_rts ) in lo
gpio-139 ( |bt_default_poweron ) out lo
gpio-140 ( |bt_default_wake_host) in lo
gpio-158 ( |vcc_sd ) out hi
GPIOs 511-511, platform/rk8xx-gpio, rk8xx-gpio, can sleep:
root@xxxxxx:/home/rk3308 #
从读取到的信息中可以知道,内核把 GPIO 当前的状态都列出来了。
5,GPIO 控制
5.1,采用sysfs文件系统的方式控制GPIO
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#define SYSFS_GPIO_DIR "/sys/class/gpio"
#define POLL_TIMEOUT (3 * 1000) /* 3 seconds */
#define MAX_BUF 64
int gpio_export(unsigned int gpio)
{
int fd, len;
char buf[MAX_BUF];
fd = open(SYSFS_GPIO_DIR "/export", O_WRONLY);
if (fd < ) {
perror("gpio/export");
return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, len);
close(fd);
return ;
}
int gpio_unexport(unsigned int gpio)
{
int fd, len;
char buf[MAX_BUF];
fd = open(SYSFS_GPIO_DIR "/unexport", O_WRONLY);
if (fd < ) {
perror("gpio/export");
return fd;
}
len = snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, len);
close(fd);
return ;
}
int gpio_set_dir(unsigned int gpio, unsigned int out_flag)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/direction", gpio);
fd = open(buf, O_WRONLY);
if (fd < ) {
perror("gpio/direction");
return fd;
}
if (out_flag)
write(fd, "out", );
else
write(fd, "in", );
close(fd);
return ;
}
int gpio_set_value(unsigned int gpio, unsigned int value)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
fd = open(buf, O_WRONLY);
if (fd < ) {
perror("gpio/set-value");
return fd;
}
if (value)
write(fd, "1", );
else
write(fd, "0", );
close(fd);
return ;
}
int gpio_get_value(unsigned int gpio, unsigned int *value)
{
int fd, len;
char buf[MAX_BUF];
char ch;
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
fd = open(buf, O_RDONLY);
if (fd < ) {
perror("gpio/get-value");
return fd;
}
read(fd, &ch, );
if (ch != '0') {
*value = ;
} else {
*value = ;
}
close(fd);
return ;
}
int gpio_set_edge(unsigned int gpio, char *edge)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/edge", gpio);
fd = open(buf, O_WRONLY);
if (fd < ) {
perror("gpio/set-edge");
return fd;
}
write(fd, edge, strlen(edge) + );
close(fd);
return ;
}
int gpio_fd_open(unsigned int gpio)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
fd = open(buf, O_RDONLY | O_NONBLOCK );
if (fd < ) {
perror("gpio/fd_open");
}
return fd;
}
int gpio_fd_close(int fd)
{
return close(fd);
}
int main(int argc, char **argv, char **envp)
{
struct pollfd fdset[2];
int nfds = ;
int gpio_fd, timeout, rc;
char *buf[MAX_BUF];
unsigned int gpio;
int len;
if (argc < ) {
printf("Usage: gpio-int <gpio-pin>\n\n");
printf("Waits for a change in the GPIO pin voltage level or input on stdin\n");
exit(-1);
}
gpio = atoi(argv[]);
gpio_export(gpio);
gpio_set_dir(gpio, );
gpio_set_edge(gpio, "rising");
gpio_fd = gpio_fd_open(gpio);
timeout = POLL_TIMEOUT;
while () {
memset((void*)fdset, , sizeof(fdset));
fdset[].fd = STDIN_FILENO;
fdset[].events = POLLIN;
fdset[].fd = gpio_fd;
fdset[].events = POLLPRI;
rc = poll(fdset, nfds, timeout);
if (rc < ) {
printf("\npoll() failed!\n");
return -1;
}
if (rc == ) {
printf(".");
}
if (fdset[].revents & POLLPRI) {
len = read(fdset[].fd, buf, MAX_BUF);
printf("\npoll() GPIO %d interrupt occurred\n", gpio);
}
if (fdset[].revents & POLLIN) {
(void)read(fdset[].fd, buf, );
printf("\npoll() stdin read 0x%2.2X\n", (unsigned int) buf[]);
}
fflush(stdout);
}
gpio_fd_close(gpio_fd);
return ;
}
命令控制GPIO(实际还是sysfs)
1)导出节点
//下面的动作前提是要先导出对应的gpio,动态生成了对应的节点才能下一步操作
echo 1 > /sys/class/gpio/export
2) 设置输入方向,读取对应的值
echo in > /sys/class/gpio/gpio1/direction
cat /sys/class/gpio/gpio1/value
3)设置输出方向,设置对应的值
echo out > /sys/class/gpio/gpio1/direction
//拉高
echo 1 > /sys/class/gpio/gpio1/value
//拉低
echo 1 > /sys/class/gpio/gpio1/value
4) 设置中断触发方式
//设置的中断触发方式 none rising falling both,
echo none > /sys/class/gpio/gpio1/edge
// 要结合poll机制来获取对应gpio口的中断
5)销毁对应GPIO节点
echo 1 > /sys/class/gpio/unexport
5.2,GPIO 驱动控制
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/seq_file.h>
#include <linux/delay.h>
#include <linux/ioctl.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/gpio.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#define DEVICE_NAME "gpiodrv"
#define GPIO_MAJOR 0
#define IOCTL_MAGIC 'g'
#define GPIO_OUT_LOW _IOW(IOCTL_MAGIC, 0x00, unsigned long)
#define GPIO_OUT_HIG _IOW(IOCTL_MAGIC, 0x01, unsigned long)
#define GPIO_INPUT _IOR(IOCTL_MAGIC, 0x02, unsigned long)
static struct cdev cdev;
static struct class *gpio_class;
static dev_t devno;
static int gpio_open(struct inode *inode, struct file *filp)
{
int ret = ;
filp->private_data = &cdev;
return ret;
}
static int gpio_release(struct inode *inode, struct file *filp)
{
return ;
}
static ssize_t gpio_read(struct file *filp, char __user *buff,
size_t count, loff_t *offp)
{
return ;
}
static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned int ret = ,err = ;
if (_IOC_TYPE(cmd) != IOCTL_MAGIC)
return -EINVAL;
if (arg > )
return -EINVAL;
err = gpio_request(arg,NULL);
if(err)
{
//printk("gpio_ioctl request err!\n");
}
switch(cmd) {
case GPIO_OUT_LOW:
gpio_direction_output(arg,);
break;
case GPIO_OUT_HIG:
gpio_direction_output(arg,);
break;
case GPIO_INPUT:
gpio_direction_input(arg);
ret = gpio_get_value(arg);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static struct file_operations gpio_fops = {
.owner = THIS_MODULE,
.open = gpio_open,
.release = gpio_release,
.read = gpio_read,
.unlocked_ioctl = gpio_ioctl,
};
static int gpio_setup(struct cdev *cdevp, dev_t dev)
{
int ret = ;
cdev_init(cdevp, &gpio_fops);
cdevp->owner = THIS_MODULE;
cdevp->ops = &gpio_fops;
ret = cdev_add(cdevp, dev, );
if (ret)
printk(KERN_ALERT"add gpio setup failed!\n");
return ret;
}
static int __init gpio_init(void)
{
struct device *dev;
int ret;
unsigned int gpio_major;
printk("init gpio driver module...\n");
//1.申请主次设备号
devno = MKDEV(GPIO_MAJOR, );
gpio_major = MAJOR(devno);
if (gpio_major)
ret = register_chrdev_region(devno, , DEVICE_NAME);
else
ret = alloc_chrdev_region(&devno, , , DEVICE_NAME);
if (ret < ) {
printk(KERN_ALERT"failed in registering dev.\n");
return ret;
}
//2.加入字符设备结构体
ret = gpio_setup(&cdev, devno);
if (ret < ) {
printk(KERN_ALERT"failed in setup dev.\n");
return ret;
}
//3.在class目录中创建文件
gpio_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(gpio_class)) {
printk(KERN_ALERT"failed in creating class.\n");
return -1;
}
//4.生成设备节点
dev = device_create(gpio_class, NULL, devno, NULL, DEVICE_NAME "%d", );
if (IS_ERR(dev)) {
printk(KERN_ALERT"failed in creating class.\n");
return -1;
}
return ret;
}
static void __exit gpio_exit(void)
{
cdev_del(&cdev);
unregister_chrdev_region(devno, );
device_destroy(gpio_class, devno);
class_destroy(gpio_class);
}
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XXX");
MODULE_DESCRIPTION("GPIO driver for test");
5.3,寄存器控制GPIO(此处使用SM6350为例)
首先确认GPIO对应的寄存器解释;
然后就是使用devmem命令来对寄存器进行操作(亦可使用devmem2)。
注意要使用devmem必须打开内核宏CONFIG_DEVMEM。
ubuntu 安装(亦可下载源码编译):
sudo apt-get install devmem2
devmem2使用方法:
devmem2 { address } [ type [ data ] ]
address : 物理地址
type :要访问的数据类型 : [b]yte, [h]alfword, [w]ord
data :想要写入的数据,若为读取操作则省略此参数,若为写入,则必须含有此参数。
注:下面都是以sm6350为例
1)打开CONFIG_DEVMEM
CONFIG_DEVMEM=y
控制GPIO1为输出高
devmem 0xf101000 4 0x200
devmem 0xf101004 4 0x2
3)控制GPIO1 为输出低
devmem 0xf101000 4 0x200
devmem 0xf101004 4 0x0
4)控制GPIO1 为输出上拉和输出下拉
//上拉
devmem 0xf101000 4 0x203
//下拉
devmem 0xf101004 4 0x201
5)控制GPIO1 为输入,同时读取其值
devmem 0xf101000 4 0x0
devmem 0xf101004 //读取对应的值,第一位为1 就是高,为0就是低