最近在配置pinctrl时,配置了引脚复用寄存器的SION
位,配置如下图中的所示,0x4001b8b0
中的第30位表示SION位
按照个人理解,imx6ull在设备树中配置的pinctrl节点,后面所带的值应该为配置寄存器的值,而SION
位是复用寄存器的第5位
目前猜测应该是驱动部分做了某些判断,使得在设备树配置的0x4001b8b0
中的第30位表示复用寄存器的SION位
分析驱动源码的第一步肯定是要找到对应的驱动源码,我的方法是先查看设备树中的compatible
属性,再使用grep
命令找到对应的源文件。compatible
属性的值是对应驱动文件中of_match_table
数组的值,当两者相对应时,驱动和设备树节点就能匹配成功。
查看compatible属性
驱动源码下执行grep命令,其中drivers/pinctrl/freescale/pinctrl-imx6ul.c
即为驱动源文件。
grep "fsl,imx6ul-iomuxc" drivers/* -r
imx6ull的pinctrl驱动为一个标准的platform驱动,重点应该是platform驱动中的probe函数
static int __init imx6ul_pinctrl_init(void)
{
return platform_driver_register(&imx6ul_pinctrl_driver);
}
arch_initcall(imx6ul_pinctrl_init);
imx6ul_pinctrl_driver中包含了probe
函数、remove
函数和匹配驱动使用的of_match_table
。当驱动与设备匹配后,probe函数就会被调用,解析设备的属性并做一些初始化的操作。
static struct platform_driver imx6ul_pinctrl_driver = {
.driver = {
.name = "imx6ul-pinctrl",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(imx6ul_pinctrl_of_match),
},
.probe = imx6ul_pinctrl_probe,
.remove = imx_pinctrl_remove,
};
imx6ul_pinctrl_of_match
的定义
static struct of_device_id imx6ul_pinctrl_of_match[] = {
{ .compatible = "fsl,imx6ul-iomuxc", },
{ /* sentinel */ }
};
在imx6ul_pinctrl_probe
函数中调用了imx_pinctrl_probe
函数,该函数多了一个imx6ul_pinctrl_info
参数,该参数里头存放着与所有引脚的描述信息(包含每个引脚的编号与name),这应该是为每一款imx芯片的引脚做区分,将驱动与设备信息分离
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
return imx_pinctrl_probe(pdev, &imx6ul_pinctrl_info);
}
imx6ul_pinctrl_info
是imx_pinctrl_soc_info
结构体,该结构体中包含的信息如下
struct imx_pinctrl_soc_info {
struct device *dev;
const struct pinctrl_pin_desc *pins; // 引脚描述结构体(指向数组)
unsigned int npins; // 表示有多少个引脚
struct imx_pin_reg *pin_regs; // 引脚寄存器(指向数组)
struct imx_pin_group *groups; // 引脚组结构体(设备树中的fsl,pins属性代表一组)
unsigned int ngroups; // 有多少个组
struct imx_pmx_func *functions; // 表示iomux节点下有多少个子节点,一个子节点代表一个function
unsigned int nfunctions; // iomux节点下子节点个数
unsigned int flags;
u32 grp_index;
};
imx_pinctrl_probe
中主要实现的是为info
参数中的成员分配空间、创建一个imx_pinctrl_des
并调用pinctrl_register
注册到内核中去
int imx_pinctrl_probe(struct platform_device *pdev,
struct imx_pinctrl_soc_info *info)
{
......
struct imx_pinctrl *ipctl;
struct pinctrl_desc *imx_pinctrl_desc;
// 根据引脚的个数分配imx_pin_reg数组的空间
info->pin_regs = devm_kmalloc(&pdev->dev, sizeof(*info->pin_regs) *
info->npins, GFP_KERNEL);
if (!info->pin_regs)
return -ENOMEM;
......
imx_pinctrl_desc->name = dev_name(&pdev->dev);
imx_pinctrl_desc->pins = info->pins;
imx_pinctrl_desc->npins = info->npins;
imx_pinctrl_desc->pctlops = &imx_pctrl_ops;
imx_pinctrl_desc->pmxops = &imx_pmx_ops;
imx_pinctrl_desc->confops = &imx_pinconf_ops;
imx_pinctrl_desc->owner = THIS_MODULE;
// 解析设备树中传入的信息
ret = imx_pinctrl_probe_dt(pdev, info);
if (ret) {
dev_err(&pdev->dev, "fail to probe dt properties\n");
return ret;
}
// 调用pinctrl_register进行注册
ipctl->info = info;
ipctl->dev = info->dev;
platform_set_drvdata(pdev, ipctl);
ipctl->pctl = pinctrl_register(imx_pinctrl_desc, &pdev->dev, ipctl);
......
}
imx的pinctrl驱动中将iomux节点分出了functions
、groups
的概念。在imx_pinctrl_probe_dt
函数中将有所体现。
function指的是iomux下的子节点,其中imx6ul-evk指的就是一个function
。
驱动源码下来解析function的代码
// 获取子节点的个数
nfuncs = of_get_child_count(np);
if (nfuncs <= 0) {
dev_err(&pdev->dev, "no functions defined\n");
return -EINVAL;
}
// 为info中的functions分配内存
info->nfunctions = nfuncs;
info->functions = devm_kzalloc(&pdev->dev, nfuncs * sizeof(struct imx_pmx_func),
GFP_KERNEL);
group是指在iomux中所有function包含子节点的个数,也就是iomux的孙子节点的个数,图片中的pinctrl_pwm5
和pinctrl_pwm6
节点各表示一个groups(个人认为groups是指引脚的集合,根据group来将一个集合中的引脚分出不同的作用功能)
group解析的驱动源码
info->ngroups = 0;
// 遍历所有子节点,并对子节点下的子节点个数进行统计
for_each_child_of_node(np, child)
info->ngroups += of_get_child_count(child);
// 根据获得的孙子节点的个数给info中的groups成员分配内存空间
info->groups = devm_kzalloc(&pdev->dev, info->ngroups * sizeof(struct imx_pin_group),
GFP_KERNEL);
分配好好functions和groups的内存空间大小后,将对每一个functions进行解析
// 遍历每一个function,并调用imx_pinctrl_parse_functions来解析
for_each_child_of_node(np, child)
imx_pinctrl_parse_functions(child, info, i++);
imx_pinctrl_parse_functions
函数主要是遍历functions中的groups,并调用imx_pinctrl_parse_groups
函数来解析每一个groups
for_each_child_of_node(np, child) {
func->groups[i] = child->name;
// 在imx_pinctrl_probe_dt分配创建的groups数组,grp_index用于记录当前groups的位置
grp = &info->groups[info->grp_index++];
// 解析每一个groups
imx_pinctrl_parse_groups(child, grp, info, i++);
}
在分析groups的解析函数之前我们需要了解imx6ull在pinctrl中配置的引脚经过转化之后分别对应什么内容,如
MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x4001b8b0
MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07
是一个宏定义,在arch/arm/boot/dts/imx6ul-pinfunc.h有定义
#define MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x0038 0x02C4 0x0000 0x5 0x0
宏定义展开后,配置的引脚就变为:
0x0038 0x02C4 0x0000 0x5 0x0 0x4001b8b0
每一个数值代表一个32位的整数,每个数值表示的含义为:
mux_reg conf_reg input_reg mux_mode input_val conf_val
确定在设备树中使用多少个字节来表示一个引脚,imx6ull使用的是FSL_PIN_SIZE,即使用6个32位整数来表示一个引脚的配置
if (info->flags & SHARE_MUX_CONF_REG)
pin_size = SHARE_FSL_PIN_SIZE; // SHARE_FSL_PIN_SIZE=20byte 5个32位整数
else
pin_size = FSL_PIN_SIZE; // FSL_PIN_SIZE=24byte 6个32位整数
获取fsl,pins
属性的值,list是一个32位的指针,size为传入参数,表示该属性中包含多少个32位整数
list = of_get_property(np, "fsl,pins", &size);
if (!list) {
dev_err(info->dev, "no fsl,pins property in node %s\n", np->full_name);
return -EINVAL;
}
根据获取到的pin_size
和size
大小,计算引脚数并分配空间
grp->npins = size / pin_size;
grp->pins = devm_kzalloc(info->dev, grp->npins * sizeof(struct imx_pin),
GFP_KERNEL);
grp->pin_ids = devm_kzalloc(info->dev, grp->npins * sizeof(unsigned int),
GFP_KERNEL);
if (!grp->pins || ! grp->pin_ids)
return -ENOMEM;
解析每一个引脚的配置
for (i = 0; i < grp->npins; i++) {
// 复用寄存器的值
u32 mux_reg = be32_to_cpu(*list++);
......
if (info->flags & SHARE_MUX_CONF_REG) {
conf_reg = mux_reg;
} else {
conf_reg = be32_to_cpu(*list++);
if (!(info->flags & ZERO_OFFSET_VALID) && !conf_reg)
conf_reg = -1;
}
// 相邻引脚的mux_reg之间的偏移量差距为4,根据偏移量算出引脚的id值,引脚的id值pinctrl_imx6ul.c文件中定义
pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4;
pin_reg = &info->pin_regs[pin_id];
pin->pin = pin_id;
grp->pin_ids[i] = pin_id;
// 复用寄存器地址
pin_reg->mux_reg = mux_reg;
// 配置寄存器地址
pin_reg->conf_reg = conf_reg;
// 输入寄存器地址
pin->input_reg = be32_to_cpu(*list++);
// 配置寄存器的值
pin->mux_mode = be32_to_cpu(*list++);
// 输入寄存器的值
pin->input_val = be32_to_cpu(*list++);
/* SION bit is in mux register */
config = be32_to_cpu(*list++); // 获取配置寄存器的值,即在设备树中由用户指定的值
// IMX_PAD_SION的值为0x40000000 当我们再设备树中将配置寄存器的第30位配置为1时,在驱动源码中会将复用寄存器的SION位配置成1(果然和前面猜想的一样)
if (config & IMX_PAD_SION)
pin->mux_mode |= IOMUXC_CONFIG_SION;
// 记录配置寄存器的值
pin->config = config & ~IMX_PAD_SION;
dev_dbg(info->dev, "%s: 0x%x 0x%08lx", info->pins[pin_id].name,
pin->mux_mode, pin->config);
}
驱动源码,yyds!!!一点小记录,供大家参考学习。如果有哪里分析得不对的地方,欢迎关注我的微信公众号来一起交流学习。