<Linux开发>驱动开发 -之-pinctrl子系统

<Linux开发>驱动开发 -之-pinctrl子系统
交叉编译环境搭建:
<Linux开发> linux开发工具-之-交叉编译环境搭建

uboot移植可参考以下:
<Linux开发> -之-系统移植 uboot移植过程详细记录(第一部分)
<Linux开发> -之-系统移植 uboot移植过程详细记录(第二部分)
<Linux开发> -之-系统移植 uboot移植过程详细记录(第三部分)(uboot移植完结)

Linux内核及设备树移植可参考以下:
<Linux开发>系统移植 -之- linux内核移植过程详细记录(第一部分)
<Linux开发>系统移植 -之- linux内核移植过程详细记录(第二部分完结)

Linux文件系统构建移植参考以下:
<Linux开发>系统移植 -之- linux构建BusyBox根文件系统及移植过程详细记录
<Linux开发>系统移植 -之-使用buildroot构建BusyBox根文件系统

Linux驱动开发参考以下:
<Linux开发>驱动开发 -之-pinctrl子系统
<Linux开发>驱动开发 -之-gpio子系统
<Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的LED驱动

一、前言

Linux 驱动讲究驱动分离与分层,pinctrl 和 gpio 子系统就是驱动分离与分层思想下的产物,驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架;我们会分别介绍pinctrl子系统 和 gpio 子系统。这篇文章中主要先来讲解pinctrl子系统。本片文章主要是基于iMax6ull 实现,同时也会对比高通平台的对应实现。关于pinctrl子系统的简介,在kernel的“Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt”下对其有详细的解释。后续讲解都是基于使用设备树的前提下,并且是基于此前笔者系统移植的后的内核和文件系统。相关系统移植等文章见笔者其它文章介绍。

二、pinctrl

先根据官方文档了解一下什么是pinctrl。
参考文档Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt

2.1 pinctrl简介

控制引脚复用或配置参数(如上拉/下拉、三态、驱动强度等)的硬件模块被指定为引脚控制器。每个引脚控制器必须表示为设备树中的一个节点,就像任何其他硬件模块一样。
信号受引脚配置影响的硬件模块被指定为客户端设备。同样,每个客户端设备必须表示为设备树中的一个节点,就像任何其他硬件模块一样。

上面这两句,举个例子解释一下,如果要把两个IO 用作I2C通信,那么一个为SCK、一个为SDA;使用这两个IO,就需要先配置IO相关属性参数等,然后在I2C中才能正常使用。那么就分为两部分,一个是IO的配置,一个是模块的引用;IO配置需要有一个设备树节点对应,模块配置也要有一个设备树节点对应。
如下:
IO配置使用的设备树节点:

路径:arch\arm\boot\dts\imx6ull-water-emmc.dts
pinctrl_i2c1: i2c1grp {
			fsl,pins = <
				MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
				MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
			>;
		};

图2.1.1:
<Linux开发>驱动开发 -之-pinctrl子系统_第1张图片

在I2C设备模块中使用IO:

路径:arch\arm\boot\dts\imx6ull-water-emmc.dts
&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

	mag3110@0e {
		compatible = "fsl,mag3110";
		reg = <0x0e>;
		position = <2>;
	};

	fxls8471@1e {
		compatible = "fsl,fxls8471";
		reg = <0x1e>;
		position = <0>;
		interrupt-parent = <&gpio5>;
		interrupts = <0 8>;
	};
};

图2.1.1:
<Linux开发>驱动开发 -之-pinctrl子系统_第2张图片

2.2 Pin controller(引脚控制器)

在imax中,基于2.1小节的i2c1相关,一一解释一下;

路径:arch\arm\boot\dts\imx6ull-water-emmc.dts
&iomuxc {
.............................
	pinctrl_i2c1: i2c1grp {
				fsl,pins = <
					MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
					MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
				>;
			};
................................
};

有上述内容可看到,pinctrl_i2c1节点是在iomuxc 下的,我们再来看下iomuxc 的内容;

路径:arch\arm\boot\dts\imx6ull.dtsi
aips1: aips-bus@02000000 {
.............................
	iomuxc: iomuxc@020e0000 {
				compatible = "fsl,imx6ul-iomuxc";
				reg = <0x020e0000 0x4000>;
			};
................................
};

有上述内容可知,iomuxc其实是一个挂在aips-bus总线上的一个设备。

