Linux Kernel 中,对于 GPIO 功能用 Gpiolib 来管理,作为中间层的 Gpiolib ,对上(其他 Drivers)提供一套统一的 操作 GPIO 的软件API,屏蔽了不同芯片的具体实现,对下(硬件)针对不同的芯片提供对应的一套 framework 用以物理实现功能(怎样增加 framework :需先实现 Specific Chip Driver ,再用 Gpiolib 提供的注册函数将其挂接到 Gpiolib 上即可)
一个 gpio_device 结构体表示一个 GPIO 控制器(Bank),多个结构体在内核中用 gpio_devices 链表存储。
// Linux内核/drivers/gpio/gpiolib.h
struct gpio_device {
int id; //它是系统中第几个gpio controller
struct device dev;
struct cdev chrdev;
struct device *mockdev;
struct module *owner;
struct gpio_chip *chip; //含有各类操作函数
struct gpio_desc *descs; //用来描述引脚,每个引脚对应一个gpio_desc
int base; //每个 Bank 的起始编号,加偏移量成为每个引脚的编号
u16 ngpio; //这个 Bank 有多少个引脚
char *label; //标签、名字
void *data;
struct list_head list;
#ifdef CONFIG_PINCTRL
/*
* If CONFIG_PINCTRL is enabled, then gpio controllers can optionally
* describe the actual pin range which they serve in an SoC. This
* information would be used by pinctrl subsystem to configure
* corresponding pins for gpio usage.
*/
struct list_head pin_ranges;
#endif
};
一个 GPIO 控制器(Bank)包含多个 GPIO 功能引脚,每个引脚用一个 gpio_desc 结构体表示
struct gpio_desc {
struct gpio_device *gdev; //属于哪个 Bank
unsigned long flags; //属性状态
/* flag symbols are bit numbers */
#define FLAG_REQUESTED 0
#define FLAG_IS_OUT 1
#define FLAG_EXPORT 2 /* protected by sysfs_lock */
#define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */
#define FLAG_ACTIVE_LOW 6 /* value has active low */
#define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */
#define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */
#define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */
#define FLAG_IS_HOGGED 11 /* GPIO is hogged */
#define FLAG_SLEEP_MAY_LOOSE_VALUE 12 /* GPIO may loose value in sleep */
/* Connection label */
const char *label; //一般等于gpio_chip的label,标签,名称
/* Name of the GPIO */
const char *name; //引脚名
};
gpio_chip 是操作方法结构体,抽象出所有 GPIO 操作,向上提供统一接口,适配不同的芯片,注意它是以一个 GPIO 控制器(Bank)为单位,该结构体抽象的是一个控制器(Bank)的操作方法,而不是一个引脚的,定义在 include/linux/gpio/driver.h 中,每个 GPIO Bank 都有 N 个寄存器来表示不同的操作。如:
SoC 将 I/O 分为了 2 个 Bank:
Bank 1:GPIOA ~ GPIOB
Bank 2:GPIOC ~ GPIOD
每个 Bank 都有 N 组寄存器来表示 GPIO 的操作:
针对 Bank 1 中的 GPIOA:
GPIOA_CFG 表示 GPIO A 的配置
GPIOA_PULL 表示 GPIO A 的上下拉的配置
GPIOA_DIR 表示 GPIO A 配置成为输入或者输出
GPIOA_DATA 表示 GPIO A 为输出时写高低或输入时读高低
对于 Bank 1 中的 GPIOB 同理
对于 Bank 2 同理
任何一种芯片的所有 I/O 接口都有相应的默认配置,有些芯片的 I/O 接口全部默认为 GPIO,此外再提供管脚复用功能(后期的 Linux 版本中,使用 pin control 来抽象)
对于管脚要实现 GPIO,需要先把此管脚配置成 GPIO ,其他功能(如 I2C,SPI 等)同理
GPIO 有一些通用的特性,如 设置管脚方向,读写管脚电平,作为外部中断输入 等
struct gpio_chip {
const char *label; //标签,取别名
struct device *dev;
struct module *owner;
/*request是特定芯片激活的可选回调函数。如果提供了,
在调用gpio_request()或gpiod_get()时,它会在分配GPIO之前执行。*/
int (*request)(struct gpio_chip *chip, unsigned offset);
/*free 是一个可选的回调函数,用于特定芯片的释放。如果提供了,
那么在调用gpiod_put()或gpio_free()时,它会在GPIO被释放之前执行。*/
void (*free)(struct gpio_chip *chip, unsigned offset);
/*get_direction 在需要知道方向的时候执行GPIO偏移量。返回值应为0表示out,
1表示in(与GPIOF_DIR_XXX相同),或负错误。*/
int (*get_direction)(struct gpio_chip *chip, unsigned offset);
//direction_input 将信号偏移量offset配置为输入,否则返回错误。
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsigned offset,int value);
//get 返回GPIO offset 的值;对于输出信号,这将返回实际感知到的值或0。
int (*get)(struct gpio_chip *chip,unsigned offset);
//set 指定一个输出值给GPIO offset。
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
/*当需要为 mask 定义的多个信号分配输出值时,调用 set_multiple。如果没有提供,
内核将安装一个通用回调函数,它将遍历掩码位并在每个位执行chip->set(i)。*/
void (*set_multiple)(struct gpio_chip *chip, unsigned long *mask,
unsigned long *bits);
int (*set_debounce)(struct gpio_chip *chip, unsigned offset,unsigned debounce);
int (*to_irq)(struct gpio_chip *chip, unsigned offset);
int base; //每个 Bank 的起始编号,加偏移量成为每个引脚的编号
u16 ngpio; //个数
const char *const *names; //每个引脚的名字
bool can_sleep;
bool irq_not_threaded;
bool exported;
#ifdef CONFIG_GPIOLIB_IRQCHIP
/*
* With CONFIG_GPIOLIB_IRQCHIP we get an irqchip
* inside the gpiolib to handle IRQs for most practical cases.
*/
struct irq_chip *irqchip;
struct irq_domain *irqdomain;
unsigned int irq_base;
irq_flow_handler_t irq_handler;
unsigned int irq_default_type;
#endif
#if defined(CONFIG_OF_GPIO)
/*
* If CONFIG_OF is enabled, then all GPIO controllers described in the
* device tree automatically may have an OF translation
*/
struct device_node *of_node;
int of_gpio_n_cells;
int (*of_xlate)(struct gpio_chip *gc,
const struct of_phandle_args *gpiospec, u32 *flags);
};
旧版:
1.获取节点结构体(方法很多,本次通过路径获得节点结构体 )
struct device_node *of_find_node_by_path(const char *path)
path:在设备树中节点的绝对路径
返回值:成功=节点结构体首地址,失败=NULL
2.从节点结构体中获得gpio编号(每个gpio引脚唯一,不同 Bank 中也不相同,只是在gpio子系统中所有引脚的编号,并非引脚编号,从0开始,板子上应该有写)
int of_get_named_gpio(struct device_node *np,const char *propname,int index)
np:节点指针
propname:键(属性)名字,即使用gpio的键
index:索引号,当属性=多个值时,0=第一个,1=第二个
返回值:成功=gpio编号,失败=错误码
3.向内核申请gpio引脚,将指定的gpio管脚添加到pin_ctrl子系统中管理,避免其他外设使用
int gpio_request(unsigned gpio,const char *label)
gpio:获得的gpio引脚的编号
label:标签名,给gpio设置个名字,可填NULL
返回值:成功=0,失败=错误码
4.释放在pin_ctrl子系统中注册的gpio编号(驱动结束 static void __exit XXX_exit(void)中使用)
void gpio_free(unsigned gpio)
gpio:获得的gpio编号
返回值:无
操作GPIO的API
1.设置gpio管脚的方向(输入/输出)
输入:
int gpio_direction_input(unsigned gpio)
输出:
int gpio_direction_output(unsigned gpio, int value)
gpio:获得的gpio编号
value:默认电平的状态,1=高电平,0=低电平
返回值:成功=0,失败=错误码
2.读取管脚的值
int gpio_get_value(unsigned gpio)
gpio:获得的gpio编号
返回值:1=高电平,0=低电平
3.设置管脚的输出电平(记住在驱动结束 static void __exit XXX_exit(void)中要恢复电平)
void gpio_set_value(unsigned gpio, int value)
gpio:获得的gpio编号
value:要设置的电平状态,1=高电平,0=低电平
返回值:无
示例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
/*
myleds{
led1=<&gpioe 10 0>; //10 --->gpioe10 0 管脚的默认状态
led2=<&gpiof 10 0>; //10 --->gpioe10 0 管脚的默认状态
led3=<&gpioe 8 0>; //10 --->gpioe10 0 管脚的默认状态
};
*/
struct device_node *node;
int gpiono;
static int __init mycdev_init(void)
{
//1.获取节点
node = of_find_node_by_path("/myleds");
if(node == NULL){
printk("parse node error\n");
return -EINVAL;
}
//2.解析得到gpio
gpiono = of_get_named_gpio(node,"led1",0);
if(gpiono < 0){
printk("get gpio number error\n");
return gpiono;
}
//3.申请使用的gpio
if(gpio_request(gpiono,NULL)){
printk("request gpio error\n");
return gpiono;
}
//4.通过gpio操作设备
gpio_direction_output(gpiono,1);
return 0;
}
static void __exit mycdev_exit(void)
{
//5.设置输出电平
gpio_set_value(gpiono,0);
//6.释放gpio编号
gpio_free(gpiono);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
新版:增加了更多电平设置,enum gpiod_flags{ }
拥有gpiod_set_config(struct gpio_desc* desc,unsigned long config)函数来设置管脚
1.获取节点结构体(方法很多,本次通过路径获得节点结构体 )
struct device_node *of_find_node_by_path(const char *path)
path:在设备树中节点的绝对路径
返回值:成功=节点结构体首地址,失败=NULL
2.获取一个引脚,对应设备树中键的值,即完成gpio引脚和返回值的一一对应
struct gpio_desc *gpiod_get_from_of_node(struct device_node *node,const char *propname, int index,enum gpiod_flags dflags,const char *label)
node:节点指针
propname:键(属性)名字,即使用gpio的键
index:索引号,当属性=多个值时,0=第一个,1=第二个
dflags:设置电平的旗标
enum gpiod_flags {
GPIOD_ASIS = 0,
GPIOD_IN = GPIOD_FLAGS_BIT_DIR_SET,
GPIOD_OUT_LOW = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT,
GPIOD_OUT_HIGH = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT | GPIOD_FLAGS_BIT_DIR_VAL,
GPIOD_OUT_LOW_OPEN_DRAIN = GPIOD_OUT_LOW | GPIOD_FLAGS_BIT_OPEN_DRAIN,
GPIOD_OUT_HIGH_OPEN_DRAIN = GPIOD_OUT_HIGH | GPIOD_FLAGS_BIT_OPEN_DRAIN,
};
label:标签名,给gpio设置个名字,可填NULL
返回值:成功=引脚结构体首地址,失败=错误码指针(用IS_ERR()检错)
操作GPIO的API
1.设置gpio管脚的方向(输入/输出)
输入:
int gpiod_direction_input(struct gpio_desc *desc)
输出:
int gpiod_direction_output(struct gpio_desc *desc, int value)
desc:获取的引脚结构体指针
value:默认电平的状态,1=高电平,0=低电平
返回值:成功=0,失败=错误码
2.读取管脚的值
int gpiod_get_value(const struct gpio_desc *desc)
desc:获取的引脚结构体指针
返回值:1=高电平,0=低电平
3.设置管脚的输出电平(记住在驱动结束 static void __exit XXX_exit(void)中要恢复电平)
void gpiod_set_value(struct gpio_desc *desc, int value)
desc:获取的引脚结构体指针
value:要设置的电平状态,1=高电平,0=低电平
返回值:无
4.释放gpio(驱动结束 static void __exit XXX_exit(void)中使用)
void gpiod_put(struct gpio_desc *desc)
desc:获取的引脚结构体指针
返回值:无
示例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
/*
myleds{
led1=<&gpioe 10 0>;
led2=<&gpiof 10 0>;
led3=<&gpioe 8 0>;
};
*/
struct device_node* node;
struct gpio_desc* desc;
static int __init mycdev_init(void)
{
// 1.获取节点
node = of_find_node_by_path("/myleds");
if (node == NULL) {
printk("parse node error\n");
return -EINVAL;
}
//2.获取一个引脚
desc = gpiod_get_from_of_node(node,"led2",0,GPIOD_OUT_HIGH,0);
if (IS_ERR(desc)) {
printk("get gpio desc error\n");
return PTR_ERR(desc);
}
//3.操作gpio
return 0;
}
static void __exit mycdev_exit(void)
{
//设置电平
gpiod_set_value(desc, 0);
//释放gpio
gpiod_put(desc);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
本文介绍了Linux内核如何通过Gpiolib管理GPIO,它作为一个中间层提供统一的GPIO操作API,隐藏了不同硬件的具体实现。GPIO由gpio_device结构体表示,每个GPIO控制器包含多个由gpio_desc结构体表示的功能引脚。gpio_chip结构体提供了GPIO控制器的操作方法,包括配置、上下拉、方向和数据读写。此外,文章还展示了旧版和新版GPIO操作API的使用方式。

539

被折叠的 条评论
为什么被折叠?



