Linux驱动——pinctrl和gpio子系统

文章详细介绍了Linux内核中的pinctrl子系统和GPIO子系统的功能及工作原理。pinctrl子系统主要用于管理芯片引脚的复用功能和电气特性设置,简化了传统直接操作寄存器的繁琐过程。GPIO子系统则提供了一种方便驱动开发者使用的接口,使得在设备树中配置GPIO信息后,驱动程序可以通过API函数轻松操作GPIO,降低了使用GPIO的复杂度。文中还给出了具体的驱动程序示例和设备树配置例子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.产生的背景

无论是哪种芯片,都有类似下图的结构:

 要想让pinA、B用于GPIO,需要设置IOMUX让它们连接到GPIO模块; 要想让pinA、B用于I2C,需要设置IOMUX让它们连接到I2C模块。 所以GPIO、I2C应该是并列的关系,它们能够使用之前,需要设置IOMUX。有时候并不仅仅是设置IOMUX,还要配置引脚,比如上拉、下拉、开漏等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。 pinctrl 子系统就是为了解决这个问题而引入的, pinctrl 子系统主要工作内容如下:

(1)获取设备树中 pin 信息;

(2)根据获取到的 pin 信息来设置 pin 的复用功能 ;

(3)根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。

 2.pinctrl子系统

要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要

根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信

息。打开 imx6ull.dtsi 文件:

iomuxc: iomuxc@020e0000 {
    compatible = "fsl,imx6ul-iomuxc";
    reg = <0x020e0000 0x4000>;
  };

打开 imx6ull-alientek-emmc.dts,找到如下所示内容:

&iomuxc {
	        pinctrl-names = "default";
	        pinctrl-0 = <&pinctrl_hog_1>;
	        imx6ul-evk {
	        	        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 */
	        	        	        >;
	        	        };

当在全局搜索&quot;fsl, imx6ul-iomuxc&quot;就会找到对应的驱动程序, 对应imx6ull的pinctrl驱动程序在/drivers/pinctrl/freescale/pinctrl-imx6ul.c文件中:

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

imx6ul_pinctrl_of_match 结构体数组一共有两个兼容性字符串, 分别为“fsl,imx6ul-iomuxc”和“fsl,imx6ull-iomuxc-snvs”,因此 iomuxc 节点与此驱动匹配,所以 pinctrl-imx6ul.c 会完成 I.MX6ULL 的 PIN 配置工作。执行的流程如下所示:

3.GPIO子系统

 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。

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

由设备树的compatible属性可以找到gpio子系统驱动的文件为:gpio-mxc.c:

                            gpt1: gpt@02098000 {
	        	        	        	        compatible = "fsl,imx6ul-gpt", "fsl,imx31-gpt";
	        	        	        	        reg = <0x02098000 0x4000>;
	        	        	        	        interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>;
	        	        	        	        clocks = <&clks IMX6UL_CLK_GPT1_BUS>,
	        	        	        	        	         <&clks IMX6UL_CLK_GPT_3M>;
	        	        	        	        clock-names = "ipg", "osc_per";
	        	        	        };

	        	        	        gpio1: gpio@0209c000 {
	        	        	        	        compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
	        	        	        	        reg = <0x0209c000 0x4000>;
	        	        	        	        interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
	        	        	        	        	             <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
	        	        	        	        gpio-controller;
	        	        	        	        #gpio-cells = <2>;
	        	        	        	        interrupt-controller;
	        	        	        	        #interrupt-cells = <2>;
	        	        	        };

	        	        	        gpio2: gpio@020a0000 {
	        	        	        	        compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
	        	        	        	        reg = <0x020a0000 0x4000>;
	        	        	        	        interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>,
	        	        	        	        	             <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
	        	        	        	        gpio-controller;
	        	        	        	        #gpio-cells = <2>;
	        	        	        	        interrupt-controller;
	        	        	        	        #interrupt-cells = <2>;
	        	        	        };

	        	        	        gpio3: gpio@020a4000 {
	        	        	        	        compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
	        	        	        	        reg = <0x020a4000 0x4000>;
	        	        	        	        interrupts = <GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>,
	        	        	        	        	             <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
	        	        	        	        gpio-controller;
	        	        	        	        #gpio-cells = <2>;
	        	        	        	        interrupt-controller;
	        	        	        	        #interrupt-cells = <2>;
	        	        	        };

	        	        	        gpio4: gpio@020a8000 {
	        	        	        	        compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
	        	        	        	        reg = <0x020a8000 0x4000>;
	        	        	        	        interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>,
	        	        	        	        	             <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
	        	        	        	        gpio-controller;
	        	        	        	        #gpio-cells = <2>;
	        	        	        	        interrupt-controller;
	        	        	        	        #interrupt-cells = <2>;
	        	        	        };

	        	        	        gpio5: gpio@020ac000 {
	        	        	        	        compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
	        	        	        	        reg = <0x020ac000 0x4000>;
	        	        	        	        interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
	        	        	        	        	             <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
	        	        	        	        gpio-controller;
	        	        	        	        #gpio-cells = <2>;
	        	        	        	        interrupt-controller;
	        	        	        	        #interrupt-cells = <2>;
	        	        	        };
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,
};

static int __init gpio_mxc_init(void)
{
	        return platform_driver_register(&mxc_gpio_driver);
}
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 */ }
};

