linux系统中pinctrl 和gpio子系统使用方法(教你点灯)

pinctrl 子系统作用

pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成, pinctrl 子系统源码目录为 drivers/pinctrl。使用这个子系统代替了我们直接去配置寄存器的步骤,这就是linux内核集成的优势了。

设备树PIN配置

要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。打开 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示:
在这里插入图片描述
别看这个节点比较少内容,他的补充内容在设备树.dts的 &iomuxc {} 节点里。
在这里插入图片描述

如图是子系统对我们平时使用到的例如gpio功能的基础应用的pin集合定义,如果需要在 iomuxc 中添加我们自定义外设的 PIN,那么需要新建一个子节点,然后将这个自定义外设的所有 PIN 配置信息都放到这个子节点中。上图中的ledgrp节点就是我创建的一个用于驱动led的pinctrl节点

我们还需要在/{}根目录下创建一个用于描述led的设备子节点(在设备树的根目录节点下创建设备子节点可以方便我们后续开发)

gpioled{
		compatible = "guozhijiang,gpioled";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_gpioled>;
		led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
		status = "okay";
	};

pinctrl-0 = <&pinctrl_gpioled>;代表我接下来要创建的pinctrl节点
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;代表gpio子节点,gpio1 表示第一组,3代表引脚编号为3,GPIO_ACTIVE_LOW代表低电平有效。这个信息在驱动编写的时候使用gpio函数会用到。
记得在&iomuxc {}设置对应的pinctrl子节点用于描述在pinctrl子系统的引脚描述,下图右侧值代表的是 conf_reg 寄存器值!此值由用户自行设置,通过此值来设置一个 IO 的上/下拉、驱动能力和速度等,由用户自行定义。

	pinctrl_gpioled: ledgrp {
			fsl,pins = <
				MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10b0
			>;
		};

我的板子原理图对应的led引脚是gpio1 io03,根据这个信息在自己设备树对应的pinfunc.h头文件里面搜索GPIO1_IO03对应的引脚定义
在这里插入图片描述
表示io03可以复用为如上图所示那么多的功能。
而在右边数值的定义是:<mux_reg conf_reg input_reg mux_mode input_val>。IOMUXC 外 设 寄 存 器 起 始 地 址 为 0x020e0000 ,所以

fsl,pins = <
				MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 (0x020e0000 + mux_reg) 
			>;

之后,烧写设备树到开发板,结果如图
在这里插入图片描述

gpio子系统介绍

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

配置gpio相关

pinctrl 配置好以后就是设置 gpio 了,驱动程序怎么知道 引脚连接的是 GPIO1_IO19 呢?肯定是需要设备树告诉驱动啊!在设备树中的节点下添加一个属性来描述 引脚就行了, 驱动直接读取这个属性值就知道引脚使用的是哪个 GPIO 了。举个如图所示sd卡的例子:
在这里插入图片描述
如图中 pinctrl-0~2 都是 SD 卡其他 PIN 的 pincrtl 节点信息。但是大家会发现,其实在 usdhc1 节点中并没有“pinctrl-3 = <&pinctrl_hog_1>”这一行,也就是说并没有指定 CD 引脚的 pinctrl 信息,那么 SD 卡驱动就没法设置 CD 引脚的复用功能啊?这个不用担心,因为在“iomuxc”节点下引用了 pinctrl_hog_1 这个节点,所以 Linux 内核中的 iomuxc 驱动就会自动初始化pinctrl_hog_1节点下的所有 PIN。
属性“cd-gpios”描述了 SD 卡的 CD 引脚使用的哪个 IO。属性值一共有三个,我们来看一下这三个属性值的含义,“&gpio1”表示 CD 引脚所使用的 IO 属于 GPIO1 组,“19”表示 GPIO1 组的第 19 号 IO,通过这两个值 SD 卡驱动程序就知道 CD 引脚使用了 GPIO1_IO19这 GPIO。“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”就表示高电平有效。根据上面这些信息, SD 卡驱动程序就可以使用 GPIO1_IO19 来检测 SD 卡的 CD 信号了## 编写驱动
根据上面这些信息, SD 卡驱动程序就可以使用 GPIO1_IO19 来检测 SD 卡的 CD 信号了,
打开 imx6ull.dtsi,在里面找到如下所示内容:
在这里插入图片描述
gpio1 节点信息描述了 GPIO1 控制器的所有信息,重点就是 GPIO1 外设寄存器基地址以及兼 容 属 性 。
设置 gpio1 节点的 compatible 属性有两个,分别为“fsl,imx6ul-gpio”和“fsl,imx35-gpio”,在 Linux 内核中搜索这两个字符串就可以找到 I.MX6UL 的 GPIO 驱动程序。reg 属性设置了 GPIO1 控制器的寄存器基地址为 0X0209C000。《I.MX6ULL 参考手册》找到“Chapter 28:General Purpose Input/Output(GPIO)”章节第 28.5 小节,有如图 45.2.2.1 所示的寄存器地址表:
在这里插入图片描述
可以看出, GPIO1 控制器的基地址就是 0X0209C000。
gpio-controller”表示 gpio1 节点是个 GPIO 控制器。
#gpio-cells”属性和“#address-cells”类似, #gpio-cells 应该为 2,表示一共有两个 cell,第一个 cell 为 GPIO 编号,比如“&gpio1 3”就表示 GPIO1_IO03。第二个 cell 表示GPIO 极 性 , 如 果 为 0(GPIO_ACTIVE_HIGH) 的 话 表 示 高 电 平 有 效 , 如 果 为1(GPIO_ACTIVE_LOW)的话表示低电平有效。