在imx6ull-water-emmc.dts的文件开始内容部分有“#include "imx6ull.dtsi” 和“#include ”,说明在设备文件中可以以c语言包含头文件的形式包含.dtsi文件和.h文件,在.dts设备树文件中也是可以引用c语言的头文件的。
图2.2.1:
<Linux开发>驱动开发 -之-pinctrl子系统_第3张图片
在IAMX系列的芯片中iomuxc是一个pin控制器,所以在IMax中使用pinctrl添加IO的配置时使用的格式如下:

pinctrl_XXX: XXXgrp {
			fsl,pins = <
				IO复用配置  IO电气属性配置
				IO复用配置  IO电气属性配置
			>;
		};

对于IMAX6ull的IO复用配置宏定义的使用可在 imx6ul-pinfunc.h 中查找。
由于:
imx6ull-water-emmc.dts包含imx6ull.dtsi,而imx6ull.dtsi又包含头文件#include “imx6ull-pinfunc.h”,然后imx6ull-pinfunc.h又包含头文件#include “imx6ul-pinfunc.h”。
图2.2.2:
<Linux开发>驱动开发 -之-pinctrl子系统_第4张图片
由上图内容可看出,MX6UL_PAD_UART4_TX_DATA这个IO引脚可复用8种不用的功能,其中一种就是用作i2c1的SCL;MX6UL_PAD_UART4_RX_DATA这个IO引脚也可复用8种不用的功能,其中一种就是用作i2c1的SDAL;也可参考“IMX6UL参考手册.pdf”中的描述,如下:
图2.2.3:
<Linux开发>驱动开发 -之-pinctrl子系统_第5张图片
以MX6UL_PAD_UART4_TX_DATA__I2C1_SCL解释其5个参数的意义。

#define MX6UL_PAD_UART4_TX_DATA__I2C1_SCL                         0x00B4 0x0340 0x05A4 0x2 0x1

这 5 个值的含义如下所示:

0x00B4:表示UART4_TX_DATA这个IO在iomuxc的基地址上的偏移,所以实际地址是 0x020E00B4h,与上图手册截图所示一致;也可通过本小节前iomuxc的设备树描述信息可知iomuxc的地址是0x020e0000;

0x0340:io的配置寄存器地址的偏移量,实际的conf_reg = 0x020e0000+0x0340=0x020e0340,这里只说明地址,实际的配置值为前面介绍的:0x4001b8b0 (如下代码所列)

pinctrl_i2c1: i2c1grp {
			fsl,pins = <
				MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
				MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
			>;
		};

具体配置值内容可参考手册内容:
图2.2.4:
<Linux开发>驱动开发 -之-pinctrl子系统_第6张图片
图2.2.5:
<Linux开发>驱动开发 -之-pinctrl子系统_第7张图片
0x05A4:input_reg 寄存器偏移地址,有些外设有 input_reg 寄存器,有 input_reg 寄存器的
外设需要配置 input_reg 寄存器。没有的话就不需要设置;对于i2c1的scl是需要的,所以对应地址需要实际填写;

图2.2.6:
<Linux开发>驱动开发 -之-pinctrl子系统_第8张图片
0x2:mux_reg 寄 存 器 值 , 在 这 里 就 相 当 于 设 置
IOMUXC_SW_MUX_CTL_PAD_UART4_TX_DATA 寄存器为 0x2,也即是设置 UART4_TX_DATA 这个 PIN 复用为 I2C1_SCL。(设置前面解释的第一个参数地址的值为0x2,其它值参考第一个参数介绍 图2.2.3);

0x1:input_reg 寄存器值,参考第3个参数的解释(图2.2.6)。

综上,对于复用的宏定义,厂商都在 imx6ul-pinfunc.h 中定义完成了,只需引用即可。重点需要驱动人员修改的值是conf_reg寄存器的值,具体值的设定可参考图2.2.4和图2.2.5,其它IO请参考官方手册。

2.3 Pinctrl client devices(客户端设备)

对于2.2小节介绍的i2c1的引脚配置,其使用的客户端当然是i2c1这个设备了。

路径:arch\arm\boot\dts\imx6ull-water-emmc.dts
&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

	mag3110@0e {
		compatible = "fsl,mag3110";
		reg = <0x0e>;
		position = <2>;
	};

	fxls8471@1e {
		compatible = "fsl,fxls8471";
		reg = <0x1e>;
		position = <0>;
		interrupt-parent = <&gpio5>;
		interrupts = <0 8>;
	};
};

