ubootuboot 2020.04 Pinctrl子系统分析和使用
Posted ZHONGCAI0901
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ubootuboot 2020.04 Pinctrl子系统分析和使用相关的知识,希望对你有一定的参考价值。
相关文章
1.《【uboot】imx6ull uboot 2020.04源码下载和编译环境配置》
2.《【uboot】uboot 2020.04 DM驱动模式 – Demo体验》
3.《【uboot】uboot 2020.04 DM驱动模式 – 架构分析》
1. 前言
在许多soc内部都包含有pin控制器,通过pin控制器的寄存器,我们可以配置一个或者一组引脚的功能和特性。uboot提供一个类似linux的pinctrl子系统,目的是为了统一各soc厂商的pin脚管理。
2. Pinctrl子系统的功能简介
我们可以通过pinctrl子系统来设置引脚的复用、配置,可以将IO复用成GPIO、I2C等其它功能。图如下:
- 管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin。
- 管理这些pin的复用(Multiplexing)。对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,行程特定的功能。pin control subsystem要管理所有的pin group。
- 配置这些pin的特性。例如使能或关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength。
3. Pinctrl Uclass创建流程
下面是pinctrl uclass创建和绑定的过程如下:
[root.c]dm_extended_scan_fdt(gd->fdt_blob, false); // 在设备树中搜索设备并进行驱动匹配,然后bind。
[root.c]dm_scan_fdt(gd->fdt_blob, false); // 扫描设备树并绑定驱动程序
[root.c]dm_scan_fdt_node(gd->dm_root, gd->fdt_blob, 0, false); // 扫描设备树并绑定一个节点的驱动程序
[lists.c]lists_bind_fdt(gd->dm_root, offset_to_ofnode(0), NULL, false); // 绑定设备树节点,它会给绑定的设备树节点创建一个新udevice,并使用@parent作为其父设备。
[device.c]device_bind_with_driver_data(parent, entry, name, id->data, node, &dev); // 创建一个设备并且绑定到driver。
[device.c]device_bind_common(parent, drv, name, NULL, driver_data, node, 0, devp); // 创建一个设备并且绑定到driver。
[uclass.c]uclass_get(drv->id, &uc); // 根据ID获取一个uclass,如果它不存在就创建它。
[uclass.c]uclass_find(); // 根据id找到对应的uclass。
[uclass.c]uclass_add(); //在未找到的情况下,就会在列表中创建一个新的uclass。
[device.c]calloc(1, size); // 1:创建一个新的udevice并初始化; 2:给dev->platdata、dev->uclass_platdata、dev->parent_platdata分配空间
[device.c]list_add_tail(&dev->sibling_node, &parent->child_head); // 将新dev放到父节点的列表中
[uclass.c]uclass_bind_device(dev);// 将udevice与uclass进行绑定,设备连接到uclass的设备列表中。
[U_BOOT_DRIVER]drv->bind(dev); // device绑定成功后,就会调用drv->bind。
[U_BOOT_DRIVER]parent->driver->child_post_bind(dev); //在一个新的child被绑定后,就会调用parent driver的child_post_bind(dev);
[UCLASS_DRIVER]uc->uc_drv->post_bind(dev); // 在一个新设备绑定到这个uclass后被调用
经过以上的流程,它会将pinctrl的uclass、uclass_driver、udevice、driver进行绑定,绑定后的关系如下图所示:
4. Pinconfig Uclass创建流程
pinctrl设备绑定后,调用pinctrl uclass driver的post_bind
方法,来遍历pinctrl的子节点,并创建的了pinconfig uclass。具体的流程如下:
[UCLASS_DRIVER]uc->uc_drv->post_bind(dev); // 在一个新设备绑定到这个uclass后被调用
[pinctrl-uclass.c]pinctrl_post_bind(dev); // PINCTRL uclass正在post binding
[pinctrl-uclass.c]pinconfig_post_bind(dev); // dev = pinctrl(iomuxc@20e0000) 绑定它的子节点,以便在以后的dt解析期间外设设备可以轻松地在父设备中搜索。
[list.c]device_bind_driver_to_node(dev, "pinconfig", "i2c1grp", node, NULL); // 这将一个新设备绑定到一个给定设备树节点的驱动程序,只有在节点缺少"compatible"字符串时才需要这样做。
[device.c]device_bind_with_driver_data(parent, entry, name, id->data, node, &dev); // 创建一个设备并且绑定到driver。
[device.c]device_bind_common(parent, drv, name, NULL, driver_data, node, 0, devp); // 创建一个设备并且绑定到driver。
[uclass.c]uclass_get(drv->id, &uc); // 根据ID获取一个uclass,如果它不存在就创建它。
[uclass.c]uclass_find(); // 根据id找到对应的uclass。
[uclass.c]uclass_add(); //在未找到的情况下,就会在列表中创建一个新的uclass。
[device.c]calloc(1, size); // 1:创建一个新的udevice并初始化; 2:给dev->platdata、dev->uclass_platdata、dev->parent_platdata分配空间
[device.c]list_add_tail(&dev->sibling_node, &parent->child_head); // 将新dev放到父节点的列表中
[uclass.c]uclass_bind_device(dev);// 将udevice与uclass进行绑定,设备连接到uclass的设备列表中。
[U_BOOT_DRIVER]drv->bind(dev); // device绑定成功后,就会调用drv->bind。
[U_BOOT_DRIVER]parent->driver->child_post_bind(dev); //在一个新的child被绑定后,就会调用parent driver的child_post_bind(dev);
[UCLASS_DRIVER]uc->uc_drv->post_bind(dev); // 在一个新设备绑定到这个uclass后被调用
经过上面的流程的后,会将pinconfig的所有子节点绑定到parent pinctrl节点上,并且创建了pinconfig uclass。它们之间的联系如下:
在uboot的命令行,我们可以输入dm tree
来打印它们之间的关系。通过DM Tree我们可以很直观的看到pinctrl和pinconfig uclass它们之间的关系,具体如下:
5. I2C使用pinctrl功能
下面通过I2C使用pinctrl功能来举例,基本上分为三个步骤:
- Device Tree定义了pinctrl I2C相关定义。
- 根据Device Tree节点信息创建pinctrl uclass设备链表。
- 当申请I2C总线时,会先probe设备激活设备,这样就会使用pinctrl里面pinconfig信息初始化IO。
涉及到的代码流程如下:
[uclass.c]uclass_get_device_by_seq(UCLASS_I2C, i, &bus); // 通过序列号ID获取UCLASS_I2C总线device
[uclass.c]uclass_find_device_by_seq(id, seq, false, &dev); // 通过序列号ID获取UCLASS_I2C总线device
[uclass.c]uclass_get_device_tail(dev, ret, devp); // 判断是否有错误码,然后调用device_probe探测一个设备并激活它
[device.c]device_probe(dev); // 探测一个设备并激活它
[pinctrl_uclass.c]pinctrl_select_state(dev, "default");// 设置I2C IO为default状态,default为I2C功能。
[uclass.c]uclass_get_device_by_phandle_id(UCLASS_PINCONFIG, phandle, &config); // 通过phandle id获取I2C pinctrl的配置设备
[uclass.c]pinctrl_config_one(config); // 将从device tree获取到的I2C config数据,通过pinctrl driver的set_state方法设置到相关的寄存器上。
I2C使用pinctrl功能简单的功能框图如下:
Device Tree中I2C定义的信息如下:
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default", "gpio";
pinctrl-0 = <&pinctrl_i2c1>;
pinctrl-1 = <&pinctrl_i2c1_gpio>;
scl-gpios = <&gpio1 28 GPIO_ACTIVE_HIGH>;
sda-gpios = <&gpio1 29 GPIO_ACTIVE_HIGH>;
status = "okay";
mag3110@e {
compatible = "fsl,mag3110";
reg = <0x0e>;
};
};
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
pinctrl_i2c1_gpio: i2c1grp_gpio {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__GPIO1_IO28 0x1b8b0
MX6UL_PAD_UART4_RX_DATA__GPIO1_IO29 0x1b8b0
>;
};
6. Device Tree中phandle的使用
在Device Tree中解析时会用到phandle属性,我们需要了解一下它的使用。
Property name: phandle
Value type: <u32>
Description:
phandle属性为设备树中唯一的节点指定一个数字标识符。phandle属性值是被其它node使用,其它node通过phandle属性值与这个node进行关联。
Example:
参见以下devicetree引用:
pic@10000000 {
phandle = <1>;
interrupt-controller;
};
pic定义了一个phandle值为1,另一个设备节点可以用phandle值为1来引用pic节点:
another-device-node {
interrupt-parent = <1>;
};
注意:DTS中的大多数设备树不会包含显式的phandle属性。当DTS被编译成二进制DTB格式时,DTC工具会自动插入phandle属性。
8. 相关函数的使用
下面会具体分析所使用到的函数。
8.1 pinconfig_post_bind()函数分析
UCLASS_DRIVER(pinctrl) = {
.id = UCLASS_PINCTRL,
.post_bind = pinctrl_post_bind,
.flags = DM_UC_FLAG_SEQ_ALIAS,
.name = "pinctrl",
};
/**
* pinctrl_post_bind() - PINCTRL uclass正在post binding
*
* 在full pinctl的情况下,递归地将子节点pinconfig设备进行绑定。
*/
static int __maybe_unused pinctrl_post_bind(struct udevice *dev)
{
const struct pinctrl_ops *ops = pinctrl_get_ops(dev); // 获取pin control操作指针
/**
* 如果设置了set_state回调,我们假设这个pinctrl驱动程序就是完整的实现。
* 在这种情况下,应该绑定它的子节点,以便在以后的dt解析期间外设设备可以轻松地在父设备中搜索。
*/
if (ops->set_state)
return pinconfig_post_bind(dev);
return 0;
}
/**
* pinconfig_post_bind() - PINCONFIG uclass正在post binding
* 递归地将其子设备pinconfig进行绑定。
*
pinctrl 0 [ + ] imx6-pinctrl | | `-- iomuxc@20e0000
pinconfig 0 [ ] pinconfig | | |-- csi1grp
pinconfig 1 [ + ] pinconfig | | |-- enet1grp
pinconfig 2 [ + ] pinconfig | | |-- enet2grp
pinconfig 3 [ ] pinconfig | | |-- flexcan1grp
pinconfig 4 [ ] pinconfig | | |-- flexcan2grp
pinconfig 5 [ ] pinconfig | | |-- i2c1grp
*/
static int pinconfig_post_bind(struct udevice *dev)
{
bool pre_reloc_only = !(gd->flags & GD_FLG_RELOC);
const char *name;
ofnode node;
int ret;
// 遍历该设备pinctrl的子节点pinconfig
dev_for_each_subnode(node, dev) {
if (pre_reloc_only &&
!ofnode_pre_reloc(node))
continue;
// 如果该设备包含"compatible"属性,那么这个节点不是一个pin config节点。但它是一个普通device,所以这里跳过。
ofnode_get_property(node, "compatible", &ret);
if (ret >= 0)
continue;
// 如果该节点的属性包含"gpio-controller",则跳过。
if (ofnode_read_bool(node, "gpio-controller"))
continue;
// 如果不是FDT_ERR_NOTFOUND,则它不是一个pin config节点,直接返回。
if (ret != -FDT_ERR_NOTFOUND)
return ret;
// 获取该节点的名称
name = ofnode_get_name(node);
ret = device_bind_driver_to_node(dev, "pinconfig", name, node, NULL);
}
return 0;
}
8.2 device_bind_driver_to_node()函数分析
/**
* device_bind_driver_to_node() - 将节点设备绑定到driver
*
* 这将一个新设备绑定到一个给定设备树节点的驱动程序,只有在节点缺少"compatible"字符串时才需要这样做。
*/
int device_bind_driver_to_node(struct udevice *parent, const char *drv_name,
const char *dev_name, ofnode node,
struct udevice **devp)
{
struct driver *drv;
int ret;
// 根据driver name查找对应的驱动程序。
drv = lists_driver_lookup_name(drv_name); // drv_name = "pinconfig"
/**
* device_bind_with_driver_data() - 创建一个设备并且绑定到driver。
* 设置一个新device连接到driver,在这种情况下,驱动通过提供driver_data的匹配表与设备匹配。
* 一旦绑定,设备就存在,但在调用 device_probe() 之前尚未激活。
*/
ret = device_bind_with_driver_data(parent, drv, dev_name, 0 /* data */, node, devp); // parent = pinctrl, drv = pinconf_drv, dev_name= "i2c1grp"、"csi1grp"...
return ret;
}
/**
* device_bind_with_driver_data() - 创建一个设备并且绑定到driver。
* 设置一个新device连接到driver,在这种情况下,驱动通过提供driver_data的匹配表与设备匹配。
* 一旦绑定,设备就存在,但在调用 device_probe() 之前尚未激活。
*/
int device_bind_with_driver_data(struct udevice *parent,
const struct driver *drv, const char *name,
ulong driver_data, ofnode node,
struct udevice **devp)
{
return device_bind_common(parent, drv, name, NULL, driver_data, node,
0, devp);
}
8.3 pinctrl_select_state_full()函数分析
/**
* pinctrl_select_state_full() - 真正实现了pinctrl_select_state的功能,将设备设置为给定的状态
*/
static int pinctrl_select_state_full(struct udevice *dev, const char *statename) // statename: "default"
{
char propname[32]; /* long enough */
const fdt32_t *list;
uint32_t phandle;
struct udevice *config;
int state, size, i, ret;
/**
* pinctrl-names = "default", "gpio";
* statename = "default";
* 通过dev_read_stringlist_search查找"default"的index是0,所以这里state = 0。
*/
state = dev_read_stringlist_search(dev, "pinctrl-names", statename); // state = 0
/**
* 因为state = 0,所以这里propname = pinctrl-0。
* dev_read_prop函数功能是从设备节点读取一个属性,它会获取到pinctrl-0的属性value。
* list是指向属性的指针,如果没有找到,则为NULL。 list = &pinctrl_i2c1;
*/
snprintf(propname, sizeof(propname), "pinctrl-%d", state);
list = dev_read_prop(dev, propname, &size);
size /= sizeof(*list);
for (i = 0; i < size; i++) {
phandle = fdt32_to_cpu(*list++);
/**
* uclass_get_device_by_phandle_id() - 通过phandle id获取一个uclass设备
*/
ret = uclass_get_device_by_phandle_id(UCLASS_PINCONFIG, phandle,
&config);
ret = pinctrl_config_one(config);
}
return 0;
}
/**
* pinctrl_select_state() - 将设备设置为给定的状态
*
*/
int pinctrl_select_state(struct udevice *dev, const char *statename)
{
if (!ofnode_valid(dev->node))
return 0;
if (pinctrl_select_state_full(dev, statename))
return pinctrl_select_state_simple(dev);
return 0;
}
8.4 uclass_get_device_by_phandle_id()函数分析
/**
* uclass_get_device_by_phandle_id() - 通过phandle id获取一个uclass设备
*
* 这将在uclass中的设备中搜索具有给定phandle id的设备。
*
* DTS中的大多数设备树不会包含显式的phandle属性。当DTS被编译成二进制DTB格式时,DTC工具会自动插入phandle属性。
*
* uclass_get_device_by_phandle_id(UCLASS_PINCONFIG, phandle, &config);
*/
int uclass_get_device_by_phandle_id(enum uclass_id id, uint phandle_id,
struct udevice **devp)
{
struct udevice *dev;
struct uclass *uc;
int ret;
ret = uclass_get(id, &uc); // 获取UCLASS_PINCONFIG
/**
遍历UCLASS_PINCONFIG下所有的设备,并且匹配它的phandle id。
pinctrl 0 [ + ] imx6-pinctrl | | `-- iomuxc@20e0000
pinconfig 0 [ ] pinconfig | | |-- csi1grp
pinconfig 1 [ + ] pinconfig | | |-- enet1grp
pinconfig 2 [ + ] pinconfig | | |-- enet2grp
pinconfig 3 [ ] pinconfig | | |-- flexcan1grp
pinconfig 4 [ ] pinconfig | | |-- flexcan2grp
pinconfig 5 [ ] pinconfig | | |-- i2c1grp
*/
uclass_foreach_dev(dev, uc) {
uint phandle;
phandle = dev_read_phandle(dev); // 获取当前设备在device tree node的phandle id
// 比较phandle id,是否和目标的一致。
if (phandle == phandle_id) {
*devp = dev;
// 判断是否有错误码,然后调用device_probe探测一个设备并激活它
return uclass_get_device_tail(dev, ret, devp);
}
}
return -ENODEV;
}
8.5 uclass_get_device_tail()函数分析
/**
* uclass_get_device_tail() - 判断是否有错误码,然后调用device_probe探测一个设备并激活它
*/
int uclass_get_device_tail(struct udevice *dev, int ret, struct udevice **devp)
{
if (ret)
return ret;
// 探测一个设备并激活它
ret = device_probe(dev);
if (ret)
return ret;
*devp = dev;
return 0;
}
8.6 device_probe()函数分析
/**
* device_probe() - 探测一个设备并激活它
*
* 激活一个设备以便它可以随时使用。首先探查它的所有父节点。
*/
int device_probe(struct udevice *dev)
{
const struct driver *drv;
int ret;
int seq;
// 检测该device是否已经激活,已激活就直接返回。
if (dev->flags & DM_FLAG_ACTIVATED)
return 0;
drv = dev->driver; // 获取该设备对应的driver
/**
* device_ofdata_to_platdata() - 设备读取平台数据
*
* 读取设备的平台数据(通常是从设备树中),以便提供探测设备所需的信息。
*/
ret = device_ofdata_to_platdata(dev);
// 如果该设备存在parent,那么先probe parent设备,确保所有的parent dev都被probed。
if (dev->parent) {
ret = device_probe(dev->parent);
if (dev->flags & DM_FLAG_ACTIVATED)
return 0;
}
/**
* uclass_resolve_seq() - 解析device的序列号
*
* 在"dev->seq = -1"时,然后"dev->req_seq = -1"代表自动分配一个序列号,"dev->req_seq > 0"代表分配
* 指定的序列号。如果请求的序列号正在使用中,那么该设备将被分配另一个序列号。
*
* 注意,该函数不会改变设备的seq值,dev->seq需要手动赋值修改。
*/
seq = uclass_resolve_seq(dev);
dev->seq = seq;
// 标记该设备处于激活状态。
dev->flags |= DM_FLAG_ACTIVATED;
//处理除root device的pinctrl之外的所有设备,对于pinctrl device不进行pinctrl的设置,因为设备可能还没有被probed。
if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
pinctrl_select_state(dev, "default");
/**
* uclass_pre_probe_device() - 处理一个即将被probed的设备
*
* 先执行uclass需要的任何预处理,然后才可以探测它。这包括uclass的pre-probe()方法和父uclass的child_pre_probe()方法。
*/
ret = uclass_pre_probe_device(dev);
if (dev->parent && dev->parent->driver->child_pre_probe) {
ret = dev->parent->driver->child_pre_probe(dev);
}
// 只处理具有有效ofnode的设备
if (dev_of_valid(dev) && !(devubootuboot 2020.04 DM驱动模式 -- Demo体验
图表java 24年发展历史及长期支持jdk版本(up to 2020.04)