编写驱动程序

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define LED_MAJOR 200
#define LED_NAME "led"

/*寄存器物理地址*/
#define CCM_CCGR1_BASE 			(0x020C406C)
#define SW_MUX_GPIO1_IO03_BASE  (0x020E0068)
#define SW_PAD_GPIO1_IO03_BASE  (0x020E02F4)
#define GPIO1_DR_BASE			(0X0209C000)
#define GPIO1_GDIR_BASE			(0X0209C004)


static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

#define LEDOFF 0
#define LEDON  1

/*led灯的打开或者关闭*/
static void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON){
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);  //bit3清0 打开led
		writel(val,GPIO1_DR);
	}
	else if(sta == LEDOFF){
		val = readl(GPIO1_DR);
		val |= (1 << 3);  //bit3清0 关闭led
		writel(val,GPIO1_DR);
	}
}

static int led_open(struct inode *inode, struct file *filp)
{
	return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	int retvalue;
	unsigned char databuf[1];

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

	/*判断开灯还是关灯*/
	led_switch(databuf[0]);
	return 0;
}

//字符设备操作集
static const struct file_operations led_fops = {
	.owner	= THIS_MODULE,
	.write	= led_write,
	.open	= led_open,
	.release= led_release,
};


/*入口*/
static int __init led_init(void)
{
	int ret = 0;
	unsigned int val = 0;
	/*初始化LED灯 地址映射*/
	IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
	SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
	SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
	GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
	GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);

	/* 2.初始化*/
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26); /*先清除以前的配置bit26,27*/
	val |= 3 << 26;  //bit 26.27置1
	writel(val,IMX6U_CCM_CCGR1);

	writel(0x5,SW_MUX_GPIO1_IO03);//设置复用
	writel(0x10B0,SW_PAD_GPIO1_IO03);//设置电气属性

	val = readl(GPIO1_GDIR);
	val |= 1 << 3;  //bit 3置1 设置为输出
	writel(val,GPIO1_GDIR);

	val = readl(GPIO1_DR);
	val &= ~(1 << 3);  //bit3清0 打开led
	writel(val,GPIO1_DR);	

	/*注册字符设备*/
	ret = register_chrdev(LED_MAJOR,LED_NAME,&led_fops);
	if (ret < 0){
		printk("register chardev failed!\r\n");
		return -EIO;
	};
	printk("led_init\r\n");
	return 0;
}

/*出口*/
static void __exit led_exit(void)
{	
	unsigned int val = 0;
	val = readl(GPIO1_DR);
	val |= (1 << 3);  //bit3清0 关闭led
	writel(val,GPIO1_DR);
	/*取消地址映射*/
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);
	/*注销字符设备*/
	unregister_chrdev(LED_MAJOR,LED_NAME);
	printk("led_exit\r\n");
}


/*注册驱动的加载和卸载*/

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("guozhijian");

编写应用程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int fd,retvalue;
    char *filename;
    unsigned char databuf[1];

    if(argc != 3){
        printf("Error usage!\r\n");
        return -1;
    };

    filename = argv[1];

    fd = open(argv[1], O_RDWR);
    if(fd < 0){
        printf("file %s open failed!\r\n",filename);
        return -1;
    };

    databuf[0] = atoi(argv[2]);  //字符转换成数字
    retvalue = write(fd, databuf,sizeof(databuf));
    if(retvalue < 0){
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }
    close(fd);

    return 0;
}

使用gcc编译应用函数然后在开发板上面驱动led的节点就可以控制led灯亮灭啦!

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值