在上述代码中,我们可以看到i2c1的设备树节点用到了pinctrl-names和pinctrl-0,而pinctrl_i2c1正式前面 引脚控制器iomuxc设备节点中描述的内容;还有的设备可能还有pinctrl-1、pinctrl-2、pinctrl-3等等;

pinctrl-names:“default”表示默认配置;
pinctrl-0:表示i2c1中使用的一个pin配置;如果有pinctrl-1、pinctrl-2,则表示有多个pin配置可选择,在USB等设备中比较常见有多个配置。

2.4 小结

对于使用pinctrl 设备树的配置主要就是两部分,第一部分:在iomuxc节点下创建设备使用的pinctrl配置io;第二部分:就是在对于设备下使用pinctrl配置。

三、pinctrl驱动分析

我们在第二节讲解了IO的pinctrl的新增,在kernel中有pinctrl相关代码与之对应。这就是 Linux 内核的 pinctrl 子系统;

3.1 找pinctrl驱动

在前面介绍的设备树节点中,IO所有的东西都已经准备好了,包括寄存器地址和寄存器值,Linux 内核相应的驱动文件就会根据这些值来做相应的初始化。
我们都知道,设备树节点 和驱动是通过compatible属性进行匹配的,又因为pinctrl_i2c1是在iomuxc设备下的,所以相当于查找iomuxc的compatible熟悉所匹配的驱动即可;
又:compatible = “fsl,imx6ul-iomuxc”;
在kernel的drivers目录中搜索"fsl,imx6ul-iomuxc";
图 3.1.1:
<Linux开发>驱动开发 -之-pinctrl子系统_第9张图片
找到的文件路径为:drivers\pinctrl\freescale\pinctrl-imx6ul.c

3.2 pinctrl驱动解释

主要解析的pinctrl驱动内容如下部分:

路径:drivers\pinctrl\freescale\pinctrl-imx6ul.c
static struct of_device_id imx6ul_pinctrl_of_match[] = {
	{ .compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info, },
	{ .compatible = "fsl,imx6ull-iomuxc-snvs", .data = &imx6ull_snvs_pinctrl_info, },
	{ /* sentinel */ }
};
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
	const struct of_device_id *match;
	struct imx_pinctrl_soc_info *pinctrl_info;

	match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);

	if (!match)
		return -ENODEV;

	pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;

	return imx_pinctrl_probe(pdev, pinctrl_info);
}

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,
};

图 3.2.1:
<Linux开发>驱动开发 -之-pinctrl子系统_第10张图片
第 326~330行:of_device_id结构体数组是与设备树匹配有关的内容,具体匹配相关知识读者可自行查阅资料,of_device_id里面保存着这个驱动文件的兼容性值,设备树中的 compatible 属性值会和 of_device_id 中的所有兼容性字符串比较,查看是否可以使用此驱动。imx6ul_pinctrl_of_match 结构体数组一共有两个兼容性字符串,分别为“fsl,imx6ul-iomuxc”和“fsl,imx6ull-iomuxc-snvs”,因此 iomuxc 节点与此驱动匹配,所以 pinctrl-imx6ul.c 会完成 I.MX6ULL 的 PIN 配置工作。

第 347~355 行,platform_driver 是平台设备驱动,platform_driver 是个结构体,有个 probe 成员变量。在这里大家只需要知道,当设备和驱动匹配成功以后 platform_driver 的 probe 成员变量所代表的函数就会执行,在 353 行设置 probe 成员变量为 imx6ul_pinctrl_probe 函数,因此在本章实验中 imx6ul_pinctrl_probe 这个函数就会执行,可以认为 imx6ul_pinctrl_probe 函数就是 I.MX6ULL 这个 SOC 的 PIN 配置入口函数。

imx6ul_pinctrl_probe 函数解析设备树流程如下:
图 3.2.2:
<Linux开发>驱动开发 -之-pinctrl子系统_第11张图片

在上图 3.2.2 中函数 imx_pinctrl_parse_groups 负责获取设备树中关于 PIN 的配置信息,也就是我们前面2.2小节分析的那 6 个 u32 类型的值。处理过程如下所示:

路径:drivers\pinctrl\freescale\pinctrl-imx.c
/*
 * Each pin represented in fsl,pins consists of 5 u32 PIN_FUNC_ID and
 * 1 u32 CONFIG, so 24 types in total for each pin.
 */
#define FSL_PIN_SIZE 24
#define SHARE_FSL_PIN_SIZE 20

