(1)系统中GPIO信息的管理,比如有多少个GPIO,每个GPIO的编号是什么等;
(2)GPIO的申请、释放;
(3)IO的输入、输出方向的设置;IO电平的输出或者输入设置;以及GPIO与中断号的相互转换;
(4)DTS中关于GPIO相关的配置信息的解析;
(5)gpio系统与sysfs文件系统的交互;
(6)gpio系统与debugfs文件系统的交互等。
(1)GPIO端口抽象为struct gpio_chip结构体,并提供接口将gpio_chip注册到系统中;
(2)struct gpio_chip结构体抽象了关于GPIO执行申请、释放、方向设置、IO电平输出等接口,特定SoC芯片的GPIO控制器驱动程序需要实现这些接口,从而使设备驱动程序可以正常使用gpio;
(3)将gpio口抽象成struct gpio_dese结构体,包含了gpio口的状态以及所属端口的struct gpio_chip结构体;
(1)对应用层提供了同一访问gpio资源的方式,屏蔽了底层硬件的差异;
(2)gpio资源被抽象成一个类(/sys/class/gpio),每个gpio端口抽象成gpio类下的一个设备,应用通过读写设备文件夹下的文件进行操作gpio口;
(1)gpiolib框架导出了一些函数,用于gpio资源的注册和使用;
(2)在内核启动过程中,Soc会通过gpiolib导出的注册函数,将自己的gpio资源注册到gpiolib框架中,让内核知道Soc的gpio数量以及操作方法;
(3)内核中其他驱动使用gpio口时,需要先向gpiolib申请,只有分配到gpio口才能去操作;
(4)驱动中去操作gpio口时,虽然是调用gpiolib导出的函数接口,但其实gpiolib导出的函数并没有做实际的硬件操作,最终也是调用Soc注册gpio资源时提供的gpio操作方法;
总结:gpiolib机制就是将gpio硬件的共性提取出来写成一个框架,表现出来就是一系列函数接口和结构体;不同Soc的gpio之间的差异,gpiolib框架封装成了结构体和函数指针的形式,所谓Soc注册gpio资源,就是填充gpiolib定义好的函数指针和结构体;
本文介绍的是2.6版本的内核中gpio子系统,在3.12及以后的版本内核对gpio子系统进行了重构,工作原理基本一样,只是方式进行了调整,
用旧版本的gpio子系统不妨碍我们学习内核的gpio子系统;
(1)内核维护人员开发部分:就是提供"/drivers/gpio/gpiolib.c"等文件,定义了gpio资源在内核中的抽象表现形式,导出函数接口给其他开发人员使用,Soc厂商的内核移植人员就是根据导出的函数接口去注册gpio资源;
(2)Soc厂商内核移植人员:根据Soc的实际gpio资源以及gpio操作方式,去填充内核gpio子系统中对gpio资源的结构体定义和操作函数接口指针;
CONFIG_GPIO_SYSFS=y
CONFIG_GPIOLIB=y
(1)CONFIG_GPIO_SYSFS:决定sysfs是否支持gpio子系统,也就是能否在"/sys/class/“目录下看到gpio类;
(2)CONFIG_GPIOLIB:决定是否将”/drivers/gpio/gpiolib.c"编译进内核,如果选择否则在内核和驱动中不能使用gpio子系统相关的函数接口;
总结:CONFIG_GPIOLIB一般都是选择y,因为其他驱动会用到内核gpio子系统;CONFIG_GPIO_SYSFS根据自己的需求来进行选择,如果你不需要通过"/sys/class/gpio"目录下的文件来操作gpio口,就不需要开启;
(1)端口:端口的英文是port,一个端口由多个gpio口组成,用端口的形式便于管理gpio口;
(2)gpio口:多个gpio口组成一个端口;
(3)举例:在Soc中都是以端口为单位对gpio口进行管理,上面就是GPA0端口的控制寄存器,GPA0端口控制寄存器每4bit控制一个gpio口,整个GPA0端口由8个gpio口组成,表示为GPA0_0、GPA0_1、······GPA0_7;关于gpio更详细的知识参考博客:《ARM芯片学习(S5PV210开发)——GPIO控制LED》;
struct gpio_chip {
const char *label; //GPIO端口的名字
struct device *dev;
struct module *owner;
//操作gpio口的方法
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; //gpio在内核中的编号
u16 ngpio; //端口中GPIO的数目
const char *const *names;
unsigned can_sleep:1;
unsigned exported:1;
};
(1)struct gpio_chip结构体:在内核中表示一个GPIO端口;
(2)label:GPIO端口的名字;
(3)众多函数指针:都是操作GPIO口的方法,在注册GPIO资源时需要填充;
(4)base:gpio口在内核中的标号,该标号是一个整数且在内核中唯一,将来申请gpio口时就是根据这个编号来查找;
(5)ngpio:端口中gpio口的数量;
struct gpio_desc {
struct gpio_chip *chip; //gpio口所在端口的信息
unsigned long flags; //gpio口的状态
/* gpio口的状态值 */
#define FLAG_REQUESTED 0
#define FLAG_IS_OUT 1
#define FLAG_RESERVED 2
#define FLAG_EXPORT 3 /* protected by sysfs_lock */
#define FLAG_SYSFS 4 /* exported via /sys/class/gpio/control */
#define FLAG_TRIG_FALL 5 /* trigger on falling edge */
#define FLAG_TRIG_RISE 6 /* trigger on rising edge */
#define FLAG_ACTIVE_LOW 7 /* sysfs value has active low */
#define PDESC_ID_SHIFT 16 /* add new flags before this one */
#define GPIO_FLAGS_MASK ((1 << PDESC_ID_SHIFT) - 1)
#define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE))
#ifdef CONFIG_DEBUG_FS
const char *label;
#endif
}
(1)struct gpio_desc结构体在内核中用来描述一个gpio口的相关信息;
(2)chip:gpio口所在端口的信息;
(3)flags:gpio口的状态;
//注册gpio资源的接口
EXPORT_SYMBOL_GPL(gpiochip_add);
//申请、释放gpio口
EXPORT_SYMBOL_GPL(gpio_request);
EXPORT_SYMBOL_GPL(gpio_free);
EXPORT_SYMBOL_GPL(gpio_request_array);
EXPORT_SYMBOL_GPL(gpio_free_array);
//设置gpio口的方向
EXPORT_SYMBOL_GPL(gpio_direction_input);
EXPORT_SYMBOL_GPL(gpio_direction_output);
//获取、设置gpio口的值
EXPORT_SYMBOL_GPL(__gpio_get_value);
EXPORT_SYMBOL_GPL(__gpio_set_value);
(1)上面导出的函数在"/drivers/gpio/gpiolib.c"文件中;
(2)内核中对gpio资源的注册、操作都是通过上面的方法;
devicemaps_init();
mdesc->map_io();
smdkc110_map_io();
s5pv210_gpiolib_init();
samsung_gpiolib_add_4bit_chips(); //注册gpio端口组
samsung_gpiolib_add_4bit(); //填充单个gpio端口
s3c_gpiolib_add(); //填充gpio口的操作方法
gpiochip_add(); //调用gpiolib框架的注册函数,真正向内核gpio子系统注册gpio信息
对上面流程调用不熟悉的,参考博客:《静态映射和动态映射》;
struct s3c_gpio_chip {
struct gpio_chip chip; //gpiolib架构中描述gpio端口的结构体
struct s3c_gpio_cfg *config; //gpio口的配置信息
struct s3c_gpio_pm *pm; //电源管理相关
void __iomem *base; //gpio端口相关寄存器组的虚拟基地址
int eint_offset;
spinlock_t lock; //锁
#ifdef CONFIG_PM
u32 pm_save[7];
#endif
};
(1)这是三星的工程师在内核中实现gpio驱动时,用来描述gpio端口的结构体;
(2)比较重要的是chip和base变量;
static struct s3c_gpio_chip s5pv210_gpio_4bit[] = {
{
.chip = {
.base = S5PV210_GPA0(0), //gpio端口包含的gpio口的第一个编号
.ngpio = S5PV210_GPIO_A0_NR, //端口的gpio数目
.label = "GPA0", //gpio端口的名字
.to_irq = s5p_gpiolib_gpioint_to_irq, //由gpio编号换算出中断号的方法
},
}, {
.chip = {
.base = S5PV210_GPA1(0),
.ngpio = S5PV210_GPIO_A1_NR,
.label = "GPA1",
.to_irq = s5p_gpiolib_gpioint_to_irq,
},
······
}
(1)s5pv210_gpio_4bit是一个结构体数组,每个成员都是struct s3c_gpio_chip结构体,是三星的代码里表示一个gpio端口;
(2)在定义时初始化了s5pv210芯片的所有gpio引脚,就是初始化了struct s3c_gpio_chip结构体的chip变量;
(3)chip变量是struct gpio_chip结构体,这是gpiolib框架中用来表示一个gpio端口的结构体;
(4)s5pv210_gpio_4bit结构体用来保存s5pv210芯片所有的gpio引脚信息,最后会将所有gpio引脚信息注册到gpiolib框架中;
//S5P_VA_GPIO:这是整个gpio的虚拟地址的基地址,在静态映射中确定
//每次偏移0x20
#define S5PV210_BANK_BASE(bank_nr) (S5P_VA_GPIO + ((bank_nr) * 0x20))
__init int s5pv210_gpiolib_init(void)
{
struct s3c_gpio_chip *chip = s5pv210_gpio_4bit;
int nr_chips = ARRAY_SIZE(s5pv210_gpio_4bit); //获取s5pv210_gpio_4bit数组的成员数,也就是s5pv210芯片中的端口数
int i = 0;
for (i = 0; i < nr_chips; i++, chip++) {
if (chip->config == NULL)
chip->config = &gpio_cfg;
if (chip->base == NULL)
chip->base = S5PV210_BANK_BASE(i); //计算得到每个gpio端口寄存器组的虚拟地址基地址
}
//向内核注册所有gpio端口信息
samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);
return 0;
(1)在内核中都是使用虚拟地址,在注册gpio时需要填充gpio口相关寄存器组的基地址,将来操作gpio口时需要用到;
(2)填充gpio口对应虚拟地址的前提时,要先将gpio口的物理地址映射成虚拟地址,这是在内核的静态映射时完成的,参考博客:《静态映射和动态映射》;
(3)从上面的图可以看出,在Soc中,gpio的寄存器地址是有规则排序的,同一个gpio的相关寄存器地址是紧挨着的,并且每个gpio的相关寄存器功能和数目上都是相同的,所以相邻gpio口的寄存器偏移地址都是一样的,偏移量都是0x20;
(4)每个gpio端口的虚拟地址基地址保存在struct s3c_gpio_chip结构体的base变量中;
__init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)
{
struct gpio_chip *gc = &chip->chip;
int ret;
spin_lock_init(&chip->lock); //初始化锁
//检查gpio的操作方法是否为空
if (!gc->direction_input)
gc->direction_input = s3c_gpiolib_input;
if (!gc->direction_output)
gc->direction_output = s3c_gpiolib_output;
if (!gc->set)
gc->set = s3c_gpiolib_set; //设置gpio引脚值的函数
if (!gc->get)
gc->get = s3c_gpiolib_get; //获取gpio引脚值的函数
/* gpiochip_add() prints own failure message on error. */
ret = gpiochip_add(gc); //向内核gpio子系统注册gpio口
if (ret >= 0)
s3c_gpiolib_track(chip);
}
void __init samsung_gpiolib_add_4bit(struct s3c_gpio_chip *chip)
{
chip->chip.direction_input = samsung_gpiolib_4bit_input; //设置gpio引脚为输入模式的函数
chip->chip.direction_output = samsung_gpiolib_4bit_output;//设置gpio引脚为输出模式的函数
chip->pm = __gpio_pm(&s3c_gpio_pm_4bit); //电源管理
}
void __init samsung_gpiolib_add_4bit_chips(struct s3c_gpio_chip *chip,
int nr_chips)
{
for (; nr_chips > 0; nr_chips--, chip++) {
samsung_gpiolib_add_4bit(chip);
s3c_gpiolib_add(chip);
}
}
(1)gpio口的操作方法就是struct gpio_chip结构体里的函数指针;
(2)在向内核gpio子系统注册gpio口之前,必须填充好gpio口的操作函数指针;
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
(1)gpio_desc是struct gpio_desc结构体数组,每个成员表示一个gpio口,是内核管理gpio口资源的重要变量;
(2)ARCH_NR_GPIOS:代表Soc在内核中gpio编号的最大值,和具体芯片型号有关;
(3)gpio_desc数组保存gpio是按照gpio的编号依次保存的,gpio口在内核的编号保存在struct gpio_chip结构体的base变量中;
(4)假设GPA0_4的编号是4,则gpio_desc[4]就是保存的GPA0_4口的信息;
(5)将来申请gpio口资源,就是按照gpio口的编号进行的;
int gpiochip_add(struct gpio_chip *chip)
{
······
/* 根据gpio口的编号,判断该gpio口是否已经注册过 */
for (id = base; id < base + chip->ngpio; id++) {
if (gpio_desc[id].chip != NULL) {
status = -EBUSY;
break;
}
}
//status=0则代表整个gpio端口里的gpio引脚都没有注册过
if (status == 0) {
//注册gpio端口里的所有gpio口
for (id = base; id < base + chip->ngpio; id++) {
gpio_desc[id].chip = chip;
/* REVISIT: most hardware initializes GPIOs as
* inputs (often with pullups enabled) so power
* usage is minimized. Linux code should set the
* gpio direction first thing; but until it does,
* we may expose the wrong direction in sysfs.
*/
gpio_desc[id].flags = !chip->direction_input
? (1 << FLAG_IS_OUT)
: 0;
}
}
······
return status;
}
(1)根据gpio口在内核中的编号,检查在gpio_desc数组中是否已经注册;
(2)如果没有注册过就去注册,注册过就会跳过;
参见博客:《gpio子系统在sysfs中构建leds类》;
//label--本次申请gpio资源时取个名字,方便以后查询
int gpio_request(unsigned gpio, const char *label)
//释放gpio口
void gpio_free(unsigned gpio);
//设置gpio模式为输出、出入
int gpio_direction_output(unsigned gpio, int value)
int gpio_direction_input(unsigned gpio);
//获取、设置gpio引脚值
int __gpio_get_value(unsigned gpio);
void __gpio_set_value(unsigned gpio, int value);
gpio:gpio口在内核中的编号,这是贯穿始终的;可以在内核源码中查找,也可以在/sys/class/gpio/目录下去查找;
参见博客:《应用层通过/sys/class/gpio文件操作gpio口》;
参见博客:《Linux驱动调试中的Debugfs的使用简介》;
参考资源:https://www.jianshu.com/p/e22a0b3619fe