2.6.35内核的gpio子系统详解

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资源注册三者之间的联系

2.6.35内核的gpio子系统详解_第1张图片

(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口的区别

2.6.35内核的gpio子系统详解_第2张图片

(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》;

2.2、struct gpio_chip结构体

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口的数量;

2.3、struct gpio_desc结构体

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口的状态;

2.4、gpiolib导出的符号

//注册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资源的注册、操作都是通过上面的方法;

3、X210建立gpiolib机制的过程分析

3.1、函数调用流程

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信息

对上面流程调用不熟悉的,参考博客:《静态映射和动态映射》;

3.2、struct s3c_gpio_chip结构体

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变量;

3.3、s5pv210_gpio_4bit全局变量

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框架中;

3.4、gpio端口的虚拟地址

2.6.35内核的gpio子系统详解_第3张图片

//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变量中;

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);	//初始化锁

	//检查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口的操作函数指针;

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)
{
	······
	/* 根据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)如果没有注册过就去注册,注册过就会跳过;

4、gpiolib在sysfs中创建gpio类

参见博客:《gpio子系统在sysfs中构建leds类》;

5、驱动程序中申请、使用gpio口

//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/目录下去查找;

6、应用层操作gpio口

参见博客:《应用层通过/sys/class/gpio文件操作gpio口》;

7、gpio口的调试手段:debugfs

参见博客:《Linux驱动调试中的Debugfs的使用简介》;

参考资源:https://www.jianshu.com/p/e22a0b3619fe

你可能感兴趣的:(嵌入式Linux内核,嵌入式驱动开发,驱动开发,stm32,单片机)