static int imx_pinctrl_parse_groups(struct device_node *np,
				    struct imx_pin_group *grp,
				    struct imx_pinctrl_soc_info *info,
				    u32 index)
{
	int size, pin_size;
	const __be32 *list;
	int i;
	u32 config;

	dev_dbg(info->dev, "group(%d): %s\n", index, np->name);

	if (info->flags & SHARE_MUX_CONF_REG)
		pin_size = SHARE_FSL_PIN_SIZE;
	else
		pin_size = FSL_PIN_SIZE;
	/* Initialise group */
	grp->name = np->name;

	/*
	 * the binding format is fsl,pins = ,
	 * do sanity check and calculate pins number
	 */
	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;
	}

	/* we do not check return since it's safe node passed down */
	if (!size || size % pin_size) {
		dev_err(info->dev, "Invalid fsl,pins property in node %s\n", np->full_name);
		return -EINVAL;
	}

	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++);
		u32 conf_reg;
		unsigned int pin_id;
		struct imx_pin_reg *pin_reg;
		struct imx_pin *pin = &grp->pins[i];

		if (!(info->flags & ZERO_OFFSET_VALID) && !mux_reg)
			mux_reg = -1;

		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;
		}

		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++);
		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);
	}

	return 0;
}


第 10 和 11 行:设备树中的 mux_reg 和 conf_reg 值会保存在 info 参数中,input_reg、mux_mode、input_val 和 config 值会保存在 grp 参数中。对应结构体如下:

路径:drivers\pinctrl\freescale\pinctrl-imx.h
/**
 * struct imx_pin_group - describes an IMX pin group
 * @name: the name of this specific pin group
 * @npins: the number of pins in this group array, i.e. the number of
 *	elements in .pins so we can iterate over that array
 * @pin_ids: array of pin_ids. pinctrl forces us to maintain such an array
 * @pins: array of pins
 */
struct imx_pin_group {
	const char *name;
	unsigned npins;
	unsigned int *pin_ids;
	struct imx_pin *pins;
};
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;
	unsigned int ngroups;
	struct imx_pmx_func *functions;
	unsigned int nfunctions;
	unsigned int flags;
	u32 grp_index;
};

第26行:获取设备的节点名;对应前面介绍的i2c1的话就是pinctrl_i2c1这个名称了。

第32行:绑定格式为fsl,pins=进行健全性检查并计算引脚数,我们这一看到,这就是为什么对于imax系列的pinctrl相关的配置都是以fsl,pins=这种格式了。这是因为imax的解析方法有关,不同的半导体厂商,提供驱动解析设备树是不同的,所以要注意这点区别;

第44行:计算pin的数量,就是配置几个io引脚;
第45行:为pin分配内存;
第46行:为pin分配内存;

第52-88行:主要是循环遍历每一个pin的配置信息,并逐一解析出来;

第53行:将list中的一行数据转为u32格式后,赋值给 mux_reg变量;

第74-78行:这几行就是解析出pin的配置的5个参数,分别是mux_reg、conf_reg、input_reg、mux_mode、input_val;

第81行: 获取conf_reg的配置值,对应fsl,pins= 中的第二个参数值;

对于解析设备树节点相关信息,主要就是上述几点;那么设备树解析完了之后,就需要把设备注册到内核,看下对应的函数。函数 pinctrl_register,此函数用于向 Linux 内核注册一个PIN 控制器,此函数原型如下:

路径:drivers\pinctrl\core.c
/**
 * pinctrl_register() - register a pin controller device
 * @pctldesc: descriptor for this pin controller
 * @dev: parent device for this pin controller
 * @driver_data: private pin controller data for this pin controller
 */
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
				    struct device *dev, void *driver_data)
{
}

参数pctldesc:pctldesc是pin控制器描述符,将其注册到pin控制子系统。

路径:include\linux\pinctrl\pinctrl.h
/**
 *struct pinctrl_desc-pin控制器描述符,将其注册到pin控制子系统
*@name:引脚控制器的名称
*@pins:一个引脚描述符数组,描述该引脚控制器处理的所有引脚
*@npins:数组中描述符的数量,通常只有上面pins字段的array_SIZE()
*@pctlops:pin控制操作vtable,为了支持引脚分组等全局概念,这是可选的。
*@pmxops:pinmux操作vtable,如果您在驱动程序中支持pinmuxing
*@confops:pin配置操作vtable,如果您在驱动程序中支持pin配置
*@owner:提供引脚控制器的模块,用于重新计数
*@num_custom_params:要从硬件描述中解析的驱动程序特定自定义参数的数量
*@custom_params:从硬件描述中解析的驱动程序特定自定义参数列表
*@custom_conf_items:关于如何在debugfs中打印@params的信息,其大小必须与@custom_params的大小相同,即@num_custom_params
 */
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;
#ifdef CONFIG_GENERIC_PINCONF
	unsigned int num_custom_params;
	const struct pinconf_generic_params *custom_params;
	const struct pin_config_item *custom_conf_items;
