引用
1、Linux pinctrl子系统学习(一)
2、Linux pinctrl子系统学习(二)
1 Pinctrl子系统介绍
- 众所周知,ARM SoC提供了十分丰富的硬件接口,而接口物理上的表现就是一个个的pin(或者叫做pad, finger等)。为了实现丰富的硬件功能,SoC的pin需要实现复用功能,即单独的pin需要提供不同功能,例如,pin0既可以作为GPIO,可以也用于i2c的SCL,通过pin相关的复用寄存器来切换不同的功能。除此之外,软件还可以通过寄存器配置pin相关的电气特性,例如,上拉/下拉、驱动能力、开漏等。
- Linux kernel 3.0之前的内核,对于pin的功能配置都是通过目标板的配置文件(arch/arm/mach-*)来初始化的,这种配置方式比较繁琐,十分容易出现问题(例如,pin的功能配置冲突)。所以,Linux kernel 3.0之后,实现了DT的板级配置信息管理机制,大大改善了对于pin的配置方式,随之一起实现的就是pinctrl子系统。
- pinctrl子系统主要负责以下功能:
1、通过DTS配置的pin的功能;
2、对于pin实现复用功能;
3、配置pin的电器特性,例如,上拉/下拉、驱动能力、开漏等。; - 可见,pinctrl子系统地位相当于kernel全局的pin管理中心,kernel中所有需要pin资源的驱动、子系统都需要通过pinctrl子系统来申请、配置、释放。对于pin的操作来说,pinctrl子系统十分重要的。
3 Pinctrl子系统与consumer关系
-
从”pinctrl子系统关系图”中得知,linux kernel中的各种consumer调用了pinctrl子系统的功能。linux kernel中的统一设备驱动模型提供了driver 和device的绑定机制,一旦匹配就会调用driver 的probe函数。
-
而在调用probe函数时,consumer就会调用pinctrl子系统里的回调函数,进行pin的申请。consumer的probe函数可以通过devm_pinctrl_get获得pinctrl的句柄,再自行调用pinctrl_select_state设置pin state。
2 Pinctrl子系统的框架
- 驱动分层最终目的:消除复杂性。框架如上图所示,可以分为三层,分别为最顶层consumer,即各种需要使用到pin的一些功能外设,如spi,i2c,sdio等等。中间层为pinctrl-core,是完成底层驱动与上层之间的连接。而pinctrl-driver为最底层,直接控制相关寄存器。
2.1 pinctrl-core
- pinctrl-core抽象层主要的功能就是为Consumer提供访问pin的能力,即driver配置pin复用能、配置引脚的电气特性。
- 其实,对于pinctrl-core抽象层如何为上层提供服务完全不需要关心,此过程由Linux内核完成。那么,pinctrl-core如何完成与pinctrl-driver连接呢? pinctrl-core与pinctrl-driver是通过pin controller descriptor进行通信的。该结构定义如下:
/**
* 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
*/
struct pinctrl_desc {
/*pinctrl-driver属性*/
const char *name;
const struct pinctrl_pin_desc *pins;
unsigned int npins;
/*pinctrl-drive抽象接口*/
const struct pinctrl_ops *pctlops;
const struct pinmux_ops *pmxops;
const struct pinconf_ops *confops;
struct module *owner;
};
2.2 pinctrl-driver
- 实际上在bsp层,我们需要做的就是定义一个pinctrl_desc结构体,并将此结构体注册到pinctrl子系统中去,上层即可连接到我们所写的bsp层驱动。
- 在编写一个pinctrl_desc结构体之前,首先要清楚一下几个概念:pin,pin groups,pin configuration和pin multiplexing。
2.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;
};
2.2.2 Pin groups
- 在SoC系统中,有时需要将很多pin组合在一起,以实现特定的功能,例如SPI接口、I2C接口等。因此pin controller需要以group为单位,访问、控制多个pin,这就是pin groups。相应地,pinctrl-driver需要提供一些机制,来获取系统中到底有多少groups、每个groups包含哪些pins、等等。
- 在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(指针)中。
-
dt_node_to_map用于将device tree中的pin state信息转换为pin map,consumer driver在需要的时候,可以调用pinctrl_get/devm_pinctrl_get接口。pinctrl subsystem在pinctrl get的过程中,解析consumer device的dts node,找到相应的pin state,进行调用pinctrl driver提供的dt_node_to_map API,解析pin state并转换为pin map。
注:pin state -
consumer在某一状态下(如工作状态、休眠状态、等等),所使用的pin(pin group)、的function和configuration,是唯一确定的。就是说pin(pin group)以及相应的function和configuration的组合,可以确定一个设备的一个“状态”。consumer可以调用pinctrl subsystem提供的API(例如pinctrl_select_state),使自己的某个pin state生效。pinctrl subsystem进而调用pinctrl driver提供的各种回调函数,配置pin controller的硬件。
在设备树下定义的pin_state:
2.2.3 Pin configuration
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当前配置,保存在config指针中(配置的具体含义,只有pinctrl driver自己知道,下同)。
- pin_config_set,设置指定pin的配置(可以同时配置多个config,具体意义要由相应pinctrl driver解释)。
- pin_config_group_get、pin_config_group_set,获取或者设置指定pin group的配置项。剩下的是一些debug用的api。
2.2.4 Pin multiplexing
- 为了照顾不同类型的产品、不同的应用场景,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;
};
- 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的反操作。
- strict,为true时,说明该pin controller不允许某个pin作为gpio和其它功能同时使用。
3.1 设备树中的pin state
在device tree source 文件中可以在驱动节点中定义该驱动需要用到的pin配置。
device-node-name{
//定义该device自己的属性
pinctrl-names= "sleep","default";
pinctrl-0 = &xxx_state_sleep;
pinctrl-1 = &xxx_state_default;
}
- pinctrl-0 pinctrl-1 …表示了该设备的一个个状态,这里我们定义了两个pinctrl-0 pinctrl-1,数字0、1就是pinctrl-names中对应的字符串数组的index。其中pinctrl-0就是“sleep”状态,pinctrl-1就是“default”状态。而xxx_state_sleep,xxx_state_default,xxx_state_idel就是驱动具体的pin配置项了,需要在pinctrl设备节点处定义:
pinctrl@e01b0000{
xxx_state_ default:xxx_ default {
pins = "PC10", "PC11", "PC12", "PC13", "PC14", "PC15";
function = "spi0";
};
};
3.2 pinctrl关键词表
3.2.1 pin命名表
pin的命名遵循芯片datasheet上的命名。
在dts中使用关键词“pins”(不同厂商不同)后跟名字数组来定义需要使用哪些pin,
如:
spi0_0: spi0 {
pins = "PC10", "PC11", "PC12", "PC13", "PC14", "PC15";
function = "spi0";
}
3.2.2 MUX pin-function
相同的pin,可以通过设置Mux Function的方式改变IC内部连通性,使得相同的pin用作不同的功能,例如:
pio0_0: gpio0 {
pins = "PE0", "PE1", "PE2", "PE3";
function = "gpio";
}
spi2_0: spi2 {
pins = "PE0", "PE1", "PE2", "PE3";
function = "spi2";
};
3.2.3 Mux Group命名表
Mux Group的意义在于,将一些具有相同功能的pin作为一个集合group管理起来,方便consumer在需要配置多个管脚时,更加的简洁方便。
在dts中使用关键词“groups”后跟mux Group名字数组来定义需要使用哪些Mux group,如:
sd0 {
groups = "mfp2_8_7";
function = "sd0";
};
3.2.4 MUX Group-function
Mux Group所控制的pin,可以通过设置Mux Function的方式改变IC内部连通性,使得同一组pin用作不同的功能,例如:
jtag {
groups ="mfp2_8_7";
function = "jtag";
};
3.2.5 Pin drive
对于某些pin可以设置pin的驱动能力(即供电能力),可以通过配置pin的等级对pin的驱动能力进行配置。
在dts中使用关键词“xxx,drive”后跟驱动能力等级来pin用哪种驱动能力,如:
i2c0_0: i2c0
{
pins = "PB0", "PB1";
function = "i2c0";
amicro,drive = <xxx_PINCTRL_12_MA>;
amicro,pull = <xxx_PINCTRL_PULL_UP>;
};
3.2.6 Pin Pull Up/Down
在dts中使用关键词“xxx,pull”后跟上下拉数据定义指定的pin组使用哪种上下拉,如:
i2c0_0: i2c0 {
pins = "PB0", "PB1";
function = "i2c0";
amicro,drive = <xxx_PINCTRL_12_MA>;
amicro,pull = <xxx_PINCTRL_PULL_UP>;
};
表示将i2c0这两个pin上拉。
#define xxx_PINCTRL_NO_PULL 0
#define xxx_PINCTRL_PULL_UP 1
#define xxx_PINCTRL_PULL_DOWN 2
3.3 v3s的pin信息表
通过分析pinctrl-sunxi-v3s.c可以看到,v3s的所有pin信息被保存在了一个类型为自定义结构体xxx_pinctrl_desc的数组sunxi_v3s_pinctrl_data中。
static struct sunxi_pinctrl_desc sunxi_v3s_pinctrl_data = {
.pins = sunxi_v3s_pins, /*单个pin的信息表*/
.npins = ARRAY_SIZE(sunxi_v3s_pins), /*pin的总数*/
.banks = sunxi_v3s_banks, /*banks的分组信息*/
.nbanks = ARRAY_SIZE(sunxi_v3s_banks), /*banks分组数量*/
.nirqs = 16, /*外部中断线数量*/
};
单个pin的信息被集合到了一个类型为自定义结构体sunxi_desc_pin的sunxi_v3s_pins数组中去,分析结构体成员可以看看到底保存了什么内容。
struct sunxi_desc_pin {
struct pinctrl_pin_desc pin; /*pinctrl通用的结构体,保存pin的编号,名字,以及pinctrl设备私有数据,内核可以识别*/
struct sunxi_desc_function *functions; /*am780自定义结构体,与功能有关*/
};
struct sunxi_desc_function {
const char *name; /*功能名字*/
u8 muxval; /*datasheet上复用功能编号*/
};
可以看到,v3s的pin信息表所保存的数据包括
1、 pin的编号,如PA0的编号为0,PB0编号为16
2、 pin的名字,如“PA0”,“PE14”
3、 复用功能映射编号,从datasheet中获取,如PA0:gpio为0,adc1为2,spi0为3
这些信息的作用是与各自平台的pinctrl子系统的pinconf和pinmux这些底层回调函数有关,最终的目的是通过这些信息来操作相关寄存器(最底层操作)。