相关文章
- 《【Linux】基于IMX6ULL平台Pinctrl架构分析总结(一)-- Pin Controller注册过程》
- 《【Linux】基于IMX6ULL平台Pinctrl架构分析总结(二)-- Client Device使用过程》
1. 前言
在上一篇文章已经介绍了Pinctrl子系统中Pin Controller注册过程《【Linux】基于IMX6ULL平台Pinctrl架构分析总结(一)-- Pin Controller注册过程》,本篇文章主要是介绍Client Device是如何设置Pin的状态。在Device Tree中,Pinctrl主要分为2个部分:Pin Controller和Clinet Device。Device可能会有多个状态,不同的状态下,Pin的状态的作用可能不相同。I2C1 Client Device只有一个default
状态,它对应配置pinctrl-0
。它的配置pinctrl-0
指向了Pin Controller的pinctrl_i2c1
,通过这些配置将Pin设置I2C功能。
下面的截图是基于IMX6ULL平台Pinctrl I2C1加载后,总结的结构体之间大致的关系,方便我们理解Pinctrl子系统Client Device是如何使用的。后面会详细的一步步分析代码,来更加深刻的理解这个过程。
2. 源码分析
源码分析主要关注在I2C1设备是如何使用Pinctrl子系统来设置Pin状态,I2C1就是Pinctrl子系统中的Client Device。我们都知道设备注册成功之后调用platform_driver的probe函数,但在调用之前会先绑定Driver的Pin到默认状态。在这里也就是在加载I2C1驱动时,会先设置Pin为I2C功能。
Path:Kernel\drivers\base\dd.c
在设备驱动匹配成功后,会先调用really_probe
。pinctrl_bind_pins
函数会将I2C1的pin设置为I2C功能,然后调用I2C驱动的probe函数dev->bus->probe
。
static int really_probe(struct device *dev, struct device_driver *drv)
{
...
/* 设备被加载时,在调用probe之前先绑定pin。 */
ret = pinctrl_bind_pins(dev);
/* 调用设备驱动对应的probe函数。 */
ret = dev->bus->probe(dev);
...
}
Path:Kernel\drivers\base\pinctrl.c
在Linux驱动中都会用struct device
来表示当前的设备,然后通过它的成员struct dev_pin_info
访问设备的pin相关信息。devm_pinctrl_get(dev)
函数会创建并初始化pinctrl,然后赋值给struct pinctrl *p
。它从Device Tree中解析并保存pin所有状态下的配置信息,以备后续使用。
int pinctrl_bind_pins(struct device *dev)
{
/* 给struct dev_pin_info申请空间,并将地址赋值给struct device中的pins */
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
/* 创建并初始化pinctrl */
dev->pins->p = devm_pinctrl_get(dev);
/* 通过name = default在dev->pins->p中查找对应的pinctrl_state */
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_DEFAULT);
/* 将pin设置成default状态,也就是将pin设置成I2C功能。 */
ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state);
return 0;
}
Path:Kernel\drivers\pinctrl\core.c
根据上面函数devm_pinctrl_get(dev)
,追踪到create_pinctrl(dev, NULL)
函数。在create_pinctrl函数中,会创建并初始化struct pinctrl,然后从devicetree中当前设备的pinctrl数据解析到dt_maps中。pinctrl_dt_to_map
会将解析的pin数据保存到全局的LIST_HEAD(pinctrl_maps)
,后面遍历它找到当前设备的maps。最后设备设置好后,将对应设备的pinctrl p添加到全局的LIST_HEAD(pinctrl_list)
链表中。
我们可以通过运行起来的设备,cat /sys/kernel/debug/pinctrl/pinctrl-handles
来查看当前的pinctrl。大致的架构如下:
static struct pinctrl *create_pinctrl(struct device *dev,
struct pinctrl_dev *pctldev)
{
struct pinctrl *p;
/* 创建并初始化struct pinctrl */
p = kzalloc(sizeof(*p), GFP_KERNEL);
p->dev = dev;
INIT_LIST_HEAD(&p->states);
INIT_LIST_HEAD(&p->dt_maps);
/* 从devicetree中当前设备的pinctrl数据解析到dt_maps中 */
ret = pinctrl_dt_to_map(p, pctldev);
/* 遍历pinctrl_maps中所有的map */
for_each_maps(maps_node, i, map) {
/* 对比设备的name,必须保证该map当前Device的。 */
if (strcmp(map->dev_name, devname))
continue;
if (pctldev &&
strcmp(dev_name(pctldev->dev), map->ctrl_dev_name))
continue;
/* 将对应驱动的map信息转换成setting,并且保存在pinctrl p中 */
ret = add_setting(p, pctldev, map);
}
/* 将对应设备的pinctrl p添加到全局的pinctrl_list链表中 */
list_add_tail(&p->node, &pinctrl_list);
return p;
}
Path:Kernel\drivers\pinctrl\devicetree.c
在pinctrl_dt_to_map
函数中,主要是解析Device Tree中I2C1的数据。通过pinctrl-name
找到对应的pinctrl-0
配置。在pinctrl-0
存放的是i2c1grp的phandle值。通过phandle ID就可以拿到存放在Pin作为I2C功能的配置数据,解析它并且存放到map中。
int pinctrl_dt_to_map(struct pinctrl *p, struct pinctrl_dev *pctldev)
{
struct device_node *np = p->dev->of_node;
/* 从当前设备中解析pinctrl-assert-gpios属性,I2C1设备并没有该属性跳过。 */
ret = dt_gpio_assert_pinctrl(p);
/* 循环遍历state ID,在I2C1中只有一个pinctrl-0。 */
for (state = 0; ; state++) {
/* 获取I2C1 pinctrl-0的属性 */
propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state);
prop = of_find_property(np, propname, &size);
list = prop->value; // 获取pinctrl-0属性的地址
size /= sizeof(*list); // 获取config的个数,i2c1这里只有一个
/* 获取pinctrl-names对应state */
ret = of_property_read_string_index(np, "pinctrl-names",
state, &statename);
/* For every referenced pin configuration node in it */
for (config = 0; config < size; config++) {
/* 从pinctrl-0获取phandle,这里phandle=0x2a */
phandle = be32_to_cpup(list++);
/* 通过phandle,找到i2c1grp设备node */
np_config = of_find_node_by_phandle(phandle);
/* 解析i2c1grp设备node */
ret = dt_to_map_one_config(p, pctldev, statename,
np_config);
}
}
return 0;
}
Path:Kernel\drivers\pinctrl\devicetree.c
在dt_to_map_one_config
函数中,通过get_pinctrl_dev_from_of_node
函数遍历pinctrldev_list
,找到以前注册的Pin Controller(iomuxc@20e0000)
。在struct pinctrl_dev
中保存有dt_node_to_map的方法,这也是Pin Controller注册时提供的方法,最终会调用imx实现的方法imx_dt_node_to_map生成映射表项。最后通过dt_remember_or_free_map
将映射表块存储起来,以备以后使用。
static int dt_to_map_one_config(struct pinctrl *p,
struct pinctrl_dev *hog_pctldev,
const char *statename,
struct device_node *np_config)
{
struct pinctrl_dev *pctldev = NULL;
struct device_node *np_pctldev;
const struct pinctrl_ops *ops;
/* np_pctldev这里指向的是i2c1grp node */
np_pctldev = of_node_get(np_config);
for (;;) {
/* 获取i2c1grp的parent node,iomuxc@20e0000 */
np_pctldev = of_get_next_parent(np_pctldev);
/* 从pinctrldev_list中对比iomuxc@20e0000 pin controller是否已经注册 */
pctldev = get_pinctrl_dev_from_of_node(np_pctldev);
if (pctldev)
break;
}
/* 调用pinctrl driver解析设备树节点,生成映射表项。 */
ops = pctldev->desc->pctlops;
ret = ops->dt_node_to_map(pctldev, np_config, &map, &num_maps);
/* 将映射表块存储起来,以备以后使用。 */
return dt_remember_or_free_map(p, statename, pctldev, map, num_maps);
}
Path:Kernel\drivers\pinctrl\freescale\pinctrl-imx.c
在imx_dt_node_to_map
函数中,主要是根据I2C1 group的数据来创建pinctrl_map。i2c1grp
里面保存了2个pin,但是这里确创建了3个pinctrl_map。因为map[0]
默认规定是group的相关信息,。map[1]
和map[2]
保存才是pin相关的配置数据。
static int imx_dt_node_to_map(struct pinctrl_dev *pctldev,
struct device_node *np,
struct pinctrl_map **map, unsigned *num_maps)
{
const struct group_desc *grp;
struct pinctrl_map *new_map;
/* 通过name查找group_desc, pctldev=iomuxc@20e0000,np->name=i2c1grp */
grp = imx_pinctrl_find_group_by_name(pctldev, np->name);
/* 从group中过去pin的数据,并统计需要pinctrl_map的个数 */
for (i = 0; i < grp->num_pins; i++) {
pin = &((struct imx_pin *)(grp->data))[i];
if (!(pin->conf.mmio.config & IMX_NO_PAD_CTL))
map_num++;
}
/* 给pinctrl_map申请空间 */
new_map = kmalloc_array(map_num, sizeof(struct pinctrl_map),
GFP_KERNEL);
/* 返回map表的地址和个数 */
*map = new_map;
*num_maps = map_num;
/* 根据np(i2c1grp)获取parent(iomuxc@20e0000) device node */
parent = of_get_parent(np);
/* new_map[0]是固定存放group type */
new_map[0].type = PIN_MAP_TYPE_MUX_GROUP;
/* name = iomuxc@20e0000 */
new_map[0].data.mux.function = parent->name;
/* name = i2c1grp */
new_map[0].data.mux.group = np->name;
/* create config map */
new_map++;
for (i = j = 0; i < grp->num_pins; i++) {
/* 获取imx_pin的pin相关信息 */
pin = &((struct imx_pin *)(grp->data))[i];
new_map[j].type = PIN_MAP_TYPE_CONFIGS_PIN;
/* 通过parent(iomuxc@20e0000)获取pin name */
new_map[j].data.configs.group_or_pin = pin_get_name(pctldev, pin->pin);
/* 获取该pin对应的config */
new_map[j].data.configs.configs = &pin->conf.mmio.config;
new_map[j].data.configs.num_configs = 1;
j++;
}
return 0;
}
Path:Kernel\drivers\pinctrl\devicetree.c
在dt_remember_or_free_map
函数中,i2c1grp
的3个pinctrl_map赋值新创建的struct pinctrl_dt_map
,然后添加到struct pinctrl
成员dt_maps
列表中。
static int dt_remember_or_free_map(struct pinctrl *p, const char *statename,
struct pinctrl_dev *pctldev,
struct pinctrl_map *map, unsigned num_maps)
{
struct pinctrl_dt_map *dt_map;
/* 初始化map表项common部分 */
for (i = 0; i < num_maps; i++) {
const char *devname;
devname = kstrdup_const(dev_name(p->dev), GFP_KERNEL);
map[i].dev_name = devname; //devnames = i2c@21a0000
map[i].name = statename; //statename = default
map[i].ctrl_dev_name = dev_name(pctldev->dev); //ctrl_dev_name = iomuxc@20e0000
}
/* 给pinctrl_dt_map申请空间并初始化,然后添加到pinctrl的dt_maps队列中. */
dt_map = kzalloc(sizeof(*dt_map), GFP_KERNEL);
dt_map->pctldev = pctldev;
dt_map->map = map;
dt_map->num_maps = num_maps;
list_add_tail(&dt_map->node, &p->dt_maps);
/* 将map添加到core.c的pinctrl_maps队列中 */
return pinctrl_register_map(map, num_maps, false);
}
Path:Kernel\drivers\pinctrl\core.c
在pinctrl_register_map
函数中,会创建一个struct pinctrl_maps
来保存当前设备的所有pinctrl_map
。然后将该设备的struct pinctrl_maps
添加到全局的LIST_HEAD(pinctrl_maps)
列表中,方便后面遍历pinctrl_maps就可以找到对应设备的map。
int pinctrl_register_map(const struct pinctrl_map *maps, unsigned num_maps,
bool dup)
{
struct pinctrl_maps *maps_node;
/* 检测map的是否有效 */
switch (maps[i].type) {
case PIN_MAP_TYPE_DUMMY_STATE:
break;
case PIN_MAP_TYPE_MUX_GROUP:
ret = pinmux_validate_map(&maps[i], i);
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
case PIN_MAP_TYPE_CONFIGS_GROUP:
ret = pinconf_validate_map(&maps[i], i);
break;
default:
return -EINVAL;
}
/* 将map添加到core.c的pinctrl_maps队列中 */
maps_node = kzalloc(sizeof(*maps_node), GFP_KERNEL);
maps_node->num_maps = num_maps;
maps_node->maps = maps;
list_add_tail(&maps_node->node, &pinctrl_maps);
return 0;
}
Path:Kernel\drivers\pinctrl\core.c
在add_setting
函数中,create_state
会创建一个struct pinctrl_state
来保存map里面信息到setting中。最后将当前设备的struct pinctrl_state
添加设备的struct pinctrl
的states list列表中,如下图:
static int add_setting(struct pinctrl *p, struct pinctrl_dev *pctldev,
const struct pinctrl_map *map)
{
struct pinctrl_state *state;
struct pinctrl_setting *setting;
/* 第一次加载设备驱动在p->states中未找到对应default name的state, 然后会调用creat_state进行添加。 */
state = find_state(p, map->name);
state = create_state(p, map->name);
/* 给pinctrl_setting申请空间 */
setting = kzalloc(sizeof(*setting), GFP_KERNEL);
/* type = PIN_MAP_TYPE_MUX_GROUP、PIN_MAP_TYPE_CONFIGS_PIN */
setting->type = map->type;
/* 从pinctrldev_list中对比name=iomuxc@20e0000,然后拿到pin controller device。 */
setting->pctldev =
get_pinctrl_dev_from_devname(map->ctrl_dev_name);
/* devnames = i2c@21a0000 */
setting->dev_name = map->dev_name;
switch (map->type) {
case PIN_MAP_TYPE_MUX_GROUP:
/* 根据map的信息,将group和function的name信息转换成存储位置信息保存到setting中。 */
ret = pinmux_map_to_setting(map, setting);
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
case PIN_MAP_TYPE_CONFIGS_GROUP:
/* 根据map的信息,将pin的name信息转换成存储位置信息保存到setting中,config信息直接赋值。 */
ret = pinconf_map_to_setting(map, setting);
break;
default:
ret = -EINVAL;
break;
}
/* 将pinctrl_setting添加到state->settings队列中 */
list_add_tail(&setting->node, &state->settings);
return 0;
}
Path:Kernel\drivers\pinctrl\pinmux.c
在pinmux_map_to_setting
函数中,通过Pinctrl核心结构体struct pinctrl_dev
获取pinmux复用的操作方法,然后将pin的mux复用信息保存到setting中。
int pinmux_map_to_setting(const struct pinctrl_map *map,
struct pinctrl_setting *setting)
{
struct pinctrl_dev *pctldev = setting->pctldev;
const struct pinmux_ops *pmxops = pctldev->desc->pmxops;
/* 通过function name(iomuxc@20e0000)查找在pinctrl_dev的pin_function_tree中的位置。 */
ret = pinmux_func_name_to_selector(pctldev, map->data.mux.function);
setting->data.mux.func = ret; // 并且将function(iomuxc@20e0000)位置保存到setting中
/* 获取iomuxc@20e0000 groups的name和number个数 */
ret = pmxops->get_function_groups(pctldev, setting->data.mux.func,
&groups, &num_groups);
/* group = i2c1grp,匹配iomuxc@20e0000 groups中是否有对应的i2c1grp string */
group = map->data.mux.group;
ret = match_string(groups, num_groups, group);
/* group = i2c1grp,匹配iomuxc@20e0000 groups中是否有对应的i2c1grp group的位置 */
ret = pinctrl_get_group_selector(pctldev, group);
setting->data.mux.group = ret; // 并且将group(i2c1grp)位置保存到setting中
return 0;
}
Path:Kernel\drivers\pinctrl\pinconf.c
在pinconf_map_to_setting
函数中,根据pin的name找到pin number,并保存到setting中。然后,将根据map中pin config信息保存到setting。
int pinconf_map_to_setting(const struct pinctrl_map *map,
struct pinctrl_setting *setting)
{
struct pinctrl_dev *pctldev = setting->pctldev;
switch (setting->type) {
case PIN_MAP_TYPE_CONFIGS_PIN:
/* 根据pin的name找到pin number,并保存到setting中 */
pin = pin_get_from_name(pctldev,map->data.configs.group_or_pin);
setting->data.configs.group_or_pin = pin;
break;
case PIN_MAP_TYPE_CONFIGS_GROUP:
/* I2C1未使用该类型,暂不分析。 */
pin = pinctrl_get_group_selector(pctldev,map->data.configs.group_or_pin);
setting->data.configs.group_or_pin = pin;
break;
default:
return -EINVAL;
}
/* 根据pin config信息保存到setting中 */
setting->data.configs.num_configs = map->data.configs.num_configs;
setting->data.configs.configs = map->data.configs.configs;
return 0;
}
Path:Kernel\drivers\pinctrl\core.c
在pinctrl_select_state
函数中,I2C1只有一个default
状态,这里选择并使能。Pin就会被设置为I2C功能。
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
{
if (p->state == state)
return 0;
return pinctrl_commit_state(p, state);
}
static int pinctrl_commit_state(struct pinctrl *p, struct pinctrl_state *state)
{
struct pinctrl_setting *setting, *setting2;
struct pinctrl_state *old_state = p->state;
p->state = NULL;
/* 对于new state应用它所有的settings */
list_for_each_entry(setting, &state->settings, node) {
switch (setting->type) {
case PIN_MAP_TYPE_MUX_GROUP:
ret = pinmux_enable_setting(setting);
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
case PIN_MAP_TYPE_CONFIGS_GROUP:
ret = pinconf_apply_setting(setting);
break;
default:
ret = -EINVAL;
break;
}
/* 将new state赋值给p->state,保存设备当前状态。 */
p->state = state;
return 0;
}
Path:Kernel\drivers\pinctrl\pinmux.c
在pinmux_enable_setting
函数中,获取pin group的数据,然后设置到对应的寄存器。
int pinmux_enable_setting(const struct pinctrl_setting *setting)
{
struct pinctrl_dev *pctldev = setting->pctldev;
const struct pinctrl_ops *pctlops = pctldev->desc->pctlops;
const struct pinmux_ops *ops = pctldev->desc->pmxops;
/* 根据setting中的group存储位置,在pctldev->pin_group_tree中找到对于的group所有的pin,如:i2c1grp */
ret = pctlops->get_group_pins(pctldev, setting->data.mux.group,
&pins, &num_pins);
/* 将所使用的pin都进行申请 */
for (i = 0; i < num_pins; i++) {
ret = pin_request(pctldev, pins[i], setting->dev_name, NULL);
}
/* 将所使用的pin的pin_desc中的mux_setting进行配置 */
for (i = 0; i < num_pins; i++) {
desc = pin_desc_get(pctldev, pins[i]);
desc->mux_setting = &(setting->data.mux);
}
/* 获取pin的配置数据,然后设置到对应的寄存器。 */
ret = ops->set_mux(pctldev, setting->data.mux.func,
setting->data.mux.group);
return 0;
}
Path:Kernel\drivers\pinctrl\pinconf.c
在pinconf_apply_setting
函数中,会获取对应pin的setting数据,并且设置到对应的寄存器。
int pinconf_apply_setting(const struct pinctrl_setting *setting)
{
struct pinctrl_dev *pctldev = setting->pctldev;
const struct pinconf_ops *ops = pctldev->desc->confops;
/* 设置对应pin的配置 */
switch (setting->type) {
case PIN_MAP_TYPE_CONFIGS_PIN:
ret = ops->pin_config_set(pctldev,
setting->data.configs.group_or_pin,
setting->data.configs.configs,
setting->data.configs.num_configs);
break;
case PIN_MAP_TYPE_CONFIGS_GROUP:
ret = ops->pin_config_group_set(pctldev,
setting->data.configs.group_or_pin,
setting->data.configs.configs,
setting->data.configs.num_configs);
break;
default:
return -EINVAL;
}
return 0;
}
Path:Kernel\drivers\pinctrl\freescale\pinctrl-imx.c
设备的配置最终会调用它来设置对应Pin的寄存器。
static int imx_pinconf_set(struct pinctrl_dev *pctldev,
unsigned pin_id, unsigned long *configs,
unsigned num_configs)
{
/* 将pin的配置设置到对应的寄存器 */
imx_pinconf_set_mmio(pctldev, pin_id, configs, num_configs);
}
3. 分析过程的笔记草图
下面的截图是分析过程做的笔记,可以参考一起看。