#endif
};

其中最重要是 pinctrl_ops 、pinmux_ops 、pinconf_ops 这三个结构体成员,其实这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN 的配置。pinctrl_desc 结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的 Linux 内核源码中已经把这些工作做完了。对于imax系列的芯片,半导体厂商则是在imx_pinctrl_probe 函数中实现这些成员变量的赋值。
imx_pinctrl_probe 函数如下:

路径:drivers\pinctrl\freescale\pinctrl-imx.c
int imx_pinctrl_probe(struct platform_device *pdev,
		      struct imx_pinctrl_soc_info *info)
{
	struct device_node *dev_np = pdev->dev.of_node;
	struct device_node *np;
	struct imx_pinctrl *ipctl;
	struct resource *res;
	struct pinctrl_desc *imx_pinctrl_desc;
	int ret, i;

	if (!info || !info->pins || !info->npins) {
		dev_err(&pdev->dev, "wrong pinctrl info\n");
		return -EINVAL;
	}
	info->dev = &pdev->dev;

	imx_pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*imx_pinctrl_desc),
					GFP_KERNEL);
	if (!imx_pinctrl_desc)
		return -ENOMEM;

	/* Create state holders etc for this driver */
	ipctl = devm_kzalloc(&pdev->dev, sizeof(*ipctl), GFP_KERNEL);
	if (!ipctl)
		return -ENOMEM;

	info->pin_regs = devm_kmalloc(&pdev->dev, sizeof(*info->pin_regs) *
				      info->npins, GFP_KERNEL);
	if (!info->pin_regs)
		return -ENOMEM;

	for (i = 0; i < info->npins; i++) {
		info->pin_regs[i].mux_reg = -1;
		info->pin_regs[i].conf_reg = -1;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	ipctl->base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(ipctl->base))
		return PTR_ERR(ipctl->base);

	if (of_property_read_bool(dev_np, "fsl,input-sel")) {
		np = of_parse_phandle(dev_np, "fsl,input-sel", 0);
		if (np) {
			ipctl->input_sel_base = of_iomap(np, 0);
			if (IS_ERR(ipctl->input_sel_base)) {
				of_node_put(np);
				dev_err(&pdev->dev,
					"iomuxc input select base address not found\n");
				return PTR_ERR(ipctl->input_sel_base);
			}
		} else {
			dev_err(&pdev->dev, "iomuxc fsl,input-sel property not found\n");
			return -EINVAL;
		}
		of_node_put(np);
	}

	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;
	}

	ipctl->info = info;
	ipctl->dev = info->dev;
	platform_set_drvdata(pdev, ipctl);
	ipctl->pctl = pinctrl_register(imx_pinctrl_desc, &pdev->dev, ipctl);
	if (!ipctl->pctl) {
		dev_err(&pdev->dev, "could not register IMX pinctrl driver\n");
		return -EINVAL;
	}

	dev_info(&pdev->dev, "initialized IMX pinctrl driver\n");

	return 0;
}

在函数中:
第9行:定义pinctrl_desc 结构体变量imx_pinctrl_desc;
第18-19行:为imx_pinctrl_desc变量申请内存;
第60-66行:完成对pinctrl_desc 结构体变量imx_pinctrl_desc的成员赋值;
第77行:将imx_pinctrl_desc变量带入,注册pin控制器;

pinctrl_ops结构体:

路径:include\linux\pinctrl\pinctrl.h
/**
*struct-pinctrl_ops-全局引脚控制操作,由引脚控制器驱动程序实现。
*@get_groups_count:返回注册的组总数。
*@get_group_name:返回pin组的组名
*@get_group_pins:返回某个组选择器pins对应的pin数组,数组大小以num_pins为单位
*@pin_dbg_show:可选的debugfs显示挂钩,它将为debugfs中的某个引脚提供每个设备的信息
*@dt_node_to_map:解析设备树“pin configuration node”,并为其创建映射表条目。这些条目通过@map和
*@num_maps输出参数。此功能是可选的,对于不支持设备树的pinctrl驱动程序,可以省略此功能。
*@dt_free_map:通过@dt_node_to_map创建的自由映射表条目。必须释放顶级@map指针,以及映射表项本身的任何动态分配的成员。此功能是可选的,对于不支持设备树的pinctrl驱动程序,可以省略此功能。
*/
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);
};