4.实践

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT			1		  	/* 设备号个数 */
#define GPIOLED_NAME		"gpioled"	/* 名字 */
#define LEDOFF 				0			/* 关灯 */
#define LEDON 				1			/* 开灯 */

/* gpioled设备结构体 */
struct gpioled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
	int led_gpio;			/* led所使用的GPIO编号		*/
};

struct gpioled_dev gpioled;	/* led设备 */

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &gpioled; /* 设置私有数据 */
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;
	struct gpioled_dev *dev = filp->private_data;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		gpio_set_value(dev->led_gpio, 0);	/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		gpio_set_value(dev->led_gpio, 1);	/* 关闭LED灯 */
	}
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init led_init(void)
{
	int ret = 0;

	/* 设置LED所使用的GPIO */
	/* 1、获取设备节点:gpioled */
	gpioled.nd = of_find_node_by_path("/gpioled");
	if(gpioled.nd == NULL) {
		printk("gpioled node not find!\r\n");
		return -EINVAL;
	} else {
		printk("gpioled node find!\r\n");
	}

	/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
	gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
	if(gpioled.led_gpio < 0) {
		printk("can't get led-gpio");
		return -EINVAL;
	}
	printk("led-gpio num = %d\r\n", gpioled.led_gpio);

	/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
	ret = gpio_direction_output(gpioled.led_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (gpioled.major) {		/*  定义了设备号 */
		gpioled.devid = MKDEV(gpioled.major, 0);
		register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);	/* 申请设备号 */
		gpioled.major = MAJOR(gpioled.devid);	/* 获取分配号的主设备号 */
		gpioled.minor = MINOR(gpioled.devid);	/* 获取分配号的次设备号 */
	}
	printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);	
	
	/* 2、初始化cdev */
	gpioled.cdev.owner = THIS_MODULE;
	cdev_init(&gpioled.cdev, &gpioled_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

	/* 4、创建类 */
	gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
	if (IS_ERR(gpioled.class)) {
		return PTR_ERR(gpioled.class);
	}

	/* 5、创建设备 */
	gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
	if (IS_ERR(gpioled.device)) {
		return PTR_ERR(gpioled.device);
	}
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{
	/* 注销字符设备驱动 */
	cdev_del(&gpioled.cdev);/*  删除cdev */
	unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */

	device_destroy(gpioled.class, gpioled.devid);
	class_destroy(gpioled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

### GPIO 子系统的工作原理 GPIO(General Purpose Input/Output)子系统是一种通用的硬件接口机制,用于控制微处理器中的引脚状态。它允许开发者通过软件编程的方式配置操作这些引脚的状态。根据提供的引用内容[^1],GPIO 子系统依赖于 pinctrl 子系统来完成某些功能性切换。 具体来说,`pin controller` 负责管理引脚的功能性切换,比如将引脚分配给 I2C、UART 或 SPI 等外设功能。而 `GPIO Controller` 则专注于提供基本的操作能力,例如设置引脚为输入或输出模式、读取或写入引脚值等简单功能。 当需要使用某个引脚控制外部设备(如 LED),通常会经历以下几个过程: - **使能时钟**:激活相关模块所需的电源与时钟资源。 - **配置引脚功能**:利用 pinctrl 子系统将引脚复用为 GPIO 功能。 - **配置电气属性**:调整驱动强度、上拉/下拉电阻等参数。 - **初始化 GPIO 方向**:调用 `gpio_direction_output()` 或 `gpio_direction_input()` 设置引脚为输入或输出模式。 - **控制电平**:通过 `gpio_set_value()` 函数改变引脚的高低电平状态[^2]。 ### GPIO 子系统的实现细节 在 Linux 内核中,GPIO 子系统的核心结构围绕着 `struct gpio_chip` 展开。这个数据结构定义了一个 GPIO 控制器的行为模型,并提供了多个回调函数指针以便实现特定平台的需求。以下是几个重要的成员及其作用: #### 1. `request()` 此函数负责申请指定编号的 GPIO 引脚供应用程序使用。如果成功,则返回零;否则返回错误码表示失败原因。需要注意的是,在实际应用过程中可能还需要额外处理一些特殊情况,因为并非所有平台上都完全支持这一标准接口[^3]。 #### 2. `direction_output() / direction_input()` 这两个方法分别用来设定目标 GPIO 的工作方向——即作为输出端还是输入端。它们内部可能会进一步调用底层硬件寄存器访问逻辑或者借助 pin control framework 完成最终转换。 ```c void rockchip_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value) { rockchip_gpio_set(gc, offset, value); pinctrl_gpio_direction_output(gc->base + offset); } ``` 上述代码片段展示了如何在一个典型的 Rockchip 平台上执行 output 模式的切换动作。这里不仅修改了具体的 bit 值还通知了 pinctrl subsystem 关于此变化的信息。 ### 使用方法总结 对于开发人员而言,最常用的 API 包括但不限于以下几种形式: - 请求并释放单条线路:`int gpio_request(unsigned gpio, const char *label)` void gpio_free(unsigned gpio). - 配置出入类型:int gpio_direction_[output|input](unsigned gpio,int val). - 获取当前数值:int gpio_get_value(unsigned gpio),以及相应地更新新状态:void gpio_set_value(unsigned gpio,int value). 以上便是关于Linux环境下典型嵌入式架构下的GPIO子系统介绍.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小吴伴学者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值