linux GPIO子系统

版权声明:本文为博主原创文章,未经允许,不得转载。 https://blog.csdn.net/weixin_41944449/article/details/86554796

linux GPIO子系统

linux中有各种各样的子模块系统,最简单的应该就是GPIO子系统了。GPIO(General Purpose Input Output),通俗的说就是输入输出管脚了,控制它状态,就是GPIO子系统了,滑稽脸.jpg。
一直都说,linux内核代码,满满的都是软件分层的设计思想,下面通过linux4.20.3版本的内核代码,以全志平台v3s为介绍,一起来学习一下Linux是如果针对这么多平台设计一套通过的GPIO框架的。

linux内核中,有GPIO子系统,还有一个叫pinctrl的小模块,它将会负责管理SOC中各pin的状态,比如输出电流能力、是否有内部上拉或者下拉,是否有功能复用等,这些参数,都是通过pinctrl设置的。

pinctrl

pinctrl相应的代码在/drivers/pinctrl目录下,文件列表如下:

├── core.c
├── core.h
├── devicetree.c
├── devicetree.h
├── pinconf.c
├── pinconf-generic.c
├── pinconf.h
├── pinctrl-single.c
├── pinctrl-utils.c
├── pinctrl-utils.h
├── pinmux.c
├── pinmux.h
└── sunxi
      ├── Kconfig
      ├── Makefile
      ├── pinctrl-sun8i-v3s.c
      ├── pinctrl-sunxi.c
      └── pinctrl-sunxi.h

注册pinctrl

linux内核是怎么知道当前有多少pin,各个pin作什么功能,这些信息都是通过相应平台的pinctrl驱动获取的,下面以sunxi-v3s为例进行介绍。
首先的将会在/drivers/pinctrl/sunxi目录下存在一个pinctrl-sun8i-v3s.c的文件,在该文件最后将会有这样的一个字段builtin_platform_driver(sun8i_v3s_pinctrl_driver),简单理解就是通过builtin_platform_driver向内核注册了sun8i_v3s_pinctrl_driver这个pinctrl驱动。platform总线模型这里就不介绍了,只需要理解,当我们在dts中配置了相应的设备,同时compatible字段一致,即可成功匹配,接着将会执行相应的probe函数。

[ pinctrl-sun8i-v3s.c ]
static int sun8i_v3s_pinctrl_probe(struct platform_device *pdev)
{
	return sunxi_pinctrl_init(pdev,
				  &sun8i_v3s_pinctrl_data);
}

static const struct of_device_id sun8i_v3s_pinctrl_match[] = {
	{ .compatible = "allwinner,sun8i-v3s-pinctrl", },
	{}
};