pinmux_ops 结构体:

路径:include\linux\pinctrl\pinmux.h
/**

*struct pinmux_ops-pinmux操作,由支持pinmuxing的pin控制器驱动程序实现

*@request:由核心调用,看看是否可以为muxing提供某个引脚。这是由核心调用的,以在选择函数中的任何实际mux设置之前获取引脚。允许驾驶员返回一个否定的错误代码来回答“否”

*@free:request()回调的反向函数,在被请求后释放一个pin

*@get_functions_count:返回此pinmux驱动程序中可用的可选命名函数数

*@get_function_name:返回muxing选择器的函数名,核心调用它来确定它应该将某个设备映射到哪个mux设置

*@get_function_groups:返回连接到某个函数选择器的组名称数组(依次引用引脚)。组名可以与通用@pinctrl_ops一起使用,以检索受影响的实际管脚。将在@groups中返回适用的组,并在@num_groups中返回组数

*@set_mux:使用某个引脚组启用某个复用功能。驱动程序不需要弄清楚启用此功能是否与该组中引脚的其他使用冲突,此类冲突由引脚复用器子系统处理。@func_selector选择某个函数,而@group_selector选择要使用的某组引脚。在简单控制器上,可以忽略后一个参数

*@gpio_request_enable:请求并启用某个引脚上的gpio。只有当你可以将每个引脚单独复用为GPIO时,才能实现这一点。受影响的GPIO范围与偏移(引脚编号)一起传递到特定GPIO范围-功能选择器和引脚组与此正交,但核心将确保引脚不会碰撞。

*@gpio_disable_free:释放某个引脚上的gpio复用,与@gpio_request_enable相反

*@gpio_set_direction:由于控制器可能需要不同的配置,这取决于gpio是配置为输入还是输出,因此可以实现方向选择器功能作为需要引脚复用的gpio控制器的支持。

*/
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 * const 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);
};

pinconf_ops 结构体:

路径:include\linux\pinctrl\pinconf.h
/**

*struct-pinconf_ops-pin配置操作,由支持pin配置的驱动程序实现。

*@is_generic:对于想要使用通用接口的pin控制器,此标志告诉框架它是通用的。

*@pin_config_get:获取某个引脚的配置,如果请求的配置在该控制器上不可用,则应返回-ENOTSUPP,如果可用但已禁用,则返回-EINVAL

*@pin_config_set:配置单个引脚

*@pin_config_group_get:获取整个pin组的配置

*@pin_config_group_set:配置一个组中的所有引脚

*@pin_config_dbg_parse_modify:用于修改引脚配置的可选调试

*@pin_config_dbg_show:可选的debugfs显示挂钩,它将为debugfs中的某个引脚提供每个设备的信息

*@pin_config_group_dbg_show:可选的debugfs显示挂钩,它将为debugfs中的某个组提供每个设备的信息

*@pin_config_config_dbg_show:可选的debugfs显示钩子,它将解码并显示驱动程序的pin配置参数

*/
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的操作函数集合。

3.3 pinctrl分析小节

对于pinctrl子系统的分析,不再深入分析了;在上述分析中,pinctrl-imx6ul.c文件是针对imx6ul和imx6ull这两款芯片的 pinctrl驱动,其进而会调用pinctrl-imx.c中的相关函数,pinctrl-imx.c则是所有imx系列芯片共用的驱动;在pinctrl-imx.c中还会使用到“drivers\pinctrl\core.c” 和“include\linux\pinctrl\pinctrl.h”等,这些又是pinctrl子系统公共的文件。

所以对于半导体厂商来说,他们会基于自己的方式,实现设备树上pinctrl节点的书写格式 和 对于的节点驱动。
这里对于imax6ull来说:
设备树格式:fsl,pins = < xxx xxx>;
对于的解析驱动文件:drivers\pinctrl\freescale\pinctrl-imx6ul.c 和drivers\pinctrl\freescale\pinctrl-imx.c

四、新增LEDpinctrl

