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

<Linux开发>驱动开发 -之-gpio子系统
交叉编译环境搭建:
<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驱动

一、前言

我们介绍了pinctrl子系统,pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO,Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。接下来我们就来看看GPIO子系统。

二、gpio 子系统

在介绍pinctrl子系统时,我们知道pinctrl的设备树节点是用来初始化pin的;那么比如我们要把一个pin io作为普通的GPIO 用控制输出高低电平,或者读取GPIO的高低电平,应该如何实现呢?如控制LED灯的亮灭操作;这些是将IO作为GPIO来使用。由于GPIO功能是一个最常用而且是大部分io的共有的通用功能,所以在Linux中就产生了一个gpio子系统来管理这一类功能了。

2.1 GPIO在设备树中的形式

路径:arch\arm\boot\dts\imx6ull-water-emmc.dts

pinctrl_hog_1: hoggrp-1 {
			fsl,pins = <
				MX6UL_PAD_UART1_RTS_B__GPIO1_IO19	0x17059 /* SD1 CD */
				MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT	0x17059 /* SD1 VSELECT */
				MX6UL_PAD_GPIO1_IO09__GPIO1_IO09        0x17059 /* SD1 RESET */
			>;
		};

&usdhc1 {
	pinctrl-names = "default", "state_100mhz", "state_200mhz";
	pinctrl-0 = <&pinctrl_usdhc1>;
	pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
	pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
	cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
	keep-power-in-suspend;
	enable-sdio-wakeup;
	vmmc-supply = <®_sd1_vmmc>;
	status = "okay";
};

在上述设备树内容中,pinctrl_hog_1 是pinctrl节点,该节点是与热插拔有关的IO;如上述所列GPIO1_IO19,此引脚是用来检测SD的的热插拔的,当SD卡插入时,检测GPIO1_IO19为低电平,当SD卡拔出后,检测GPIO1_IO19为高电平;这样就可以通过检测GPIO的电平状态来判定是否有SD卡插入了。

上述内容usdhc1 节点是SD卡设备的总节点,所以该节点下需要描述SD卡的所有信息,因此用来检测SD卡的pinctrl节点pinctrl_hog_1需要被引用,上述代码中显然没有引用,别担忧,搜索一下会发现,其实在iomuxc节点下已经默认包含了pinctrl_hog_1 了,所以就不需要重复引用了。
<Linux开发>驱动开发 -之-gpio子系统_第1张图片
在usdhc1 节点下还有一行cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;这一行就是与GPIO子系统有关的信息了;
其中:
cd-gpios:表示SD卡对应接口的CD引脚;一般通用的有统一的名字,自己写驱动时也可以自定义的;
&gpio1 :表示GPIO1控制器;
19 :表示GPIO组下的第19个IO;
GPIO_ACTIVE_LOW:表示低电平有效,反之为高;

整体意思就是,usdhc1 节点下的cd-gpios属性用来检测SD的热插拔状态的,对应检测的就是GPIO1_IO19这个引脚,检测低电平有效,说明有SD卡插入。

2.2 GPIO节点

前面讲到cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;,其中gpio1 也是设备树中的一个节点,对应内容如下:

路径:arch\arm\boot\dts\imx6ull.dtsi
gpio1: gpio@0209c000 {
				compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
				reg = <0x0209c000 0x4000>;
				interrupts = ,
					     ;
				gpio-controller;
				#gpio-cells = <2>;
				interrupt-controller;
				#interrupt-cells = <2>;
			};

<Linux开发>驱动开发 -之-gpio子系统_第2张图片
从截图姐看出,处理gpio1,还有gpio2、gpio3、gpio4和gpio5;在imx6ull中就有5个GPIO控制器;当需要用哪个GPIO时选择对应的GPIO控制器就行了。

gpio1 节点信息描述了 GPIO1 控制器的所有信息,重点就是 GPIO1 外设寄存器基地址以及兼 容 属 性 。 关 于 I.MX 系 列 SOC 的 GPIO 控 制 器 绑 定 信 息 请 查 看 文 档Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt

