reference:
内核相关文档
Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt
Documentation\gpio\gpio.txt
Documentation\devicetree\bindings\gpio\gpio.txt
背景
随着内核的发展,linux驱动框架在不断的变化。在早期,GPIO子系统存在之前,我们驱动需要在代码中配置寄存器来使用GPIO引脚。
此后,出现了gpio子系统,后来又出现了pinctrl子系统。
有些平台的实现没有使用内核提供的pinctrl子系统,而是继续采用在内核提供pinctrl子系统前自己实现的那套机制来pinmux操作,如Ti的omap平台;
有些平台则基于pinctrl子系统来实现pinmux、pinconf的控制。
介绍
GPIO子系统可以说是Linux中最简单的子系统。
GPIO(General Purpose Input Output):负责管理整个系统各gpio输入输出管脚的使用情况,同时通过sys文件系统导出了调试信息和应用层控制接口。
Pinctrl(Pin Control):负责管理SOC中各pin的状态,比如输出电流能力、是否有内部上拉或者下拉,是否有功能复用等参数。
要想操作GPIO引脚,需要先把所用引脚配置成GPIO功能,这个通过pinctrl子系统来实现。然后可以根据设置的引脚的方向来读取引脚的值和设置输出值。
在BSP工程师实现好GPIO子系统后,我们就可以在设备树中指定GPIO引脚,在驱动中使用GPIO子系统的标准函数来获取GPIO、设置GPIO方向、读取/设置GPIO的值。这样的驱动代码是于单板无关的。
gpio子系统
gpio子系统内部实现主要提供了两类接口:
一类给bsp工程师,用于注册gpio chip(也就是所谓的gpio控制器驱动)
另一部分给驱动工程师使用,为驱动工程师屏蔽了不同gpio chip之间的区别,驱动工程师调用的api的最终操作流程会导向gpio对应的gpio chip的控制代码,也就是bsp的代码。
核心实现
gpio子系统的实现源码在drivers/gpio文件夹下,主要文件有:
在安卓系统中,实现源码在kernel/drivers/gpio
文件
作用
devres.c
针对gpio api增加的devres机制的支持
gpiolib.c
gpio子系统的核心实现
gpiolib-of.c
对设备树的支持
gpiolib-acpi.c
和acpi相关,不分析
gpio-xxx.c
根据平台的不同,所对应的gpio控制
gpio子系统提供了两层接口,一层给上层驱动工程师调用,一层给下层bsp工程师调用。
上层使用前,当然先得bsp工程师完成对应的动作。
Linux内核中GPIO子系统的软件驱动分层图
GPIO子系统有两套接口:
一是基于描述符(descriptor-based)的,相关api函数都是以”gpiod_”为前缀,它使用gpio_desc结构来表示一个引脚。
另一种是老(legency)的,相关api函数都是以”gpio_”为前缀,它使用一个整数来表示一个引脚,强烈建议不要使用legacy的接口函数。
其实,legacy gpio大部分api就是基于描述符api来实现的,我们可以看到很多legacy api内部的实现调用了to_desc。
// 1.获取GPIO
gpiod_get;
gpiod_get_index;
gpiod_get_array;
devm_gpiod_get;
devm_gpiod_get_index;
devm_gpiod_get_array;
// 2.设置方向
gpiod_direction_input;
gpiod_direction_output;
// 3.读值、写值
gpiod_get_value;
gpiod_set_value;
// 4\. 设为中断(如果必要)
request_irq(gpiod_to_irq(gpio_desc)…); //将gpio转为对应的irq,然后注册该irq的中断handler
// 5.释放GPIO
gpiod_put;
gpiod_put_array;
devm_gpiod_put;
devm_gpiod_put_array;
前缀为”devm_”的含义是设备资源管理,这是一种自动释放资源的机制。
思想:“资源是属于设备的,设备不存在时资源就可以自动释放”。
背景:在Linux驱动开发过程中,先申请了GPIO,再申请内存,如果内存申请失败,那么在返回之前就需要先释放GPIO资源。如果使用的是devm相关函数,在内存申请失败时可以直接返回,设备的销毁函数会自动地释放已经申请了的GPIO资源。
因此,建议使用devm相关函数操作GPIO。
gpio控制api( descriptor)
使用基于描述符的接口时,GPIO被作为一个描述符来使用。
#include
// 更多相关的说明可以参考 Documentation/gpio/consumer.txt
获取一个或一组GPIO
struct gpio_desc * gpiod_get(struct device *dev,
const char *con_id,
enum gpiod_flags flags);
/*
在允许GPIO不存在时,可以使用gpiod_get_optional()和gpiod_get_index_optional()函数。
这两个函数在没有成功分配到GPIO的时候返回NULL而不是-ENOENT。
*/
struct gpio_desc * gpiod_get_optional(struct device *dev,
const char *con_id,
enum gpiod_flags flags);
struct gpio_descs {
unsigned int ndescs; // 数量
struct gpio_desc *desc[]; // 每一个 desc 的情况
}
// 返回gpio_descs 注意:不是 gpio_desc
struct gpio_descs * gpiod_get_array(struct device *dev,
const char *con_id,
enum gpiod_flags flags);
/*多个Pin时需要附带index参数。*/
struct gpio_desc * gpiod_get_index(struct device *dev,
const char *con_id,
unsigned int idx,
enum gpiod_flags flags);
struct gpio_desc * gpiod_get_index_optional(struct device *dev,
const char *con_id,
unsigned int index,
enum gpiod_flags flags);
struct gpio_desc * devm_gpiod_get(struct device *dev, const char *con_id,
enum gpiod_flags flags);
struct gpio_desc * devm_gpiod_get_index(struct device *dev,
const char *con_id,
unsigned int idx,
enum gpiod_flags flags);
描述:必须通过调用gpiod_get()函数族来获取对应的描述符。
参数解析:
con_id:字符串类型,即GPIO的名字;
一般需要查看设备树中的定义。除此之外,我们还可以在设备树文件里添加参数(GPIO_ACTIVE_LOW、GPIO_OPEN_DRAIN、GPIO_OPEN_SOURCE)来触发该接口内部设置gpio,具体的参数格式和具体的gpio chip driver有关,一般可以在Documentation/devicetree/bindings/gpio里找到对应平台的方法。
有关DeviceTree情况中con_id参数的更详细说明请参阅Documentation/gpio/board.txt
例如:
在SD卡驱动看到的去查找名字为cd-gpios的gpio:
// simple.c:
ctx->cd_gpio = devm_gpiod_get_optional(dev, “cd”, 0);
在使用SD卡驱动的主dts就有cd pin的定义:
// xxx.dts:
cd-gpios = ;
index:逻辑下标。将一个GPIO设备(DESC)下的多个Pin看成一个数组,此时index是数组成员下标。
内核文档有个例子,比如gpio如下定义:
led-gpios = , /* red */
, /* green */
; /* blue */
如果index是0,那么对应的就是gpio 15;如果index是1,那么对应就是gpio 16,以此类推。
flags:用于可选地指定GPIO的方向和初始值,它的值可以是:
GPIOD_ASIS或0表示根本不初始化GPIO。需要随后使用专门的函数设置方向
GPIOD_IN初始化GPIO作为输入。
GPIOD_OUT_LOW将GPIO初始化为输出,值为0。
GPIOD_OUT_HIGH将GPIO初始化为输出,值为1。
GPIOD_OUT_LOW_OPEN_DRAIN:与GPIOD_OUT_LOW相同,但强制以开漏的方式使用
GPIOD_OUT_HIGH_OPEN_DRAIN:与GPIOD_OUT_HIGH相同,但强制以开漏的方式使用
最后两个标志用于必须开漏方式的情况,比如GPIO被用作I2C时,如果该GPIO尚未在映射(参见board.txt)中被配置为开漏方式,将被强制配置为开漏方式并给出WARNING。
这两个函数都返回有效的GPIO描述符或可被IS_ERR()检查的错误代码(它们永远不会返回NULL指针)。