主要是基于pinctrl来新增LED的pin配置;
原理图如下:
在这里插入图片描述
在这里插入图片描述
本次实验使用的是imax6ull开发板,开发板上有一个LED连接 GPIO1_IO03 这个 PIN;
添加如下内容:

路径:arch\arm\boot\dts\imx6ull-water-emmc.dts
pinctrl_led: ledgrp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
			>;
		};

<Linux开发>驱动开发 -之-pinctrl子系统_第12张图片
虽然已经添加进设备树了,前面我们也分析了,要在其它设备中去使用使用这个pinctrl才行。
这个在后面讲完GPIO子系统后,整体验证时再接着看效果吧。

五、高通平台pinctrl

借助高通8155平台做个对比分析;

5.1 查看高通8155pinctrl

lagvm/LINUX/android/kernel/msm-5.4/arch/arm64/boot/dts/vendor/qcom/sm8150-pinctrl.dtsi
&soc {
	tlmm: pinctrl@3100000 {
		compatible = "qcom,sm8150-pinctrl";
		reg = <0x03100000 0x300000>,
		      <0x03500000 0x300000>,
		      <0x03900000 0x300000>,
		      <0x03D00000 0x300000>;
		reg-names = "west", "east", "north", "south";
		// ,
		interrupts =<GIC_SPI 209 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 210 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 211 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 212 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 213 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 214 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 215 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 216 IRQ_TYPE_LEVEL_HIGH>;
		gpio-ranges = <&tlmm 0 0 175>;
		gpio-controller;
		#gpio-cells = <2>;
		interrupt-controller;
		#interrupt-cells = <2>;
		wakeup-parent = <&pdc>;
		....
	}
}	

有设备树节点嘛可看出,对于高通8155平台来说,其使用的pin控制器的名称为:tlmm;

5.2 高通8155pinctrl格式

高通平台上,串行外设是在 qupv3控制器上的,所以在dtsi中找到一个串口模块的pinctrl的配置信息如下:

路径:lagvm/LINUX/android/kernel/msm-5.4/arch/arm64/boot/dts/vendor/qcom/sm8150-pinctrl.dtsi