static struct platform_driver sun8i_v3s_pinctrl_driver = {
	.probe	= sun8i_v3s_pinctrl_probe,
	.driver	= {
		.name		= "sun8i-v3s-pinctrl",
		.of_match_table	= sun8i_v3s_pinctrl_match,
	},
};
[ sun8i-v3s.dtsi ]
                pio: pinctrl@1c20800 {
                        compatible = "allwinner,sun8i-v3s-pinctrl";
                        reg = <0x01c20800 0x400>;

在v3s的pinctrl probe函数中,将会通过sunxi_pinctrl_init()函数将相应的pin信息注册到pinctrl模块中。
通过跟踪probe函数可以知道,在probe函数中还将通过struct sunxi_pinctrl_desc将pin的功能复用信息传递到了sunxi_pinctrl中,在probe中,将会解析pin的功能复用、pin个数、gooups数量等,最终通过devm_pinctrl_register()函数,将信息的信息登记到pinctrl_dev中,填充gpio_chip结构体,最后获取pin的中断等信息。

内核GPIO使用

相应的信息注册之后,其他的驱动又是如何使用这些pin呢,下面一起来看一下。
在内核中,需要使用GPIO之前,需要先通过[ /drivers/gpio/gpiolib-legacy.c ]gpio_request()函数申请相应的GPIO资源。

int gpio_request(unsigned gpio, const char *label)
{
	struct gpio_desc *desc = gpio_to_desc(gpio);

	/* Compatibility: assume unavailable "valid" GPIOs will appear later */
	if (!desc && gpio_is_valid(gpio))
		return -EPROBE_DEFER;

	return gpiod_request(desc, label);
}

显然,还函数的第一个参数就是pin的索引,第二个参数是pin标签,之前说到,pin可能存在多种功能,那么在request的时候,怎么知道当前应该使用什么功能呢。
在gpio_request()函数中,将会现先根据gpio通过gpio_to_desc()函数获取gpio_desc。在gpio_to_desc()函数中,就是将传递进来的gpio索引与全局链表gpio_devices作比较,最终得到该gpio所述的gpio_desc。那么该全局链表gpio_devices的信息又是什么时候添加的呢,是在pinctrl驱动的probe函数中通过gpiochip_add_data(pctl->chip, pctl)函数添加的([probe()—>gpiochip_add_data()—>gpiochip_add_data_with_key()—>gpiodev_add_to_list()])。

前面有提到,在pinctrl-sun8i-v3s.c中通过probe将pin相应信息传递到pinctrl_dev,下面我们假设需要内核串口申请PB0作为uart2的TX功能引脚。在request中,将会在gpiod_request_commit()函数中通过status = chip->request(chip, offset)调用到相应平台的request函数。还记得吧,sunxi平台的chip的request函数是在pinctrl的probe函数赋值为gpiochip_generic_request()函数,最终将会调用到core.c中的pinctrl_gpio_request()函数,将pin的引用加1。

static const struct sunxi_desc_pin sun8i_v3s_pins[] = {
	...
	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 0),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart2"),		/* TX */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 0)),	/* PB_EINT0 */
	...

那么又是如何确定该pin当前使用什么功能呢?这个是通过pinctrl_select_state()函数设置的,pinctrl_select_state()—>pinctrl_commit_state()—>pinmux_enable_setting()—>ops->set_mux()—>sunxi_pmx_set_mux()—>sunxi_pmx_set(),通过这样的一条通路,完成pin功能复用设置。

设置GPIO的输出值,代码通过如下:gpio_set_value()—>__gpio_set_value()—>gpiod_set_raw_value()—>gpiod_set_raw_value_commit()—>chip->set()—>sunxi_pinctrl_gpio_set()。

应用层又是如何设置GPIO呢?

要通过应用层配置GPIO状态,即通过sysfs接口访问GPIO,需要在编译之前先选上相应的接口,在内核主目录执行make menuconfig,按照以下操作执行:

扫描二维码关注公众号,回复: 5245867 查看本文章
[ make menuconfig ]
 Device Drivers  --->
 	[*] GPIO Support  --->
 		[*]   /sys/class/gpio/... (sysfs interface) 

接着编译烧写之后,将会在/sys/class/gpio目录下存在三种文件:

  • export/unexport文件
  • gpioN指代具体的gpio引脚
  • gpio_chipN指代gpio控制器
export/unexport文件接口

/sys/class/gpio/export,该接口只能写不能读
用户程序通过写入gpio的编号来向内核申请将某个gpio的控制权导出到用户空间当然前提是没有内核代码申请这个gpio端口
比如 echo 19 > export
上述操作会为19号gpio创建一个节点gpio19,此时/sys/class/gpio目录下边生成一个gpio19的目录

/sys/class/gpio/unexport和导出的效果相反。
比如 echo 19 > unexport
上述操作将会移除gpio19这个节点。

/sys/class/gpio/gpioN

指代某个具体的gpio端口,里边有如下属性文件:

  • direction:表示gpio端口的方向,读取结果是in或out。该文件也可以写,写入out 时该gpio设为输出同时电平默认为低。写入low或high则不仅可以设置为输出 还可以设置输出的电平;
  • value:表示gpio引脚的电平,0(低电平)1(高电平),如果gpio被配置为输出,这个值是可写的,记住任何非零的值都将输出高电平, 如果某个引脚能并且已经被配置为中断,则可以调用poll(2)函数监听该中断,中断触发后poll(2)函数就会返回;
/sys/class/gpio/gpiochipN

gpiochipN表示的就是一个gpio_chip,用来管理和控制一组gpio端口的控制器,该目录下存在一下属性文件:

  • base:和N相同,表示控制器管理的最小的端口编号;
  • lable:诊断使用的标志(并不总是唯一的);
  • ngpio:表示控制器管理的gpio端口数量(端口范围是:N ~ N+ngpio-1);
参考

猜你喜欢

转载自blog.csdn.net/weixin_41944449/article/details/86554796