GPIO(通用目的输入/输出端口)是一种灵活的软件控制的数字信号。大多数的嵌入式
处理器都引出一组或多组的 GPIO,并且部分普通管脚通过配置可以复用为 GPIO。利用可
编程逻辑器件,或总线(如 I 2 C、SPI)转 GPIO 芯片,也可以扩展系统的 GPIO。不管是何
种 GPIO,GPIOLIB 为内核和用户层都提供了标准的操作方法。
GPIOLIB 的接口十分简洁。在 GPIOLIB,所有的 GPIO 都是用整形的 GPIO 编号标识。
只要获得要操作 GPIO 的编号,就可以调用 GPIOLIB 提供的方法操作 GPIO。
GPIOLIB 的内核接口是指:若某些 GPIO 在 GPIOLIB 框架下被驱动后,GPIOLIB 为内
核的其它代码操作该 GPIO 而提供的标准接口。
GPIO 在使用前,必须先调用 gpio_request()函数申请 GPIO:
int gpio_request(unsigned gpio, const char *label);
该函数的 gpio 参数为 GPIO 编号;label 参数为 GPIO 的标识字符串,可以随意设定。
若该函数调用成功,将返回 0 值;否则返回非 0 值。gpio_request()函数调用失败的原因可能
为 GPIO 的编号不存在,或在其它地方已经申请了该 GPIO 编号而还没有释放。
当 GPIO 使用完成后,应当调用 gpio_free()函数释放 GPIO:
void gpio_free(unsigned gpio);
在操作 GPIO 输出信号前,需要调用 gpio_direction_output()函数把 GPIO 设置为输出方
向:
int gpio_direction_output(unsigned gpio, int value);
把 GPIO 设置为输出方向后,参数 value 为默认的输出电平:1 为高电平;0 为低电平。
GPIO 被设置为输出方向后,就可以调用 gpio_set_value()函数控制 GPIO 输出高电平或
低电平:
void gpio_set_value(unsigned gpio, int value);
该函数的 value 参数可取值为:1 为高电平;0 为低电平。
当需要从 GPIO 读取输入电平状态前,需要调用 gpio_direction_input()函数设置 GPIO 为
输入方向:
int gpio_direction_input(unsigned gpio);
在 GPIO 被设置为输入方向后,就可以调用 gpio_get_value()函数读取 GPIO 的输入电平
状态:
int gpio_get_value(unsigned gpio);
该函数的返回值为 GPIO 的输入电平状态:1 为高电平;0 为低电平。
大多数的嵌入式处理器的 GPIO 引脚在被设置为输入方向后,可以用于外部中断信号的
输入。这些中断号和 GPIO 编号通常有对应关系,因此 GPIOLIB 为这些 GPIO 提供了
gpio_to_irq()函数用于通过 GPIO 编号而获得该 GPIO 中断号:
int gpio_to_irq(unsigned gpio);
gpio_to_irq()函数调用完成后,返回 GPIO 中断号。
由于并不是所有的 GPIO 都可以作为外部中断信号输入端口,所以 gpio_to_irq()函数不
是对所有的 GPIO 都强制实现的
基于这些函数可以在屏蔽底层寄存器的情况下写驱动
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include <../arch/arm/mach-mx28/mx28_pins.h>
#include "gpio_driver.h"
struct gpio_info {
u32 pin;
char pin_name[20];
struct miscdevice *pmiscdev;
};
static struct gpio_info *gpio_info_file[255];
static struct gpio_info *all_gpios_info;
static struct gpio_info all_gpios_info_283[] ={
{PINID_AUART0_CTS, "gpio-DURX", NULL},
{PINID_AUART0_RTS, "gpio-DUTX", NULL},
{PINID_AUART0_RX, "gpio-URX0", NULL},
{PINID_AUART0_TX, "gpio-UTX0", NULL},
{PINID_AUART1_RX, "gpio-URX1", NULL},
{PINID_AUART1_TX, "gpio-UTX1", NULL},
{PINID_SSP2_SCK, "gpio-URX2", NULL},
{PINID_SSP2_MOSI, "gpio-UTX2", NULL},
{PINID_SSP2_MISO, "gpio-URX3", NULL},
{PINID_SSP2_SS0, "gpio-UTX3", NULL},
{PINID_SAIF0_BITCLK, "gpio-URX4", NULL},
{PINID_SAIF0_SDATA0, "gpio-UTX4", NULL},
/* modify by luozhizhuo*/
{PINID_GPMI_RDY3, "gpio-CRX0", NULL},
{PINID_GPMI_RDY2, "gpio-CTX0", NULL},
{PINID_GPMI_CE3N, "gpio-CRX1", NULL},
{PINID_GPMI_CE2N, "gpio-CTX1", NULL},
{PINID_LCD_D22, "gpio-RUN", NULL},
{PINID_LCD_D23, "gpio-ERR", NULL},
/*end modify*/
{PINID_SSP3_MISO, "gpio-MISO", NULL},
{PINID_SSP3_MOSI, "gpio-MOSI", NULL},
{PINID_SSP3_SCK, "gpio-CLK", NULL},
{PINID_SSP3_SS0, "gpio-CS", NULL},
{PINID_PWM1, "gpio-SDA", NULL},
{PINID_PWM0, "gpio-SCL", NULL},
{PINID_LCD_D17, "gpio-P1.17", NULL},
{PINID_LCD_D18, "gpio-P1.18", NULL},
{PINID_SSP0_DATA4, "gpio-P2.4", NULL},
{PINID_SSP0_DATA5, "gpio-P2.5", NULL},
{PINID_SSP0_DATA6, "gpio-P2.6", NULL},
{PINID_SSP0_DATA7, "gpio-P2.7", NULL},
{PINID_SSP1_SCK, "gpio-P2.12", NULL},
{PINID_SSP1_CMD, "gpio-P2.13", NULL},
{PINID_SSP1_DATA0, "gpio-P2.14", NULL},
{PINID_SSP1_DATA3, "gpio-P2.15", NULL},
{PINID_SAIF0_MCLK, "gpio-P3.20", NULL},
{PINID_SAIF0_LRCLK, "gpio-P3.21", NULL},
{PINID_SAIF1_SDATA0, "gpio-P3.26", NULL},
{PINID_SPDIF, "gpio-P3.27", NULL},
{0, "", NULL}, //the end
};
/*--------------------------------------------------------------------------------------------------------
*/
static int gpio_open(struct inode *inode, struct file *filp);
static int gpio_release(struct inode *inode, struct file *filp);
ssize_t gpio_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos);
static int gpio_ioctl(struct inode *inode,struct file *flip,unsigned int command,unsigned long arg);
static int gpio_init(void);
static void gpio_exit(void);
/*--------------------------------------------------------------------------------------------------------
*/
static int gpio_open(struct inode *inode, struct file *filp)
{
struct gpio_info *gpio_info_tmp;
u32 minor = iminor(inode);
gpio_info_tmp = gpio_info_file[minor];
gpio_free(MXS_PIN_TO_GPIO(gpio_info_tmp->pin));
if (gpio_request(MXS_PIN_TO_GPIO(gpio_info_tmp->pin), gpio_info_tmp->pin_name)) {
printk("request %s gpio faile \n", gpio_info_tmp->pin_name);
return -1;
}
filp->private_data = gpio_info_file[minor];
return 0;
}
static int gpio_release(struct inode *inode, struct file *filp)
{
struct gpio_info *gpio_info_tmp = (struct gpio_info *)filp->private_data;
gpio_free(MXS_PIN_TO_GPIO(gpio_info_tmp->pin));
return 0;
}
ssize_t gpio_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
struct gpio_info *gpio_info_tmp = (struct gpio_info *)filp->private_data;
char data[2];
//printk("make: %s \n", gpio_info_tmp->pin_name);
copy_from_user(data, buf, 2);
data[0] = data[0] - '0';
if (data[0] == 1 || data[0] == 0) {
gpio_direction_output(MXS_PIN_TO_GPIO(gpio_info_tmp->pin), data[0]);
}
return count;
}
static ssize_t gpio_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct gpio_info *gpio_info_tmp = (struct gpio_info *)file->private_data;
int value = 0;
char data[3];
static int flg = 0;
if (flg == 1) {
flg = 0;
return 0;
}
gpio_direction_input(MXS_PIN_TO_GPIO(gpio_info_tmp->pin));
value = gpio_get_value(MXS_PIN_TO_GPIO(gpio_info_tmp->pin));
data[0] = value ? 1 : 0;
data[0] = data[0] + '0';
data[1] = '\n';
data[3] = -1;
copy_to_user(buf, data, 2);
flg = 1;
return 3;
}
static int gpio_ioctl(struct inode *inode,struct file *flip,unsigned int command,unsigned long arg)
{
struct gpio_info *gpio_info_tmp = (struct gpio_info *)flip->private_data;
int data = 0;
switch (command) {
case SET_GPIO_HIGHT:
gpio_direction_output(MXS_PIN_TO_GPIO(gpio_info_tmp->pin), 1);
break;
case SET_GPIO_LOW:
gpio_direction_output(MXS_PIN_TO_GPIO(gpio_info_tmp->pin), 0);
break;
case GET_GPIO_VALUE:
gpio_direction_input(MXS_PIN_TO_GPIO(gpio_info_tmp->pin));
data = gpio_get_value(MXS_PIN_TO_GPIO(gpio_info_tmp->pin));
data = data ? 1 : 0;
copy_to_user((void *)arg, (void *)(&data), sizeof(int));
break;
default:
printk("cmd error \n");
return -1;
}
return 0;
}
static struct file_operations gpio_fops={
.owner = THIS_MODULE,
.open = gpio_open,
.write = gpio_write,
.read = gpio_read,
.release = gpio_release,
.ioctl = gpio_ioctl,
};
static int __init gpio_init(void)
{
int i = 0;
int ret = 0;
all_gpios_info = all_gpios_info_283;
for (i = 0; all_gpios_info[i].pin != 0; i++) {
all_gpios_info[i].pmiscdev = kmalloc(sizeof(struct miscdevice), GFP_KERNEL);
if (all_gpios_info[i].pmiscdev == NULL) {
printk("unable to malloc memory \n");
return -1;
}
memset(all_gpios_info[i].pmiscdev, 0, sizeof(struct miscdevice));
all_gpios_info[i].pmiscdev->name = all_gpios_info[i].pin_name;
all_gpios_info[i].pmiscdev->fops = &gpio_fops;
all_gpios_info[i].pmiscdev->minor = MISC_DYNAMIC_MINOR;
ret = misc_register(all_gpios_info[i].pmiscdev);
if (ret) {
printk("misc regist faile \n");
return -1;
}
gpio_info_file[all_gpios_info[i].pmiscdev->minor] = &(all_gpios_info[i]);
printk("build device i:%d dev:/dev/%s \n", i, all_gpios_info[i].pmiscdev->name);
}
printk("zlg EasyARM-imx283 gpio driver up. \n");
return 0;
}
static void __exit gpio_exit(void)
{
int i = 0;
for (i = 0; all_gpios_info[i].pin != 0; i++) {
misc_deregister(all_gpios_info[i].pmiscdev);
}
printk("zlg EasyARM-imx28xx gpio driver down.\n");
}
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("zhuguojun, ZhiYuan Electronics Co, Ltd.");
MODULE_DESCRIPTION("GPIO DRIVER FOR MAGICARM270.");
对于GPIOLIB底层实现的源码位于drivers/gpiolib.c
GPIOLIB 对每组 GPIO 都用一个 gpio_chip 对象来实现其驱动。gpio_chip 也称为 GPIO
控制器,其定义
struct gpio_chip {
const char *label;
struct device *dev;
struct module *owner;
int (*request)(struct gpio_chip *chip, unsigned offset);
void (*free)(struct gpio_chip *chip, unsigned offset);
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
int (*get)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
int (*set_debounce)(struct gpio_chip *chip, unsigned offset, unsigned debounce);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
int (*to_irq)(struct gpio_chip *chip, unsigned offset);
void (*dbg_show)(struct seq_file *s, struct gpio_chip *chip);
int base;
u16 ngpio;
const char *const *names;
unsigned can_sleep:1;
unsigned exported:1;
};
下面介绍 gpio_chip 的部分成员:
base 该组 GPIO 的起始值。
ngpio 该组 GPIO 的数量。
owner 该成员表示所有者,一般设置为 THIS_MODULE。
request在对该组的GPIO调用gpio_request()函数时,该成员指向的实现函数将被调用。
在该成员指向的实现函数中,通常需要执行指定 GPIO 的初始化操作。
在实现函数中都是用索引值来区别组内的 GPIO。索引值是指组内的某一 GPIO 编号相
对于该组 GPIO 起始值(base)的偏移量,例如,组内第 1 个 GPIO 的索引值为 0、第 2 个
GPIO 的索引值为 1„„ 实现函数的 offset 参数为要操作 GPIO 的索引值(以下相同)。
free 在对该组的 GPIO 调用 gpio_free()函数时,该成员指向的实现函数将被调用。在该
成员的实现函数中,通常需要执行指定 GPIO 硬件资源的释放操作。
direction_input 在对该组的 GPIO 调用 gpio_direction_input()函数时,该成员指向的实
现函数将被调用。在该成员的实现函数中,需要把指定的 GPIO 设置为输入方向。
get 在对该组的 GPIO 调用 gpio_get_value()函数时,该成员指向的实现函数将被调用。
在该成员的实现函数中,需要返回指定 GPIO 的电平输入状态。
direction_output 在对该组的 GPIO 调用 gpio_direction_output()函数时,该成员指向的实
现函数将被调用。在该成员的实现函数中,需要把指定的 GPIO 设置为输出方向。
set 在对该组的 GPIO 调用 gpio_set_value()函数时,该成员指向的实现函数将被调用。
在该成员的实现函数中,需要把指定的 GPIO 设置为指定的电平输出状态。
以mx28为例查看如何实现gpiolib
mx28
gpio是以数字序号为索引的IMX283内部驱动实现了一个1-160 5*32的gpiolib
所以外部接口函数都可以通过一个数字访问到IO口
通过下面这个结构体管理gpio_chip
struct mxs_gpio_port {
int id;
int irq;
int child_irq;
struct mxs_gpio_chip *chip;
struct gpio_chip port;
};
可以通过下面这个函数将gpio_chip添加到内核中
int __init mxs_add_gpio_port(struct mxs_gpio_port *port)
{
int i, ret;
if (!(port && port->chip))
return -EINVAL;
if (mxs_valid_gpio(port))
return -EINVAL;
if (mxs_gpios[port->id])
return -EBUSY;
mxs_gpios[port->id] = port;
port->port.base = port->id * PINS_PER_BANK;
port->port.ngpio = PINS_PER_BANK;
port->port.can_sleep = 1;
port->port.exported = 1;
port->port.to_irq = mxs_gpio_to_irq;
port->port.direction_input = mxs_gpio_input;
port->port.direction_output = mxs_gpio_output;
port->port.get = mxs_gpio_get;
port->port.set = mxs_gpio_set;
port->port.request = mxs_gpio_request;
port->port.free = mxs_gpio_free;
port->port.owner = THIS_MODULE;
ret = gpiochip_add(&port->port);
if (ret < 0)
return ret;
if (port->child_irq < 0)
return 0;
for (i = 0; i < PINS_PER_BANK; i++) {
gpio_irq_chip.mask(port->child_irq + i);
set_irq_chip(port->child_irq + i, &gpio_irq_chip);
set_irq_handler(port->child_irq + i, handle_level_irq);
set_irq_flags(port->child_irq + i, IRQF_VALID);
}
set_irq_chained_handler(port->irq, mxs_gpio_irq_handler);
set_irq_data(port->irq, port);
return ret;
};
这里为 GPIO3_4 和 GPIO3_5 安排的 GPIO 编号分别为 160 和 161。这两个 GPIO 在同
一个 GPIO 组里,该组的 GPIO 编号起始编号为 160。实现该组的 GPIO 控制器代码如程序
struct gpio_chip mx28_gpio_chip = {
.label = "example_gpio",
.owner = THIS_MODULE,
.base = 160,
| .ngpio = 2,
.request = mxs_gpio_request,
.free = mxs_gpio_free,
.direction_input = mxs_gpio_input,
.get = mxs_gpio_get,
.direction_output = mxs_gpio_output,
.set = mxs_gpio_set,
143
.exported = 1,
};
由于该组的 GPIO 编号范围已经超出了内核源码定义的最大值,所以必须把
MXS_ARCH_NR_GPIOS 宏定义的值改为足够大的值:
#define MXS_ARCH_NR_GPIOS (160 + 2)
然后编译内核,把新内核固件烧写到目标机中。
下面介绍 mx28_gpio_chip 各成员函数的实现:
## request 的实现
request 成员的实现函数为 mxs_gpio_request(),该函数的代码如程序清单 4.7 所示。当
内核对编号为 160 ~ 161 的 GPIO 调用 gpio_request()函数时,该函数将被调用。
程序清单 4.7 mxs_gpio_request()函数的实现代码
static int mxs_gpio_request(struct gpio_chip *chip, unsigned int pin)
{
void __iomem *addr = PINCTRL_BASE_ADDR;
pin += 4;
__raw_writel(0x3 << pin * 2, addr + HW_PINCTRL_MUXSEL6_SET); /* set as GPIO */
return 0;
}
pin 参数为要申请 GPIO 编号的索引值:对于编号为 160 的 GPIO,pin 的值为 0;对于
编号为 161 的 GPIO,pin 的值为 1。由于 GPIO3_4 和 GPIO3_5 在 HW_PINCTRL_MUXSEL6
寄存器的操作位分别在 8 ~ 9 和 10 ~ 11,所以在该函数中需要根据 GPIO 的索引值来设置
寄存器的相应位。
## direction_input 的实现
direction_input成员的实现函数为mxs_gpio_input(),该函数的代码如程序清单 4.8所示。
当内核对编号为 160 ~ 161 的 GPIO 调用 gpio_direction_input ()函数时,该函数将被调用。
static int mxs_gpio_input(struct gpio_chip *chip, unsigned int index)
{
void __iomem *base = PINCTRL_BASE_ADDR;
index += 4;
__raw_writel(1 << index, base + HW_PINCTRL_DOE3_CLR);
return 0;
}
index 参数为要操作 GPIO 的索引值。在该函数中,需要根据传入 GPIO 的索引值在
HW_PINCTRL_DOE3 寄存器的正确位设置为 0,以控制相应的 GPIO 为输入工作状态。
## get 的实现
get 成员的实现函数为 mxs_gpio_get(),该函数的代码如程序清单 4.9 所示。当内核对编
号为 160 ~ 161 的 GPIO 调用 gpio_get_value()函数时,该函数将被调用。
程序清单 4.9 mxs_gpio_get()函数的实现代码
static int mxs_gpio_get(struct gpio_chip *chip, unsigned int index)
{
unsigned int data;
void __iomem *base = PINCTRL_BASE_ADDR;
index += 4;
data = __raw_readl(base + HW_PINCTRL_DIN3);
return data & (1 << index);
}
在该函数中,需要根据传入GPIO的索引值从HW_PINCTRL_DIN3寄存器的正确位中,
获得 GPIO 的输入电位状态。
## direction_output 的实现
direction_output 成员的实现函数为 mxs_gpio_output(),该函数的代码如程序清单 4.10
所示。当内核对编号为 160 ~ 161 的 GPIO 调用 gpio_direction_output ()函数时,该函数将被
调用。
程序清单 4.10 mxs_gpio_output()函数的实现代码
static int mxs_gpio_output(struct gpio_chip *chip, unsigned int index, int v)
{
void __iomem *base = PINCTRL_BASE_ADDR;
index += 4;
__raw_writel(1 << index, base + HW_PINCTRL_DOE3_SET); /* 设置为输出工作模式 */
if (v) { /* 当 v 为非 0 时, 设置 GPIO 输出高电平 */
__raw_writel(1 << index, base + HW_PINCTRL_DOUT3_SET);
} else { /* 当 v 为 0 时,设置 GPIO 输出低电平 */
__raw_writel(1 << index, base + HW_PINCTRL_DOUT3_CLR);
}
return 0;
}
参数 v 为 GPIO 被设置为输出工作模式后,默认的输出值:0 为输出低电平,非 0 为输
出高电平。在该函数中,需要根据输入 GPIO 的索引值,在 HW_PINCTRL_DOE3 寄存器把
GPIO 设置为输出工作状态,然后在 HW_PINCTRL_DOUT3 寄存器设置默认的输出电平。
## set 的实现
set 成员的实现函数为 mxs_gpio_set(),该函数的实现代码如程序清单 4.11 所示。当内
核对编号为 160 ~ 161 的 GPIO 调用 gpio_set_value ()函数时,该函数将被调用。
程序清单 4.11 mxs_gpio_set()函数的实现代码
static void mxs_gpio_set(struct gpio_chip *chip, unsigned int index, int v)
{
void __iomem *base = PINCTRL_BASE_ADDR;
index += 4;
if (v) { /* 设置 GPIO 输出高电平 */
__raw_writel(1 << index, base + HW_PINCTRL_DOUT3_SET);
} else { /* 设置 GPIO 输出低电平 */
__raw_writel(1 << index, base + HW_PINCTRL_DOUT3_CLR);
}
}
在该函数中,需要根据输入 GPIO 的索引值,在 HW_PINCTRL_DOUT3 寄存器设置输
出电平。
## GPIO 控制器的注册和注销
在模块的初始化函数中,需要注册 GPIO 控制器,如程序清单 4.12 所示。
程序清单 4.12 注册 GPIO 控制器的实现代码
static int __init mx28_gpio_init(void)
{
int ret = 0;
ret = gpiochip_add(&mx28_gpio_chip);
if (ret) {
printk("add example gpio faile:%d \n", ret);
goto out;
}
printk("add example gpio success... \n");
out:
return ret;
}
module_init(mx28_gpio_init);
static void __exit mx28_gpio_exit(void)
{
gpiochip_remove(&mx28_gpio_chip);
printk("remove example gpio... \n");
}
module_exit(mx28_gpio_exit);
装载模块以后可以在sys/class/gpio/export目录下找到对应的设备文件