qupv3_se10_2uart_pins: qupv3_se10_2uart_pins {
			qupv3_se13_4uart_pins: qupv3_se13_4uart_pins {
			qupv3_se13_default_ctsrtsrx:
				qupv3_se13_default_ctsrtsrx {
				mux {
					pins = "gpio43", "gpio44", "gpio46";
					function = "gpio";
				};

				config {
					pins = "gpio43", "gpio44", "gpio46";
					drive-strength = <2>;
					bias-pull-down;
				};
			};

			qupv3_se13_default_tx: qupv3_se13_default_tx {
				mux {
					pins = "gpio45";
					function = "gpio";
				};

				config {
					pins = "gpio45";
					drive-strength = <2>;
					bias-pull-up;
				};
			};

			qupv3_se13_ctsrx: qupv3_se13_ctsrx {
				mux {
					pins = "gpio43", "gpio46";
					function = "qup13";
				};

				config {
					pins = "gpio43", "gpio46";
					drive-strength = <2>;
					bias-disable;
				};
			};

			qupv3_se13_rts: qupv3_se13_rts {
				mux {
					pins = "gpio44";
					function = "qup13";
				};

				config {
					pins = "gpio44";
					drive-strength = <2>;
					bias-pull-down;
				};
			};

			qupv3_se13_tx: qupv3_se13_tx {
				mux {
					pins = "gpio45";
					function = "qup13";
				};

				config {
					pins = "gpio45";
					drive-strength = <2>;
					bias-pull-up;
				};
			};
		};

<Linux开发>驱动开发 -之-pinctrl子系统_第13张图片

可以看到qupv3_se13_4uart_pins节点下分别创建了多个不同的子节点,分别代表使用 配置不同的pin;在这个文件中的其它pinctrl节点的格式也是如此;这就是高通平台的形式,与NXP的IMX平台,在书写格式上就有差异了。

5.3 高通8155pinctrl使用

那么是如何如用的,全局搜索一下就知道了;找到如下内容:

路径:lagvm/LINUX/android/kernel/msm-5.4/arch/arm64/boot/dts/vendor/qcom/sm8150-qupv3.dtsi

/* 4-wire UART */
	qupv3_se13_4uart: qcom,qup_uart@c8c000 {
		compatible = "qcom,msm-geni-serial-hs";
		reg = <0xc8c000 0x4000>;
		reg-names = "se_phys";
		clock-names = "se-clk", "m-ahb", "s-ahb";
		clocks = <&gcc GCC_QUPV3_WRAP2_S3_CLK>,
			<&gcc GCC_QUPV3_WRAP_2_M_AHB_CLK>,
			<&gcc GCC_QUPV3_WRAP_2_S_AHB_CLK>;
		pinctrl-names = "default", "active", "sleep";
		pinctrl-0 = <&qupv3_se13_default_ctsrtsrx>,
				<&qupv3_se13_default_tx>;
		pinctrl-1 = <&qupv3_se13_ctsrx>, <&qupv3_se13_rts>,
						<&qupv3_se13_tx>;
		pinctrl-2 = <&qupv3_se13_ctsrx>, <&qupv3_se13_rts>,
						<&qupv3_se13_tx>;
		interrupts-extended = <&intc GIC_SPI 585 IRQ_TYPE_LEVEL_HIGH>,
				<&tlmm 46 0>;
		qcom,wrapper-core = <&qupv3_2>;
		qcom,wakeup-byte = <0xFD>;
		status = "disabled";
	};

<Linux开发>驱动开发 -之-pinctrl子系统_第14张图片
看上述代码,是不是和分析imx时是一样的,对应pinctrl来说引用都是使用pinctrl-x这样的形式的,pinctrl-names中的3个参数 对应的就是pinctrl-0、pinctrl-1和pinctrl-2,这样驱动通过pinctrl-names中的名字就能检索到pinctrl-x了。
对应引用pinctrl来说所有的设备都是一样的,不管是高通的设备还是NXP、还是rkxxxx等。

5.4 高通8155pinctrl驱动

设备树上的pinctrl格式差异自然会导致驱动文件的差异;按照驱动和设备树的匹配方式,在驱动目录下搜索tlmm节点下的compatible数据字段即可,找到内容如下:

路径:lagvm/LINUX/android/kernel/msm-5.4/drivers/pinctrl/qcom/pinctrl-sm8150.c

static const struct of_device_id sm8150_pinctrl_of_match[] = {
	{ .compatible = "qcom,sm8150-pinctrl", },
	{ },
};

static struct platform_driver sm8150_pinctrl_driver = {
	.driver = {
		.name = "sm8150-pinctrl",
		.of_match_table = sm8150_pinctrl_of_match,
	},
	.probe = sm8150_pinctrl_probe,
	.remove = msm_pinctrl_remove,
};

所以pinctrl-sm8150.c文件就是tlmm控制器的驱动文件了;与前面介绍的pinctrl-imx6ul.c文件是相同的功能,只是不同半导体厂商,各自的实现存在差异而以。

当内核驱动后,匹配设备树节点与驱动成功后,就会调用static int sm8150_pinctrl_probe(struct platform_device *pdev)函数,sm8150_pinctrl_probe()函数的内容和于前分析的是类似的,就是解析设备树节点,然后注册到内核。

--->sm8150_pinctrl_probe()   lagvm/LINUX/android/kernel/msm-5.4/drivers/pinctrl/qcom/pinctrl-sm8150.c
	--->msm_pinctrl_probe()  lagvm/LINUX/android/kernel/msm-5.4/drivers/pinctrl/qcom/pinctrl-msm.c
		--->devm_pinctrl_register()  lagvm/LINUX/android/kernel/msm-5.4/drivers/pinctrl/core.c
			--->pinctrl_register()   lagvm/LINUX/android/kernel/msm-5.4/drivers/pinctrl/core.c

整个流程和前面分析的基本一样,首先解析设备树中的节点,然后在注册。

同样,入3.2节中解释的会有一个Pin控制器描述符,描述符中有三个成员变量,是用来控制Pin的,如下图:
<Linux开发>驱动开发 -之-pinctrl子系统_第15张图片

六、pinctrl总结

经过上述分析,不同点则是,对于pinctrl节点的书写格式,因不同芯片厂商,会存在差异;由于pinctrl节点的书写格式差异,以及不同厂商方法不同,所以对于pin控制器的驱动程序也会由所不同;共同点则是,都是通过pinctrl_register()函数来注册;pin控制器都会包含三个结构体分别是pinctrl_ops、pinmux_ops和pinconf_ops结构体。

后会继续讲解gpio子系统,然后配置pinctrl子系统,来进行控制io,实现LED的控制驱动。

你可能感兴趣的:(Linux驱动开发,驱动开发,linux,arm开发)