最近学习linux中GPIO subsystem和pinctrl subsystem相关知识,查阅了一些资料,在这里与大家分享一下。
主要参考了:
[http://www.wowotech.net/linux_kenrel/pin-control-subsystem.html] 文档。
pin control subsystem的软件框架图
pin control subsystem的主要功能包括:
- 管理系统中所有可以控制的pin。在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin。
- 管理这些pin的复用(Multiplexing)。对于SOC而言,其引脚除了配置成普通GPIO之外,若干个引脚还可以组成一个pin group,形成特定的功能。例如pin number是{ 0, 8, 16, 24 }这四个引脚组合形成一个pin group,提供SPI的功能。pin control subsystem要管理所有的pin group。
- 配置这些pin的特性。例如配置该引脚上的pull-up/down电阻,配置drive strength等。
pin control subsystem向其他driver提供的接口
普通driver调用pin control subsystem的主要目标是:
- 设定该设备的功能复用。设定设备的功能复用需要了解两个概念,一个是function,另外一个pin group。function是功能抽象,对应一个HW逻辑block,例如SPI0。虽然给定了具体的gunction
name,我们并不能确定其使用的pins的情况。例如:为了设计灵活,芯片内部的SPI0的功能可能引出到pin group { A8, A7, A6, A5 },也可能引出到另外一个pin group{ G4, G3, G2, G1 },但毫无疑问,这两个pin group不能同时active,毕竟芯片内部的SPI0的逻辑功能电路只有一个。 因此,只有给出function selector(所谓selector就是一个ID或者index)以及function的pin group selector才能进行function mux的设定。 - 设定该device对应的那些pin的电气特性。
同时为了表示device pin的不同状态(idle、sleep等),定义了pin control state的概念,device driver可以切换设备处于的状态。为了方便管理pin control state,使用一个pin control state holder的概念,用来管理一个设备的所有的pin control状态。因此普通driver调用pin control subsystem的接口从逻辑上将主要是:
- 获取pin control state holder的句柄。
- 设定pin control状态。
- 释放pin control state holder的句柄。
pin control state holder的定义如下:
struct pinctrl {
struct list_head node; //系统中的所有device的pin control state holder被挂入到了一个全局链表中
struct device *dev; //该pin control state holder对应的device
struct list_head states; //该设备的所有的状态被挂入到这个链表中
struct pinctrl_state *state; //当前的pin control state
struct list_head dt_maps; //mapping table
struct kref users; //reference count
};
系统中的每一个需要和pin control subsystem进行交互的设备在进行设定之前都需要首先获取这个句柄。而属于该设备的所有的状态都是挂入到一个链表中,链表头就是pin control state holder的states成员,一个state的定义如下:
struct pinctrl_state {
struct list_head node; //挂入链表头的节点
const char *name; //该state的名字
struct list_head settings; //属于该状态的所有的settings
};
一个pin state包含若干个setting,所有的settings被挂入一个链表中,链表头就是pin state中的settings成员,定义如下:
struct pinctrl_setting {
struct list_head node;
enum pinctrl_map_type type;
struct pinctrl_dev *pctldev;
const char *dev_name;
union {
struct pinctrl_setting_mux mux;
struct pinctrl_setting_configs configs;
} data;
};
当driver设定一个pin state的时候,pin control subsystem内部会遍历该state的settings链表,将一个一个的setting进行设定。这些settings有各种类型,定义如下:
enum pinctrl_map_type {
PIN_MAP_TYPE_INVALID,
PIN_MAP_TYPE_DUMMY_STATE,
PIN_MAP_TYPE_MUX_GROUP, //功能复用的setting
PIN_MAP_TYPE_CONFIGS_PIN, //设定单一一个pin的电气特性
PIN_MAP_TYPE_CONFIGS_GROUP, //设定单pin group的电气特性
};
有pin mux相关的设定(PIN_MAP_TYPE_MUX_GROUP),定义如下:
struct pinctrl_setting_mux {
unsigned group; //该setting所对应的group selector
unsigned func; //该setting所对应的function selector
};
有了function selector以及属于该functiong的roup selector就可以进行该device和pin mux相关的设定了。设定电气特性的settings定义如下:
struct pinctrl_map_configs {
const char *group_or_pin; //该pin或者pin group的名字
unsigned long *configs; //要设定的值的列表。这个值被用来写入HW
unsigned num_configs; //列表中值的个数
};
pinctrl 接口
devm_pinctrl_get和pinctrl_get
devm_pinctrl_get是Resource managed版本的pinctrl_get,系统会帮助我们管理并释放资源。这两个接口都是获取设备(设备模型中的struct device)的pin control state holder(struct pinctrl)。pin control state holder不是静态定义的,一般在第一次调用该函数的时候会动态创建。devm_pinctrl_put和pinctrl_put
devm_pinctrl_get
和pinctrl_get
获取句柄的时候申请了很多资源,在devm_pinctrl_put和pinctrl_put可以释放。需要注意的是多次调用get函数不会重复分配资源,只会reference count加一,在put中referrenct count减一,当count==0的时候才释放该device的pin control state holder持有的所有资源。pinctrl_lookup_state
根据state name在pin control state holder找到对应的pin control state。具体的state是各个device自己定义的,不过pin control subsystem自己定义了一些标准的pin control state,定义在pinctrl-state.h文件中:
#define PINCTRL_STATE_DEFAULT "default"
#define PINCTRL_STATE_IDLE "idle"
#define PINCTRL_STATE_SLEEP "sleep"
- pinctrl_select_state
设定一个具体的pin control state接口。
pinctrl subsystem和GPIO subsystem交互
- pinctrl_request_gpio。
该接口主要用来申请GPIO。GPIO也是一种资源,使用前应该request,使用完毕后释放。具体的代码如下:
int pinctrl_request_gpio(unsigned gpio) //这里传入的是GPIO 的ID
{
struct pinctrl_dev *pctldev;
struct pinctrl_gpio_range *range;
int ret;
int pin;
ret = pinctrl_get_device_gpio_range(gpio, &pctldev, &range); //A
if (ret) {
if (pinctrl_ready_for_gpio_range(gpio))
ret = 0;
return ret;
}
mutex_lock(&pctldev->mutex);
pin = gpio_to_pin(range, gpio); //将GPIO ID转换成pin ID
ret = pinmux_request_gpio(pctldev, range, pin, gpio); //B
mutex_unlock(&pctldev->mutex);
return ret;
}
A:根据GPIO ID找到该ID对应的pin control device(struct pinctrl_dev)和GPIO rang(pinctrl_gpio_range)。在core driver中,每个low level的pin controller device都被映射成一个struct pinctrl_dev,并形成链表,链表头就是pinctrldev_list。由于实际的硬件设计(例如GPIO block被分成若干个GPIO 的bank,每个bank就对应一个HW GPIO Controller Block),一个pin control device要管理的GPIO ID是分成区域的,每个区域用struct pinctrl_gpio_range来抽象,在low level 的pin controller初始化的时候(具体参考samsung_pinctrl_register的代码),会调用pinctrl_add_gpio_range将每个GPIO bank表示的gpio range挂入到pin control device的range list中(gpio_ranges成员)。pinctrl_gpio_range 的定义如下:
struct pinctrl_gpio_range {
struct list_head node;
const char *name;
unsigned int id; //GPIO chip ID
unsigned int base; //该range中的起始GPIO IDD
unsigned int pin_base; //在线性映射的情况下,这是起始的pin base
unsigned const *pins; //在非线性映射的时候,这是table是pin到GPIO的lookup table
unsigned int npins; //这个range有多少个GPIO引脚
struct gpio_chip *gc; //每个GPIO bank都是一个gpio chip,对应一个GPIO range
};
pin ID和GPIO ID有两种映射关系,一种是线性映射(这时候pin_base有效),也就是说,对于这个GPIO range,GPIO base ID是a,pin ID base是b,那么a<—>b,a+1<—>b+1,a+2<—>b+2,以此类推。对于非线性映射(pin_base无效,pins是有效的),我们需要建立一个lookup table,以GPIO ID为索引,可以找到对于的pin ID。
B:这里主要是进行复用功能的设定,毕竟GPIO也是引脚的一个特定的功能。pinmux_request_gpio函数的作用主要有两个,一个是在core driver中标记该pin已经用作GPIO了,这样,如果有模块后续request该资源,那么core driver可以拒绝不合理的要求。第二步就是调用底层pin controller driver的callback函数,进行底层寄存器相关的操作。
- pinctrl_free_gpio
有申请就有释放,这是pinctrl_request_gpio的逆函数。 - pinctrl_gpio_direction_input和pinctrl_gpio_direction_output
为已经指定为GPIO功能的引脚设定方向,输入或者输出。
pinctrl subsystem 和驱动模型的接口
- pinctrl_bind_pins
其代码如下:
int pinctrl_bind_pins(struct device *dev)
{
int ret;
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
dev->pins->p = devm_pinctrl_get(dev);
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_DEFAULT);
ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state);
dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_SLEEP);
dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_IDLE);
return 0;
}
struct device数据结构有一个pins的成员,它描述了和该设备相关的pin control的信息,定义如下:
struct dev_pin_info {
struct pinctrl *p; //device对应的pin control state holder
struct pinctrl_state *default_state; //缺省状态
struct pinctrl_state *sleep_state; //电源管理相关的状态
struct pinctrl_state *idle_state; //电源管理相关的状态
};
该函数主要执行了以下步骤:
- 调用devm_pinctrl_get获取该device对应的 pin control state holder句柄。
- 搜索default state,sleep state,idle state并记录在本device中。
- 将该设备设定为pin default state。
pinctrl subsystem 和device tree相关的接口
device tree模块主要是为 pin control subsystem提供pin mapping database的支持。这个database的每个entry用下面的数据结构表示:
struct pinctrl_map {
const char *dev_name; //使用这个mapping entry的设备名
const char *name; //该名字表示了该mapping entry
enum pinctrl_map_type type; //这个entry的mapping type
const char *ctrl_dev_name; //pin controller这个设备的名字
union {
struct pinctrl_map_mux mux;
struct pinctrl_map_configs configs;
} data;
};
- 通过device tree来建立pin mapping database。
pin mapping信息定义在dts中,主要包括两个部分,一个是定义在各个具体的device node中,另外一处是定义在pin controller的device node中。主要包括两个部分,一个是定义在各个具体的device node中,另外一处是定义在pin controller的device node中。
一个典型的device tree中的外设node定义如下:
device-node-name {
定义该device自己的属性
pinctrl-names = "sleep", "default";
pinctrl-0 = ;
pinctrl-1 = ;
};
对普通device的dts分析在函数pinctrl_dt_to_map中,代码如下:
int pinctrl_dt_to_map(struct pinctrl *p)
{
of_node_get(np);
for (state = 0; ; state++) { //(1)
/* Retrieve the pinctrl-* property */
propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state);
prop = of_find_property(np, propname, &size);
kfree(propname);
if (!prop)
break;
list = prop->value;
size /= sizeof(*list); //(2)
/* Determine whether pinctrl-names property names the state */
ret = of_property_read_string_index(np, "pinctrl-names", state, &statename); //(3)
if (ret < 0) {
/* strlen("pinctrl-") == 8 */
statename = prop->name + 8; //(4)
}
/* For every referenced pin configuration node in it */
for (config = 0; config < size; config++) { //(5)
phandle = be32_to_cpup(list++);
/* Look up the pin configuration node */
np_config = of_find_node_by_phandle(phandle); //(6)
/* Parse the node */
ret = dt_to_map_one_config(p, statename, np_config); //(7)
of_node_put(np_config);
if (ret < 0)
goto err;
}
/* No entries in DT? Generate a dummy state table entry */
if (!size) {
ret = dt_remember_dummy_state(p, statename); //(8)
if (ret < 0)
goto err;
}
}
return 0;
err:
pinctrl_dt_free_maps(p);
return ret;
}
上述函数主要完成了以下步骤:
- pinctrl-0 pinctrl-1 pinctrl-2……表示了该设备的一个个的状态,这里我们定义了两个pinctrl-0和pinctrl-1分别对应sleep和default状态。这里每次循环分析一个pin state。
- 代码执行到这里,size和list分别保存了该pin state中所涉及pin configuration phandle的数目以及phandle的列表读取从pinctrl-names属性中获取state name
- 如果没有定义pinctrl-names属性,那么我们将pinctrl-0 pinctrl-1 pinctrl-2……中的那个ID取出来作为state name
- 遍历一个pin state中的pin configuration list,这里的pin configuration实际应该是pin controler device node中的sub node,用phandle标识。
- 用phandle作为索引,在device tree中找他该phandle表示的那个pin configuration
- 分析一个pin configuration,具体下面会仔细分析
- 如果该设备没有定义pin configuration,那么也要创建一个dummy的pin state。
这里我们已经进入对pin controller node下面的子节点的分析过程了。分析一个pin configuration的代码如下:
static int dt_to_map_one_config(struct pinctrl *p, const char *statename,
struct device_node *np_config)
{
struct device_node *np_pctldev;
struct pinctrl_dev *pctldev;
const struct pinctrl_ops *ops;
int ret;
struct pinctrl_map *map;
unsigned num_maps;
/* Find the pin controller containing np_config */
np_pctldev = of_node_get(np_config);
for (;;) {
np_pctldev = of_get_next_parent(np_pctldev);//(1)
if (!np_pctldev || of_node_is_root(np_pctldev)) {
of_node_put(np_pctldev);
return -EPROBE_DEFER;
}
pctldev = get_pinctrl_dev_from_of_node(np_pctldev); //(2)
if (pctldev)
break; //(3)
/* Do not defer probing of hogs (circular loop) */
if (np_pctldev == p->dev->of_node) {
of_node_put(np_pctldev);
return -ENODEV;
}
}
of_node_put(np_pctldev);
/*
* Call pinctrl driver to parse device tree node, and
* generate mapping table entries
*/
ops = pctldev->desc->pctlops;
ret = ops->dt_node_to_map(pctldev, np_config, &map, &num_maps); //(4)
if (ret < 0)
return ret;
/* Stash the mapping table chunk away for later use */
return dt_remember_or_free_map(p, statename, pctldev, map, num_maps); //(5)
}
- 首先找到该pin configuration node对应的parent node(也就是pin controler对应的node),如果找不到或者是root node,则进入出错处理。
- 获取pin control class device 。
- 一旦找到pin control class device则跳出for循环。
- 调用底层的callback函数处理pin configuration node。这也是合理的,毕竟很多的pin controller bindings是需要自己解析的。
- 将该pin configuration node的mapping entry信息注册到系统中。
core driver和low level pin controller driver的接口规格
pin controller描述符。每一个特定的pin controller都用一个struct pinctrl_desc来抽象,具体如下:
struct pinctrl_desc {
const char *name;
struct pinctrl_pin_desc const *pins;
unsigned int npins;
const struct pinctrl_ops *pctlops;
const struct pinmux_ops *pmxops;
const struct pinconf_ops *confops;
struct module *owner;
};
pin controller描述符需要描述它可以控制多少个pin(成员npins),每一个pin的信息为何?(成员pins)。这两个成员就确定了一个pin controller所能控制的引脚的信息。
pin controller描述符中包括了三类操作函数:pctlops是一些全局的控制函数,pmxops是复用引脚相关的操作函数,confops操作函数是用来配置引脚的特性(例如:pull-up/down)。struct pinctrl_ops中各个callback函数的具体的解释如下:
call back函数 | 描述 |
---|---|
request | pin control core进行具体的复用设定之前需要调用该函数,主要是用来请底层的driver判断某个引脚的复用设定是否是OK的。 |
free | 是request的逆函数。调用request函数请求占用了某些pin的资源,调用free可以释放这些资源 |
get_functions_count | 就是返回pin controller支持的function的数目 |
get_function_name | 给定一个selector(index),获取指定function的name |
get_function_groups | 给定一个selector(index),获取指定function的pin groups信息 |
enable | enable一个function。当然要给出function selector和pin group的selector |
disable | enable的逆函数 |
gpio_request_enable | request并且enable一个单独的gpio pin |
gpio_disable_free | gpio_request_enable的逆函数 |
gpio_set_direction | 设定GPIO方向的回调函数 |
配置引脚的特性的struct pinconf_ops数据结构的各个成员定义如下:
call back函数 | 描述 |
---|---|
pin_config_get | 给定一个pin ID以及config type ID,获取该引脚上指定type的配置。 |
pin_config_set | 设定一个指定pin的配置 |
pin_config_group_get | 以pin group为单位,获取pin上的配置信息 |
pin_config_group_set | 以pin group为单位,设定pin group的特性配置 |
pin_config_dbg_parse_modify | debug接口 |
pin_config_dbg_show | debug接口 |
pin_config_group_dbg_show | debug接口 |
pin_config_config_dbg_show | debug接口 |
参考文档:
http://www.wowotech.net/linux_kenrel/pin-control-subsystem.html