第 505 行:设置 gpio1 节点的 compatible 属性有两个,分别为“fsl,imx6ul-gpio”和“fsl,imx35-gpio”,在 Linux 内核中搜索这两个字符串就可以找到 I.MX6UL 的 GPIO 驱动程序。

第 506 行:reg 属性设置了 GPIO1 控制器的寄存器基地址为 0X0209C000,大家可以打开《I.MX6ULL 参考手册》找到“Chapter 26:General Purpose Input/Output(GPIO)”章节第 26.5 小节,有如下图所示的寄存器地址表:
<Linux开发>驱动开发 -之-gpio子系统_第3张图片
从上图可以看出,GPIO1 控制器的基地址就是 0X0209C000,GPIO2 控制器的基地址就是 0X020A0000,依次类推。

第 509 行:“gpio-controller”表示 gpio1 节点是个 GPIO 控制器。
第 510 行:“#gpio-cells”属性和“#address-cells”类似,#gpio-cells 应该为 2,表示一共有
两个 cell,第一个 cell 为 GPIO 编号,比如“&gpio1 3”就表示 GPIO1_IO03。第二个 cell 表示
GPIO 极 性 , 如 果 为 0(GPIO_ACTIVE_HIGH) 的 话 表 示 高 电 平 有 效 , 如 果 为
1(GPIO_ACTIVE_LOW)的话表示低电平有效。

2.3 GPIO找驱动

围绕GPIO子系统来说,其实就是针对GPIO这一类设备,回归实际代码那就是GPIO驱动;由2.2小节中GPIO1控制器节点的compatible属性可知,对应的驱动程序会会匹配"fsl,imx6ul-gpio", "fsl,imx35-gpio",所以我们在驱动目录下搜一下这个字段就能搜到对应的驱动程序了;对于gpio2\gpio3\gpio4\gpio5来说compatible属性都是一样的,所以5个gpio控制器的驱动都是相同的。
会发现搜索fsl,imx6ul-gpio时会找不到,搜索fsl,imx35-gpio时能找到”drivers\gpio\gpio-mxc.c“,对于设备树节点匹配驱动来说compatible属性中只要由满足的即可,所以”drivers\gpio\gpio-mxc.c“就是对应IMX6ULL的GPIO驱动文件了。

路径:drivers\gpio\gpio-mxc.c

