1、内核gpio子系统介绍
1.1、gpio子系统为驱动程序提供的服务
(1)系统中GPIO信息的管理,比如有多少个GPIO,每个GPIO的编号是什么等; (2)GPIO的申请、释放; (3)IO的输入、输出方向的设置;IO电平的输出或者输入设置;以及GPIO与中断号的相互转换; (4)DTS中关于GPIO相关的配置信息的解析; (5)gpio系统与sysfs文件系统的交互; (6)gpio系统与debugfs文件系统的交互等。
1.2、gpio子系统为Soc芯片的gpio控制器提供的服务
(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.3、gpio子系统为应用程序提供的服务
(1)对应用层提供了同一访问gpio资源的方式,屏蔽了底层硬件的差异; (2)gpio资源被抽象成一个类(/sys/class/gpio),每个gpio端口抽象成gpio类下的一个设备,应用通过读写设备文件夹下的文件进行操作gpio口;
1.4、驱动程序、内核GPIO框架、Soc的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定义好的函数指针和结构体;
1.5、gpio子系统重构
本文介绍的是2.6版本的内核中gpio子系统,在3.12及以后的版本内核对gpio子系统进行了重构,工作原理基本一样,只是方式进行了调整, 用旧版本的gpio子系统不妨碍我们学习内核的gpio子系统;
1.6、gpio子系统层次分析
(1)内核维护人员开发部分:就是提供"/drivers/gpio/gpiolib.c"等文件,定义了gpio资源在内核中的抽象表现形式,导出函数接口给其他开发人员使用,Soc厂商的 内核移植人员就是根据导出的函数接口去注册gpio资源; (2)Soc厂商内核移植人员:根据Soc的实际gpio资源以及gpio操作方式,去填充内核gpio子系统中对gpio资源的结构体定义和操作函数接口指针;
1.7、内核中配置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口,就不需要开启;
2、gpiolib框架分析
2.1、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更详细的知识参考博客:https://blog.csdn.net/weixin_42031299/article/details/116999773
2.2、struct gpio_chip结构体
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;
};
(1)struct gpio_chip结构体:在内核中表示一个GPIO端口; (2)label:GPIO端口的名字; (3)众多函数指针:都是操作GPIO口的方法,在注册GPIO资源时需要填充; (4)base:gpio口在内核中的标号,该标号是一个整数且在内核中唯一,将来申请gpio口时就是根据这个编号来查找; (5)ngpio:端口中gpio口的数量;
2.3、struct gpio_desc结构体
struct gpio_desc {
struct gpio_chip *chip;
unsigned long flags;
#define FLAG_REQUESTED 0
#define FLAG_IS_OUT 1
#define FLAG_RESERVED 2
#define FLAG_EXPORT 3
#define FLAG_SYSFS 4
#define FLAG_TRIG_FALL 5
#define FLAG_TRIG_RISE 6
#define FLAG_ACTIVE_LOW 7
#define PDESC_ID_SHIFT 16
#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口的状态;
2.4、gpiolib导出的符号
EXPORT_SYMBOL_GPL(gpiochip_add);
EXPORT_SYMBOL_GPL(gpio_request);
EXPORT_SYMBOL_GPL(gpio_free);
EXPORT_SYMBOL_GPL(gpio_request_array);
EXPORT_SYMBOL_GPL(gpio_free_array);
EXPORT_SYMBOL_GPL(gpio_direction_input);
EXPORT_SYMBOL_GPL(gpio_direction_output);
EXPORT_SYMBOL_GPL(__gpio_get_value);
EXPORT_SYMBOL_GPL(__gpio_set_value);
(1)上面导出的函数在"/drivers/gpio/gpiolib.c"文件中; (2)内核中对gpio资源的注册、操作都是通过上面的方法;
3、X210建立gpiolib机制的过程分析
3.1、函数调用流程
devicemaps_init();
mdesc->map_io();
smdkc110_map_io();
s5pv210_gpiolib_init();
samsung_gpiolib_add_4bit_chips();
samsung_gpiolib_add_4bit();
s3c_gpiolib_add();
gpiochip_add();
对上面流程调用不熟悉的,参考博客:《静态映射和动态映射》;
3.2、struct s3c_gpio_chip结构体
struct s3c_gpio_chip {
struct gpio_chip chip;
struct s3c_gpio_cfg *config;
struct s3c_gpio_pm *pm;
void __iomem *base;
int eint_offset;
spinlock_t lock;
#ifdef CONFIG_PM
u32 pm_save[7];
#endif
};
(1)这是三星的工程师在内核中实现gpio驱动时,用来描述gpio端口的结构体; (2)比较重要的是chip和base变量;
3.3、s5pv210_gpio_4bit全局变量
static struct s3c_gpio_chip s5pv210_gpio_4bit[] = {
{
.chip = {
.base = S5PV210_GPA0(0),
.ngpio = S5PV210_GPIO_A0_NR,
.label = "GPA0",
.to_irq = s5p_gpiolib_gpioint_to_irq,
},
}, {
.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框架中;
3.4、gpio端口的虚拟地址
#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);
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);
}
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变量中;
3.5、gpio口的操作方法
__init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)
{
struct gpio_chip *gc = &chip->chip;
int ret;
spin_lock_init(&chip->lock);
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;
if (!gc->get)
gc->get = s3c_gpiolib_get;
ret = gpiochip_add(gc);
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;
chip->chip.direction_output = samsung_gpiolib_4bit_output;
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口的操作函数指针;
3.6、gpio_desc全局变量
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口的编号进行的;
3.7、gpiochip_add()函数分析
int gpiochip_add(struct gpio_chip *chip)
{
······
for (id = base; id < base + chip->ngpio; id++) {
if (gpio_desc[id].chip != NULL) {
status = -EBUSY;
break;
}
}
if (status == 0) {
for (id = base; id < base + chip->ngpio; id++) {
gpio_desc[id].chip = chip;
gpio_desc[id].flags = !chip->direction_input
? (1 << FLAG_IS_OUT)
: 0;
}
}
······
return status;
}
(1)根据gpio口在内核中的编号,检查在gpio_desc数组中是否已经注册; (2)如果没有注册过就去注册,注册过就会跳过;
4、gpiolib在sysfs中创建gpio类
参见博客:《gpio子系统在sysfs中构建leds类》;
5、驱动程序中申请、使用gpio口
int gpio_request(unsigned gpio, const char *label)
void gpio_free(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value)
int gpio_direction_input(unsigned gpio);
int __gpio_get_value(unsigned gpio);
void __gpio_set_value(unsigned gpio, int value);
gpio:gpio口在内核中的编号,这是贯穿始终的;可以在内核源码中查找,也可以在/sys/class/gpio/目录下去查找;
6、应用层操作gpio口
参见博客:《应用层通过/sys/class/gpio文件操作gpio口》;
7、gpio口的调试手段:debugfs
参见博客:《Linux驱动调试中的Debugfs的使用简介》;
参考资源:https://www.jianshu.com/p/e22a0b3619fe
|