1. 前言
本站之前的三篇文章[1][2][3]介绍了pin controller(对应的pin controller subsystem)、gpio controller(对应的GPIO subsystem)有关的基本概念,包括pin multiplexing、pin configuration等等。本文将基于这些文章,单纯地从pin controller driver的角度(屏蔽掉pinctrl core的实现细节),理解pinctrl subsystem的设计思想,并掌握pinctrl驱动的移植和实现方法。
2. pin controller的概念和软件抽象
相信每一个嵌入式从业人员,都知道“pin(管脚)”是什么东西(就不赘述了)。由于SoC系统越来越复杂、集成度越来越高,SoC中pin的数量也越来越多、功能也越来越复杂,这就对如何管理、使用这些pins提出了挑战。因此,用于管理这些pins的硬件模块(pin controller)就出现了。相应地,linux kernel也出现了对应的驱动(pin controller driver)。
Kernel pinctrl core使用struct pinctrl_desc抽象一个pin controller,该结构的定义如下(先贴在这里,后面会围绕这个抽象一步步展开):
/**
* struct pinctrl_desc - pin controller descriptor, register this to pin
* control subsystem
* @name: name for the pin controller
* @pins: an array of pin descriptors describing all the pins handled by
* this pin controller
* @npins: number of descriptors in the array, usually just ARRAY_SIZE()
* of the pins field above
* @pctlops: pin control operation vtable, to support global concepts like
* grouping of pins, this is optional.
* @pmxops: pinmux operations vtable, if you support pinmuxing in your driver
* @confops: pin config operations vtable, if you support pin configuration in
* your driver
* @owner: module providing the pin controller, used for refcounting
* @num_custom_params: Number of driver-specific custom parameters to be parsed
* from the hardware description
* @custom_params: List of driver_specific custom parameters to be parsed from
* the hardware description
* @custom_conf_items: Information how to print @params in debugfs, must be
* the same size as the @custom_params, i.e. @num_custom_params
* @link_consumers: If true create a device link between pinctrl and its
* consumers (i.e. the devices requesting pin control states). This is
* sometimes necessary to ascertain the right suspend/resume order for
* example.
*/
struct pinctrl_desc {
const char *name;
const struct pinctrl_pin_desc *pins;
unsigned int npins;
const struct pinctrl_ops *pctlops;
const struct pinmux_ops *pmxops;
const struct pinconf_ops *confops;
struct module *owner;
#ifdef CONFIG_GENERIC_PINCONF
unsigned int num_custom_params;
const struct pinconf_generic_params *custom_params;
const struct pin_config_item *custom_conf_items;
#endif
bool link_consumers;
};
注1:本文后续的描述基于本站“X Project”所使用的kernel版本[4]。
注2:本文很多的表述(特别是例子),都是引用kernel的document[5](写的很好,可以耐心看看)。
2.1 Pin
kernel的pin controller子系统要想管理好系统的pin资源,第一个要搞明白的问题就是:系统中到底有多少个pin?用软件语言来表述就是:要把系统中所有的pin描述出来,并建立索引。这由上面struct pinctrl_desc结构中pins和npins来完成。
对pinctrl core来说,它只关心系统中有多少个pin,并使用自然数为这些pin编号,后续的操作,都是以这些编号为操作对象。至于编号怎样和具体的pin对应上,完全是pinctrl driver自己的事情。
因此,pinctrl driver需要根据实际情况,将系统中所有的pin组织成一个struct pinctrl_pin_desc类型的数组,该类型的定义为:
/**
* struct pinctrl_pin_desc - boards/machines provide information on their
* pins, pads or other muxable units in this struct
* @number: unique pin number from the global pin number space
* @name: a name for this pin
* @drv_data: driver-defined per-pin data. pinctrl core does not touch this
*/
struct pinctrl_pin_desc {
unsigned number;
const char *name;
void *drv_data;
};
number和name完全由driver自己决定,不过要遵循有利于代码编写、有利于理解等原则。另外,为了便于driver的编写,可以在drv_data中保存driver的私有数据结构(可以包含相关的寄存器偏移等信息)。
注3:[5]中有个例子,大家可以参考理解。
2.2 Pin groups
在SoC系统中,有时需要将很多pin组合在一起,以实现特定的功能,例如SPI接口、I2C接口等。因此pin controller需要以group为单位,访问、控制多个pin,这就是pin groups。相应地,pin controller subsystem需要提供一些机制,来获取系统中到底有多少groups、每个groups包含哪些pins、等等。
因此,pinctrl core在struct pinctrl_ops中抽象出三个回调函数,用来获取pin groups相关信息,如下:
struct pinctrl_ops {
int (*get_groups_count) (struct pinctrl_dev *pctldev);
const char *(*get_group_name) (struct pinctrl_dev *pctldev,
unsigned selector);
int (*get_group_pins) (struct pinctrl_dev *pctldev,
unsigned selector,
const unsigned **pins,
unsigned *num_pins);
void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,
unsigned offset);
int (*dt_node_to_map) (struct pinctrl_dev *pctldev,
struct device_node *np_config,
struct pinctrl_map **map, unsigned *num_maps);
void (*dt_free_map) (struct pinctrl_dev *pctldev,
struct pinctrl_map *map, unsigned num_maps);
};
get_groups_count,获取系统中pin groups的个数,后续的操作,将以相应的索引为单位(类似数组的下标,个数为数组的大小)。
get_group_name,获取指定group(由索引selector指定)的名称。
get_group_pins,获取指定group的所有pins(由索引selector指定),结果保存在pins(指针数组)和num_pins(指针)中。
注4:dt_node_to_map用于将device tree中的pin state信息转换为pin map,具体可参考后面第3章、第4章的介绍。
当然,最终的group信息是由pinctrl driver提供的,至于driver怎么组织这些group,那是driver自己的事情了,[4]中有一个例子,大家可以参考(在编写pinctrl driver的时候直接copy然后rename即可)。
2.3. Pin configuration(对象是pin或者pin group)
2.1和2.2中介绍了pinctrl subsystem中的操作对象(pin or pin group)以及抽象方法。嵌入式系统的工程师都知道,SoC中的管脚有些属性可以配置,例如上拉、下拉、高阻、驱动能力等。pinctrl subsystem使用pin configuration来封装这些功能,具体体现在struct pinconf_ops数据结构中,如下:
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
bool is_generic;
#endif
int (*pin_config_get) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *config);
int (*pin_config_set) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *configs,
unsigned num_configs);
int (*pin_config_group_get) (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *config);
int (*pin_config_group_set) (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *configs,
unsigned num_configs);
int (*pin_config_dbg_parse_modify) (struct pinctrl_dev *pctldev,
const char *arg,
unsigned long *config);
void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev,
struct seq_file *s,
unsigned offset);
void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev,
struct seq_file *s,
unsigned selector);
void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev,
struct seq_file *s,
unsigned long config);
};
pin_config_get,获取指定pin(管脚的编号,由2.1中pin的注册信息获得)当前配置,保存在config指针中(配置的具体含义,只有pinctrl driver自己知道,下同)。
pin_config_set,设置指定pin的配置(可以同时配置多个config,具体意义要由相应pinctrl driver解释)。
pin_config_group_get、pin_config_group_set,获取或者设置指定pin group的配置项。
剩下的是一些debug用的api,不再说明(用得着的时候,再研究也不迟)。
关于pinconf,有一点一定要想明白:
kernel pinctrl subsystem并不关心configuration的具体内容是什么,它只提供pin configuration get/set的通用机制,至于get到的东西,以及set的东西,到底是什么,是pinctrl driver自己的事情。后面结合pin map和pin state的介绍(3.2小节),就能更好地理解这种设计了。
2.4. Pin multiplexing(对象是pin或者pin group)
为了照顾不同类型的产品、不同的应用场景,SoC中的很多管脚可以配置为不同的功能,例如A2和B5两个管脚,既可以当作普通的GPIO使用,又可以配置为I2C0的的SCL和SDA,也可以配置为UART5的TX和RX,这称作管脚的复用(pin multiplexing,简称为pinmux)。kernel pinctrl subsystem使用struct pinmux_ops来抽象pinmux有关的操作,如下:
struct pinmux_ops {
int (*request) (struct pinctrl_dev *pctldev, unsigned offset);
int (*free) (struct pinctrl_dev *pctldev, unsigned offset);
int (*get_functions_count) (struct pinctrl_dev *pctldev);
const char *(*get_function_name) (struct pinctrl_dev *pctldev,
unsigned selector);
int (*get_function_groups) (struct pinctrl_dev *pctldev,
unsigned selector,
const char * const **groups,
unsigned *num_groups);
int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,
unsigned group_selector);
int (*gpio_request_enable) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
void (*gpio_disable_free) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
int (*gpio_set_direction) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset,
bool input);
bool strict;
};
注5:function的概念:
- 为了管理SoC的管脚复用,pinctrl subsystem抽象出function的概念,用来表示I2C0、UART5等功能。pin(或者pin group)所对应的function一经确定,它(们)的管脚复用状态也就确定了
- 和2.2中描述的pin group类似,pinctrl core不关心function的具体形态,只要求pinctrl driver将SoC的所有可能的function枚举出来(格式自行定义,不过需要有编号、名称等内容,可参考[5]中的例子),并注册给pinctrl core。后续pinctrl core将会通过function的索引,访问、控制相应的function
- 另外,有一点大家应该很熟悉:在SoC的设计中,同一个function(如I2C0),可能可以map到不同的pin(或者pin group)上
理解了function的概念之后,struct pinmux_ops中的API就简单了:
get_functions_count,获取系统中function的个数。
get_function_name,获取指定function的名称。
get_function_groups,获取指定function所占用的pin group(可以有多个)。
set_mux,将指定的pin group(group_selector)设置为指定的function(func_selector)。
request,检查某个pin是否已作它用,用于管脚复用时的互斥(避免多个功能同时使用某个pin而不知道,导致奇怪的错误)。
free,request的反操作。
gpio_request_enable、gpio_disable_free、gpio_set_direction,gpio有关的操作,等到gpio有关的文章中再说明。
strict,为true时,说明该pin controller不允许某个pin作为gpio和其它功能同时使用。
3. pinctrl subsystem的控制逻辑
第2章以struct pinctrl_desc为引子,介绍了pinctrl subsystem中有关pin controller的概念抽象,包括pin、pin group、pinconf、pinmux、pinmux function、等等,相当于从provider的角度理解pinctrl subsystem。那么,问题来了,怎么使用pinctrl subsystem提供的功能控制管脚的配置以及功能复用呢?这看似需要由consumer(例如各个外设的驱动)自行处理,实际上却不尽然:
1)前面讲了,由于pinctrl subsystem的特殊性,对于pin configuration以及pin multiplexing而言,要怎么配置、怎么复用,只有pinctrl driver自己知道。同理,各个consumer也是云里雾里,啥都搞不清楚(想象各位编写设备驱动需要用到pinctrl的时候的心情吧!)。
2)那这样的配置有道理吗?有!记得我们在[6]中提到过,对一个确定的产品来说,某个设备所使用的pinctrl功能(function)、以及所对应的pin(或者pin group)、还有这些pin(或者pin group)的属性配置,基本上在产品设计的时候就确定好了,consumer没必要(也不想)关心技术细节。因此pinctrl driver就要多做一些事情,帮助consumer厘清pin有关资源的使用情况,并在这些设备需要使用的时候(例如probe时),一声令下,将资源准备好。
3)因此,pinctrl subsystem的设计理念就是:不需要consumer关心pin controller的技术细节,只需要在特定的时候向pinctrl driver发出一些简单的指令,告诉pinctrl driver自己的需求即可(例如我在运行时需要使用这样一组配置,在休眠时使用那样一组配置)。
4)最后,需求的细节(例如需要使用哪些pin、配置为什么功能、等等),要怎么确定呢?一般是通过machine的配置文件、具体版型的device tree等,告诉pinctrl subsystem,以便在需要的时候使用。
下面小节我们将会根据这些思路,进行更为详细的分析。
3.1 pin state
据前面的描述,pinctrl driver抽象出来了一些离散的对象:pin(pin group)、function、configuration,并实现了这些对象的控制和配置方式。然后我们回到某一个具体的device上(如SPI2):
该device在某一状态下(如工作状态、休眠状态、等等),所使用的pin(pin group)、pin(pin group)的function和configuration,是唯一确定的。
把上面的话颠倒过来说,就是:
pin(pin group)以及相应的function和configuration的组合,可以确定一个设备的一个“状态”。
这个状态在pinctrl subsystem中就称作pin state。而pinctrl driver和具体板型有关的部分,需要负责枚举该板型下所有device(当然,特指那些需要pin资源的device)的所有可能的状态,并详细定义这些状态需要使用的pin(或pin group),以及这些pin(或pin group)需要配置为哪种function、哪种配置项。这些状态确定之后,consumer(device driver)就好办了,直接发号施令就行了:
喂,pinctrl subsystem,帮忙将我的xxx state激活。
pinctrl subsystem接收到指令后,找到该state的相关信息(pin、function和configuration),并调用pinctrl driver提供的相应API(参考第2章中struct pinctrl_desc有关的内容),控制pin controller即可。
3.2 pin map
在pinctrl subsystem中,pin state有关的信息是通过pin map收集,相关的数据结构如下:
/* include/linux/pinctrl/machine.h */
struct pinctrl_map {
const char *dev_name;
const char *name;
enum pinctrl_map_type type;
const char *ctrl_dev_name;
union {
struct pinctrl_map_mux mux;
struct pinctrl_map_configs configs;
} data;
};
dev_name,device的名称。
name,pin state的名称。
ctrl_dev_name,pin controller device的名字。
type,该map的类型,包括PIN_MAP_TYPE_MUX_GROUP(配置管脚复用)、PIN_MAP_TYPE_CONFIGS_PIN(配置pin)、PIN_MAP_TYPE_CONFIGS_GROUP(配置pin group)、PIN_MAP_TYPE_DUMMY_STATE(不需要任何配置,仅仅为了表示state的存在。
data,该map需要用到的数据项,是一个联合体,如果map的类型是PIN_MAP_TYPE_CONFIGS_GROUP,则为struct pinctrl_map_mux类型的变量;如果map的类型是PIN_MAP_TYPE_CONFIGS_PIN或者PIN_MAP_TYPE_CONFIGS_GROUP,则为struct pinctrl_map_configs类型的变量。
struct pinctrl_map_mux的定义如下:
struct pinctrl_map_mux {
const char *group;
const char *function;
};
group,group的名字,指明该map所涉及的pin group。
function,function的名字,表示该map需要将group配置为哪种function。
struct pinctrl_map_configs的定义如下:
struct pinctrl_map_configs {
const char *group_or_pin;
unsigned long *configs;
unsigned num_configs;
};
group_or_pin,pin或者pin group的名字。
configs,configuration数组,指明要将该group_or_pin配置成“神马样子”。
num_configs,配置项的个数。
注6:讲到这里,应该理解为什么2.3小结中struct pinconf_ops中的api,都不知道configuration到底是什么东西了吧?因为都是pinctrl driver自己安排好的,自产自销,外人(pinctrl subsystem以及consumers)没必要理解!
最后,某一个device的某一种pin state,可以由多个不同类型的map entry组合而成,举例如下[5]:
static struct pinctrl_map mapping[] __initdata = {
PIN_MAP_MUX_GROUP("foo-i2c.0", PINCTRL_STATE_DEFAULT, "pinctrl-foo", "i2c0", "i2c0"),
PIN_MAP_CONFIGS_GROUP("foo-i2c.0", PINCTRL_STATE_DEFAULT, "pinctrl-foo", "i2c0", i2c_grp_configs),
PIN_MAP_CONFIGS_PIN("foo-i2c.0", PINCTRL_STATE_DEFAULT, "pinctrl-foo", "i2c0scl", i2c_pin_configs),
PIN_MAP_CONFIGS_PIN("foo-i2c.0", PINCTRL_STATE_DEFAULT, "pinctrl-foo", "i2c0sda", i2c_pin_configs),
};
这是一个mapping数组,包含4个map entry,定义了"foo-i2c.0"设备的一个pin state(PINCTRL_STATE_DEFAULT,"default"),该state由一个PIN_MAP_TYPE_MUX_GROUP entry、一个PIN_MAP_TYPE_CONFIGS_GROUP entry以及两个PIN_MAP_TYPE_CONFIGS_PIN entry组成(这些entry的具体含义,大家可参考[5]以及相应的source code理解,这里不再详细说明)。
3.3 通过dts生成pin map
在旧时代,kernel的bsp工程师需要在machine有关的代码中,静态的定义pin map数组(类似于3.2小节中的例子),这一个非常繁琐且不易维护的过程。不过当kernel引入device tree之后,事情就简单了很多:
pinctrl driver确定了pin map各个字段的格式之后,就可以在dts文件中维护pin state以及相应的mapping table。pinctrl core在初始化的时候,会读取并解析dts,并生成pin map。
而各个consumer,可以在自己的dts node中,直接引用pinctrl driver定义的pin state,并在设备驱动的相应的位置,调用pinctrl subsystem提供的API,active或者deactive这些state。
至于dts中pin map描述的格式是什么,则完全由pinctrl driver自己决定,因为,最终的解析工作(dts to map)也是它自己做的(具体可参考后面第4章的介绍)。
4. pinctrl subsystem的整体流程
通过前面几章的分析,我们对pinctrl subsystem有了一个比较全面的认识,这里以pinctrl整个使用流程为例,简单的总结一下。
- pinctrl driver根据pin controller的实际情况,实现struct pinctrl_desc(包括pin/pin group的抽象,function的抽象,pinconf、pinmux的operation API实现,dt_node_to_map的实现,等等),并注册到kernel中。
- pinctrl driver在pin controller的dts node中,根据自己定义的格式,描述每个device的所有pin state。大致的形式如下(具体可参考kernel中的代码,照葫芦总能画出来瓢~~~):
pinctrl_xxx { /* the dts node for pin controller */
...
xxx_state_xxx: xxx_xxx { /* dts node for xxx device's "xxx state" */
xxx_pinmux { /* pinmux entry */
xxx = xxxx;
xxx = xxxxxxx;
...
};
xxx_pinconf { /* pinconf entry */
xxx = xxxx;
xxx = xxxxxxx;
...
};
xxx_pinconf {
xxx = xxxx;
xxx = xxxxxxx;
...
};
...
};
...
};
- 相应的consumer driver可以在自己的dts node中,引用pinctrl driver所定义的pin state,例如:
xxx_device: xxx@xxxxxxxx {
compatible = "xxx,xxxx";
...
pinctrl-names = "default";
pinctrl-0 = <&xxx_state_xxx>;
...
};
- consumer driver在需要的时候,可以调用pinctrl_get/devm_pinctrl_get接口,获得一个pinctrl handle(struct pinctrl类型的指针)。pinctrl subsystem在pinctrl get的过程中,解析consumer device的dts node,找到相应的pin state,进行调用pinctrl driver提供的dt_node_to_map API,解析pin state并转换为pin map。以driver probe时为例,调用过程如下(大家可以自己去看代码):
probe
devm_pinctrl_get or pinctrl_get
create_pinctrl(drivers/pinctrl/core.c)
pinctrl_dt_to_map(drivers/pinctrl/devicetree.c)
dt_to_map_one_config
pctlops->dt_node_to_map
- consumer获得pinctrl handle之后,可以调用pinctrl subsystem提供的API(例如pinctrl_select_state),使自己的某个pin state生效。pinctrl subsystem进而调用pinctrl driver提供的各种回调函数,配置pin controller的硬件。
注7:具体细节就不再描述了,后续开发pinctrl driver的时候,可以再着重说明。
5. 参考文档
[1] linux内核中的GPIO系统之(1):软件框架
[2] linux内核中的GPIO系统之(2):pin control subsystem
[3] Linux内核中的GPIO系统之(3):pin controller driver代码分析
[4] https://github.com/wowotechX/linux