static const struct of_device_id mxc_gpio_dt_ids[] = {
	{ .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
	{ .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
	{ .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
	{ .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
	{ /* sentinel */ }
};

gpio-mxc.c 所在的目录为 drivers/gpio,打开这个目录可以看到很多芯片的 gpio 驱动文件, “gpiolib”开始的文件是 gpio 驱动的核心文件,如下图:
<Linux开发>驱动开发 -之-gpio子系统_第4张图片

2.4 GPIO驱动分析

对应驱动程序来说,当和设备树节点匹配成功后就会调用xxx_probe函数进行初始化的一些操作;在gpio-mxc.c文件中有如下内容:

路径:drivers\gpio\gpio-mxc.c

static struct platform_driver mxc_gpio_driver = {
	.driver		= {
		.name	= "gpio-mxc",
		.of_match_table = mxc_gpio_dt_ids,
	},
	.probe		= mxc_gpio_probe,
	.id_table	= mxc_gpio_devtype,
};

可以看出 GPIO 驱动也是个平台设备驱动,因此当设备树中的设备节点与驱动的of_device_id 匹配以后 probe 函数就会执行,在这里就是 mxc_gpio_probe 函数,这个函数就是I.MX6ULL 的 GPIO 驱动入口函数。,其内容如下:

路径:drivers\gpio\gpio-mxc.c

static int mxc_gpio_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct mxc_gpio_port *port;
	struct resource *iores;
	int irq_base;
	int err;

	mxc_gpio_get_hw(pdev);

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

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

	port->irq_high = platform_get_irq(pdev, 1);
	port->irq = platform_get_irq(pdev, 0);
	if (port->irq < 0)
		return port->irq;

	/* disable the interrupt and clear the status */
	writel(0, port->base + GPIO_IMR);
	writel(~0, port->base + GPIO_ISR);

	if (mxc_gpio_hwtype == IMX21_GPIO) {
		/*
		 * Setup one handler for all GPIO interrupts. Actually setting
		 * the handler is needed only once, but doing it for every port
		 * is more robust and easier.
		 */
		irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
	} else {
		/* setup one handler for each entry */
		irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
		irq_set_handler_data(port->irq, port);
		if (port->irq_high > 0) {
			/* setup handler for GPIO 16 to 31 */
			irq_set_chained_handler(port->irq_high,
						mx3_gpio_irq_handler);
			irq_set_handler_data(port->irq_high, port);
		}
	}

	err = bgpio_init(&port->bgc, &pdev->dev, 4,
			 port->base + GPIO_PSR,
			 port->base + GPIO_DR, NULL,
			 port->base + GPIO_GDIR, NULL, 0);
	if (err)
		goto out_bgio;

	port->bgc.gc.to_irq = mxc_gpio_to_irq;
	port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
					     pdev->id * 32;

	err = gpiochip_add(&port->bgc.gc);
	if (err)
		goto out_bgpio_remove;

	irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
	if (irq_base < 0) {
		err = irq_base;
		goto out_gpiochip_remove;
	}

	port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
					     &irq_domain_simple_ops, NULL);
	if (!port->domain) {
		err = -ENODEV;
		goto out_irqdesc_free;
	}

	/* gpio-mxc can be a generic irq chip */
	mxc_gpio_init_gc(port, irq_base);

	list_add_tail(&port->node, &mxc_gpio_ports);

	return 0;

out_irqdesc_free:
	irq_free_descs(irq_base, 32);
out_gpiochip_remove:
	gpiochip_remove(&port->bgc.gc);
out_bgpio_remove:
	bgpio_remove(&port->bgc);
out_bgio:
	dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err);
	return err;
}

第5行:定义一个设备树节点指针变量np;

第6行:定义一个结构体指针 port,结构体类型为 mxc_gpio_port。gpio-mxc.c 的重点工作就是维护 mxc_gpio_port,mxc_gpio_port 就是对 I.MX6ULL GPIO 的抽象。mxc_gpio_port 结构体定义如下:

路径:drivers\gpio\gpio-mxc.c
struct mxc_gpio_port {
	struct list_head node;
	void __iomem *base;
	int irq;
	int irq_high;
	struct irq_domain *domain;
	struct bgpio_chip bgc;
	u32 both_edges;
};

mxc_gpio_port 的 bgc 成员变量很重要,后面分析;

第 11 行:调用 mxc_gpio_get_hw 函数获取 gpio 的硬件相关数据,其实就是 gpio 的寄存器组,函数 mxc_gpio_get_hw 里面有如下代码:

路径:drivers\gpio\gpio-mxc.c
static void mxc_gpio_get_hw(struct platform_device *pdev)
{
	const struct of_device_id *of_id =
			of_match_device(mxc_gpio_dt_ids, &pdev->dev);
	enum mxc_gpio_hwtype hwtype;

	if (of_id)
		pdev->id_entry = of_id->data;
	hwtype = pdev->id_entry->driver_data;

	if (mxc_gpio_hwtype) {
		/*
		 * The driver works with a reasonable presupposition,
		 * that is all gpio ports must be the same type when
		 * running on one soc.
		 */
		BUG_ON(mxc_gpio_hwtype != hwtype);
		return;
	}

	if (hwtype == IMX35_GPIO)
		mxc_gpio_hwdata = &imx35_gpio_hwdata;
	else if (hwtype == IMX31_GPIO)
		mxc_gpio_hwdata = &imx31_gpio_hwdata;
	else
		mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata;

	mxc_gpio_hwtype = hwtype;
}

mxc_gpio_hwdata 是个全局变量,根据设备id最终会选择imx35_gpio_hwdata;对于 I.MX6ULL 而言,硬件类型就是 IMX35_GPIO,imx35_gpio_hwdata 是个结构体变量,描述了 GPIO 寄存器组,内容如下:

路径:drivers\gpio\gpio-mxc.c
static struct mxc_gpio_hwdata imx35_gpio_hwdata = {
	.dr_reg		= 0x00,
	.gdir_reg	= 0x04,
	.psr_reg	= 0x08,
	.icr1_reg	= 0x0c,
	.icr2_reg	= 0x10,
	.imr_reg	= 0x14,
	.isr_reg	= 0x18,
	.edge_sel_reg	= 0x1c,
	.low_level	= 0x00,
	.high_level	= 0x01,
	.rise_edge	= 0x02,
	.fall_edge	= 0x03,
};

该结构体内容与《I.MX6ULL 参考手册》找到“Chapter 26:General Purpose Input/Output(GPIO)”章节第 26.5 小节的寄存器地址表是一致的,如下图:
<Linux开发>驱动开发 -之-gpio子系统_第5张图片
imx35_gpio_hwdata 结构体就是 GPIO 寄存器组结构。这样我们后面就可以通过mxc_gpio_hwdata 这个全局变量来访问 GPIO 的相应寄存器了。

第 17 行:调用函数platform_get_resource 获取设备树中内存资源信息,也就是 reg 属性值。前面说了 reg 属性指定了 GPIO1 控制器的寄存器基地址为 0X0209C000,在配合前面已经得到的mxc_gpio_hwdata,这样 Linux 内核就可以访问 gpio1 的所有寄存器了。

第 18 行:调用 devm_ioremap_resource 函数进行内存映射,得到 0x0209C000 在 Linux 内核中的虚拟地址。

第 22-23 行:通过 platform_get_irq 函数获取中断号,第 22 行获取高 16 位 GPIO 的中断号,第 23 行获取底 16 位 GPIO 中断号。

第 28-29 行:操作 GPIO1 的 IMR 和 ISR 这两个寄存器,关闭 GPIO1 所有 IO 中断,并且清除状态寄存器。

第 38-48 行:设置对应 GPIO 的中断服务函数,不管是高 16 位还是低 16 位,中断服务函数都是 mx3_gpio_irq_handler。

第 50-53 行,bgpio_init 函数第一个参数为 bgc,是 bgpio_chip 结构体指针。bgpio_chip结构体有个 gc 成员变量,gc 是个 gpio_chip 结构体类型的变量。gpio_chip 结构体是抽象出来的GPIO 控制器,gpio_chip 结构体如下所示(有缩减):

路径:include\linux\gpio\driver.h
struct gpio_chip {
	const char		*label;
	struct device		*dev;
	struct module		*owner;
	struct list_head        list;

	int			(*request)(struct gpio_chip *chip,
						unsigned offset);
	void			(*free)(struct gpio_chip *chip,
						unsigned offset);
	int			(*get_direction)(struct gpio_chip *chip,
						unsigned offset);
	int			(*direction_input)(struct gpio_chip *chip,
						unsigned offset);
	int			(*direction_output)(struct gpio_chip *chip,
						unsigned offset, int value);
	int			(*get)(struct gpio_chip *chip,
						unsigned offset);
	void			(*set)(struct gpio_chip *chip,
						unsigned offset, int value);
	void			(*set_multiple)(struct gpio_chip *chip,
						unsigned long *mask,
						unsigned long *bits);
	int			(*set_debounce)(struct gpio_chip *chip,
						unsigned offset,
						unsigned debounce);

	int			(*to_irq)(struct gpio_chip *chip,
						unsigned offset);

	void			(*dbg_show)(struct seq_file *s,
						struct gpio_chip *chip);
	int			base;
	u16			ngpio;
	struct gpio_desc	*desc;
	const char		*const *names;
	bool			can_sleep;
	bool			irq_not_threaded;
	bool			exported;

#ifdef CONFIG_GPIOLIB_IRQCHIP
	/*
	 * With CONFIG_GPIOLIB_IRQCHIP we get an irqchip inside the gpiolib
	 * to handle IRQs for most practical cases.
	 */
	struct irq_chip		*irqchip;
	struct irq_domain	*irqdomain;
	unsigned int		irq_base;
	irq_flow_handler_t	irq_handler;
	unsigned int		irq_default_type;
#endif

#if defined(CONFIG_OF_GPIO)
	/*
	 * If CONFIG_OF is enabled, then all GPIO controllers described in the
	 * device tree automatically may have an OF translation
	 */
	struct device_node *of_node;
	int of_gpio_n_cells;
	int (*of_xlate)(struct gpio_chip *gc,
			const struct of_phandle_args *gpiospec, u32 *flags);
#endif
#ifdef CONFIG_PINCTRL
	/*
	 * If CONFIG_PINCTRL is enabled, then gpio controllers can optionally
	 * describe the actual pin range which they serve in an SoC. This
	 * information would be used by pinctrl subsystem to configure
	 * corresponding pins for gpio usage.
	 */
	struct list_head pin_ranges;
#endif
};

可以看出,gpio_chip 大量的成员都是函数,这些函数就是 GPIO 操作函数。bgpio_init 函数主 要 任 务 就 是 初 始 化 bgc->gc 。 bgpio_init 里 面 有 三 个 setup 函 数 :bgpio_setup_io 、bgpio_setup_accessors 和 bgpio_setup_direction。这三个函数就是初始化 bgc->gc 中的各种有关GPIO 的操作,比如输出,输入等等。

第 51-53 行: GPIO_PSR、GPIO_DR 和 GPIO_GDIR 都是 I.MX6ULL 的 GPIO 寄存器。这些寄存器地址会赋值给 bgc 参数的 reg_dat、reg_set、reg_clr和 reg_dir 这些成员变量。至此,bgc 既有了对 GPIO 的操作函数,又有了 I.MX6ULL 有关 GPIO的寄存器,那么只要得到 bgc 就可以对 I.MX6ULL 的 GPIO 进行操作。

第61行:调用函数gpiochip_add向Linux内核注册gpio_chip,
也就是 port->bgc.gc。注册完成以后我们就可以在驱动中使用 gpiolib 提供的各个 API 函数。

综上,mxc_gpio_probe函数的目的就是,解析设备树中的gpio信息,然后构建一个gpio_chip结构体成员并对其初始化,然后将其注册到Linux内核中。对gpio操作的接口通过gpio_chip结构体注册到内核后就可以通过gpiolib的API进行操作GPIO了。

2.5 GPIO子系统API

对于驱动开发人员,和pinctrl子系统一样,驱动开发人员设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定的 GPIO,具体的pinctrl子系统和gpio子系统相关的驱动半导体厂商已经实现了,gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,大家各司其职,做好自己的本职工作即可。gpio 子系统提供的常用的 API 函数有下面几个;

(1) gpio_request 函数
gpio_request 函数用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request进行申请,函数原型如下:

int gpio_request(unsigned gpio, const char *label)

函数参数和返回值含义如下:
gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。
label:给 gpio 设置个名字。
返回值:0,申请成功;其他值,申请失败。

(2) gpio_free 函数
如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。函数原型如下:

void gpio_free(unsigned gpio)

函数参数和返回值含义如下:
gpio:要释放的 gpio 标号。
返回值:无。

(3) gpio_direction_input 函数
此函数用于设置某个 GPIO 为输入,函数原型如下所示:

int gpio_direction_input(unsigned gpio)

函数参数和返回值含义如下:
gpio:要设置为输入的 GPIO 标号。
返回值:0,设置成功;负值,设置失败。

(4) gpio_direction_output 函数
此函数用于设置某个 GPIO 为输出,并且设置默认输出值,函数原型如下:

int gpio_direction_output(unsigned gpio, int value)

函数参数和返回值含义如下:
gpio:要设置为输出的 GPIO 标号。
value:GPIO 默认输出值。
返回值:0,设置成功;负值,设置失败。

(5) gpio_get_value 函数
此函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏,定义所示:

#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)

函数参数和返回值含义如下:
gpio:要获取的 GPIO 标号。
返回值:非负值,得到的 GPIO 值;负值,获取失败。

(6) gpio_set_value 函数
此函数用于设置某个 GPIO 的值,此函数是个宏,定义如下

#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)

函数参数和返回值含义如下:
gpio:要设置的 GPIO 标号。
value:要设置的值。
返回值:无

GPIO相关的API就解释这些,其它api读者可执行查阅drivers\gpio\gpiolib.c中的其它API接口;

2.6 GPIO OF函数

在前面2.1小节中usdhc1节点定义了一个名为“cd-gpios”的属性,cd-gpios属性描述了 usdhc1这个设备所使用的 GPIO。在驱动程序中需要读取 gpio 属性内容,读取GPIO的电平来判断SD卡是否插入,Linux 内核提供了几个与GPIO 有关的 OF 函数,常用的几个 OF 函数如下所示;

(1)of_gpio_named_count 函数
of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的是空的 GPIO 信息也会被统计到,比如:

gpios = <0
			 &gpio1 1 2
			 0
			 &gpio2 3 4>;

上述代码的“gpios”节点一共定义了 4 个 GPIO,但是有 2 个是空的,没有实际的含义。通过 of_gpio_named_count 函数统计出来的 GPIO 数量就是 4 个,此函数原型如下:int of_gpio_named_count(struct device_node *np, const char *propname)函数参数和返回值含义如下:
np:设备节点。
propname:要统计的 GPIO 属性。
返回值:正值,统计到的 GPIO 数量;负值,失败。

(2)of_gpio_count函数
和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型如下所示:

int of_gpio_count(struct device_node *np)

函数参数和返回值含义如下:
np:设备节点。
返回值:正值,统计到的 GPIO 数量;负值,失败。

(3)of_gpio_named_count 函数
此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编号,此函数在驱动中使用很频繁!函数原型如下:

int of_get_named_gpio(struct device_node *np,
									 const char *propname, 
									int index)

函数参数和返回值含义如下:
np:设备节点。
propname:包含要获取 GPIO 信息的属性名。
index:GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值:正值,获取到的 GPIO 编号;负值,失败。

2.7 小节

通过前面分析,在gpio控制器的驱动程序里会将gpio相关寄存器地址放到一个gpio_chip 结构体中,然后将这个gpio控制器设备注册到kernel内核中,这样通过gpiolib提供的API函数就可以操作gpio了。

三、添加新led gpio

3.1 添加led pinctrl节点

在使用gpio前,先添加配置gpio节点:

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

在这里插入图片描述

3.2 添加led gpio节点

添加led设备节点;

路径:arch/arm/boot/dts/imx6ull-water-emmc.dts
gpioled {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "water-gpioled";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_led>;
		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
		status = "okay";
	};

<Linux开发>驱动开发 -之-gpio子系统_第6张图片
新增gpioled 节点则是在/节点下的。

添加完上述两个节点后,重新编译设备树“make dtbs”,然后用新生成的dtb文件启动kernel,在设备的/sys/firmware/devicetree/base/目录下有如下:
<Linux开发>驱动开发 -之-gpio子系统_第7张图片
新增Led节点完成,后面编写驱动和app来测试验证这个Led设备。

3.3 小节

针对imx6ull的pinctrl子系统和gpio子系统的分析就将这么多,主要分为两部分,pinctrl节点是用来配置io的,而gpio节点则是将其作为gpio功能设备来使用。

四、高通平台对比分析

4.1 某节点下的gpio属性

在高通相关源码中找到一个i2c-mux节点,如下:

路径:lagvm/LINUX/android/kernel/msm-5.4/arch/arm64/boot/dts/vendor/qcom/display/sa8155-adp-star-display.dtsi
i2c-mux@77 {
		compatible = "nxp,pca9542";
		reg = <0x77>;
		#address-cells = <1>;
		#size-cells = <0>;

		i2c@0 {
			reg = <0>;
			#address-cells = <1>;
			#size-cells = <0>;

			anx_7625_1: anx7625@2c {
				compatible = "analogix,anx7625hd";
				reg = <0x2c>;
				interrupt-parent = <&ioexp>;
				interrupts = <0 0>;
				cbl_det-gpio = <&ioexp 1 0>;
				power_en-gpio = <&tlmm 47 0>;
				reset_n-gpio = <&tlmm 49 0>;
			};
		};

		i2c@1 {
			reg = <1>;
			#address-cells = <1>;
			#size-cells = <0>;

			anx_7625_2: anx7625@2c {
				compatible = "analogix,anx7625hd";
				reg = <0x2c>;
				interrupt-parent = <&ioexp>;
				interrupts = <2 0>;
				cbl_det-gpio = <&ioexp 3 0>;
				power_en-gpio = <&tlmm 87 0>;
				reset_n-gpio = <&tlmm 29 0>;
			};
		};
	};

在i2c-mux节点下包含两个子节点i2c0 和i2c1,在其内部下又有节点anx_7625_1 和anx_7625_2。又同时包含cbl_det-gpio 、power_en-gpio和reset_n-gpio三个属性;
其中cbl_det-gpio 是中断io,这里不过多介绍,主要看power_en-gpio和reset_n-gpio;

power_en-gpio:该属性是用来使能anx_7625的电源的io,连接的是tlmm控制器下的47引脚 和 87引脚,0表示低电平有效;

reset_n-gpio:是用来复位anx_7625的,连接的是tlmm控制器下的49引脚 和 29引脚,0表示低电平有效;

4.2 高通GPIO注册

在高通的代码中,pinctrl控制器节点和gpio控制器都是tlmm。

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

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

<Linux开发>驱动开发 -之-gpio子系统_第8张图片
tlmm: pinctrl@3100000 :说明是个pinctrl控制器;
gpio-controller:说明tlmm是gpio控制器;
所以tlmm即是个pinctrl控制器也是gpio控制器;
匹配驱动时的compatible都是"qcom,sm8150-pinctrl";
所以他们的驱动应该是同一个;

在高通的pinctrl驱动中有如下调用:

--->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
		--->msm_gpio_init()lagvm/LINUX/android/kernel/msm-5.4/drivers/pinctrl/qcom/pinctrl-msm.c

在前面四个函数调用时完成pinctrl相关的,这个在<Linux开发>驱动开发 -之-pinctrl子系统有介绍;

对于gpio子系统来说重点时最后一个函数msm_gpio_init();这个就是将:
power_en-gpio = <&tlmm 87 0>;
reset_n-gpio = <&tlmm 29 0>;
等gpio注册打破GPIO子系统;

这样也就可以通过gpiolib的API函数进行操作GPIO了。

五、总结

对于gpio子系统来说,在imx平台下,需要新建一个pinctr节点和在对应设备下引用这个pinctrl以及新增xxx_gpio<&gpiox GPIOnum  level>;对于高通平台直接在对应设备下新增xxx_gpio<&gpiox GPIOnum  level>;即可;
在imx中gpio驱动和pinctrl驱动分别是用的是gpio控制器和iomuxc控制器,而高通平台中gpio 和pinctrl驱动是用同一个tlmm控制器。
所以不同平台设备实现有所差异,对应的驱动也是有差异的,这就是平台差异。

下一章就来将将如何使用pinctrl和gpio写一个led驱动。

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