45 pinctrl子系统 和 gpio子系统

一、6ull 的 gpio 使用步骤

  • 1、设置 pin 的 复用 和 电气属性(通过 pinctrl 子系统
  • 2、配置 gpio 的输入输出,高低电平(通过 GPIO子系统

二、pinctrl子系统

pinctrl 和 gpio 子系统详解

  • 借助 pinctrl子系统 来设置一个 pin 的 复用电气属性
    打开文件 imx6ull.dtsi
// 详见 imx6ull.dtsi 参考手册 176,1542
iomuxc: iomuxc@020e0000 {    // 这个结点表示 IO控制器 外设
		// compatible属性用来匹配pinctrl驱动
		compatible = "fsl,imx6ul-iomuxc";
		// 此为寄存器地址范围,此地址范围的寄存器控制每个 pin 的复用和电气属性
		// 基地址,大小
		reg = <0x020e0000 0x4000>;  
};
// gpr 控制器,详见 imx6ull.dtsi 参考手册 1475
gpr: iomuxc-gpr@020e4000 {
		compatible = 	"fsl,imx6ul-iomuxc-gpr",
						"fsl,imx6q-iomuxc-gpr", "syscon";
		reg = <0x020e4000 0x4000>;
};

 ......
// iomuxc_snvs 控制器,详见 imx6ull.dtsi 参考手册 1495
iomuxc_snvs: iomuxc-snvs@02290000 {
		compatible = "fsl,imx6ull-iomuxc-snvs";
		reg = <0x02290000 0x10000>;
};
  • 打开 imx6ull-alientek-emmc.dts 文件:
// 对节点 iomuxc 进行追加
// 追加方式:&标签名
&iomuxc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;
	imx6ul-evk {  // 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 */
			>;
		};

		...

		

		pinctrl_i2c2: i2c2grp {
			fsl,pins = <
				MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
				MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
			>;
		};

		pinctrl_lcdif_dat: lcdifdatgrp {
			fsl,pins = <
				MX6UL_PAD_LCD_DATA00__LCDIF_DATA00  0x79
				MX6UL_PAD_LCD_DATA01__LCDIF_DATA01  0x79
				...
			>;
		};

		...
	};
};
  • pinctrl 子系统就是:创建一个 设备子节点 ,然后 将此设备所用 pin 的配置信息 都放到 这个子节点 里面
    注意格式:存放 pin 配置信息的 属性名 一定要是 fsl,pins

  • 如何添加 一个 pin 的配置信息
    MX6UL_PAD_UART1_RTS_B__GPIO1_IO19

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

imx6ul-pinfunc.h 中找到

/*
 * The pin function ID is a tuple of
 * <mux_reg conf_reg input_reg mux_mode input_val>
 */

#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0

mux_regconf_reginput_regmux_modeinput_val
0x00900x031C0x00000x50x0

iomuxc 节点 首地址0x020e00000x0090 是偏移地址,UART1_RTS_B 绝对地址为 0x020e0090(imx6ull参考手册 1581)
muxmode:5 表示复用为 GPIO1_IO19
在这里插入图片描述
conf_reg:0x031C,相对于基地址的偏移,0x020e0000 + 0x031c = 0x020e031c ,这个地址寄存器是 UART1_RTS_B电气属性配置寄存器0x17059 是写给此寄存器来配置电气属性
input_reg :0,偏移为 0,表示 UART1_RTS_B 这个 pin 没有 input 功能
input_val:0,写给 input_reg 寄存器的值,但是这个 pin 没有

  • pinctrl 驱动工作原理简介
    如何找到 imx6ull 对应的 pinctrl 子系统驱动(半导体厂商写好的)
    使用 节点的 compatible 属性
    驱动文件里面有一个描述 驱动兼容性 的内容,当设备树节点的 compatible 属性驱动里面的兼容性(也是字符串)匹配时,就表示设备和驱动匹配了,表示设备可以使用该驱动文件
    所以只需要全局搜索设备节点里面的 compatible 属性的值 即可找到那个文件,为 /driver/pinctrl/freescale/pinctrl-imx6ul.c,注意其中的 of_device_id 结构体
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 */ }
};

因此设备树中的对应节点使用的驱动是此文件
当驱动和设备节点匹配以后,会执行 probe类 函数 :imx6ul_pinctrl_probe

三、gpio 子系统

  • 使用 gpio 子系统 来操作 gpio
&usdhc1 {
	pinctrl-names = "default", "state_100mhz", "state_200mhz";
	pinctrl-0 = <&pinctrl_usdhc1>;  // 此设备相关的io有这3个
	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 = <&reg_sd1_vmmc>;
	status = "okay";
};
  • 定义了一个 cd-gpios 属性,属性名自取。属性值描述 io 信息
    参考设备绑定文档:devicetree/binfings/gpio/fsl-gpio.txt
    此处使用 gpio1_io19
    打开参考手册 1357
    cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>; :&gpio 表示 使用 gpio1 这一组,19 表示第19个 pin,GPIO_ACTIVE_LOW 是个宏,值为1,表示低电平有效

  • imx6ull.dtsi 中 的 gpio1 节点

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>;
			};
  • 1、获取gpio所处的设备节点 of_find_node_by_path
    2、获取 gpio 编号,of_get_named_gpio ,(int型)
    3、申请一个 GPIO 管脚,gpio_request
    4、设置 gpio,输入或输出,
    gpio_direction_inputgpio_direction_output
    若设置成输入,gpio_get_value 读取某个值
    若设置成输出,gpio_set_value 设置输出值

  • gpiolib
    两部分,一部分是给原厂编写 gpio底层驱动的,一部分给驱动开发人员使用 gpio 操作函数的
    使用 gpiochip_add 向系统添加 gpio_chip,这些都是半导体原厂做的,这部分就是最底层的 gpio 驱动

  • gpio驱动
    drivers/gpio 目录下,gpio-xxx.c 文件 为具体的芯片的驱动文件(最底层):gpio_mxc.c
    gpiolib位于 应用层api 和 底层驱动之间,gpiochip_add add的是一个 gpio_chip 结构体,这个结构体里面包含了底层gpio的操作方法,操作gpio时最终都是调用这个结构体里面的成员函数

  • 全局搜索某节点的 compatible属性值,就能找到它的驱动文件

// gpio-muxc.c gpio 的 各个控制寄存器偏移地址
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,
};
  • 函数调用流程
    mxc_gpio_probe
    -> mxc_gpio_get_hw 获取 6ull 的 gpio控制寄存器组的地址
    -> bgpio_init (重点)初始化 gpio_chip 结构体
    -> gpio_add 想内核添加 gpio_chip

四、驱动编写

  • 1、修改设备树
    参考 imx6ul-pinfunc.h
    访问 iomuxc 节点, 在其下添加节点
pinctrl_gpiobeep: beepgrp {
			fsl,pins = <
				/*MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0*/
				MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10b0
			>;
		};
  • 根结点下添加节点
gpiobeep {
		compatible = "alientek,gpiobeep" ; // 其实用不到这个compatible 属性
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_gpiobeep>; // pin 的 配置信息
		// 此项属性用来获取 gpio 编号
		beep-gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;
		status = "okay";
	};
  • 2、驱动
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/fs.h>
#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/of.h>
#include<linux/of_address.h>
#include<linux/of_irq.h>
#include<linux/gpio.h>
#include<linux/of_gpio.h>

#define GPIOBEEP_DEVID_CNT 1
#define GPIOBEEP_NAME    "gpiobeep"
#define BEEP_OFF 0
#define BEEP_ON 1

extern struct gpiobeep_dev gpiobeep;

// 自定义 beep设备类型,描述一个 beep 的信息
struct gpiobeep_dev
{
    dev_t devid; // 设备号
    u32 major;
    u32 minor;
    struct cdev cdev; // 用于注册字符设备
    struct class *class; // 用于自动创建设备节点,注意成员变量类型是结构体指针
    struct device *device; // 用于自动创建设备节点,注意成员变量类型是结构体指针
    struct device_node *nd; // 用来表示设备树中的一个节点,用于获取设备树信息
    int beep_gpio; // gpio 标号
};  // 数据类型定义一定要放在最前面

static ssize_t beep_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    int ret;
    u8 databuf;
    struct gpiobeep_dev *dev = filp->private_data;

    ret = copy_from_user(&databuf, buf, count); // 从 用户空间 到 内核空间
    if(ret < 0)
    {
        printk("%d:--------------\r\n", __LINE__);
        return -1;
    }
    if(databuf == BEEP_OFF) // 蜂鸣器不响
    {
        gpio_set_value(dev->beep_gpio, 1);
    }
    else if(databuf == BEEP_ON) // // 蜂鸣器响
    {
        gpio_set_value(dev->beep_gpio, 0);
    }
    return 0;
}

static int beep_open(struct inode *inode, struct file *filp)
{
	// 其它操作集成员函数都可以通过 filp->private_data 来访问 beep类型结构体
    filp->private_data = &gpiobeep;
    
    return 0;
}

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

// 操作集函数
static const struct file_operations gpiobeep_fops = 
{
    .owner = THIS_MODULE,
    .write = beep_write,
    .open = beep_open,
    .release = beep_release,
};

struct gpiobeep_dev gpiobeep;

// 驱动入口函数
static int __init beep_init(void)
{
    int val;
    int ret = 0;
    // 注册字符设备
    //1 获取设备号
    gpiobeep.major = 0;
    if(gpiobeep.major)
    {
        gpiobeep.devid = MKDEV(gpiobeep.major, 0); // 次设备号设为0
        // 注册自己指定的设备号
        ret = register_chrdev_region(gpiobeep.devid, GPIOBEEP_DEVID_CNT, GPIOBEEP_NAME);
    }
    else
    {
    	// 系统分配设备号,就不要再向系统注册了
        ret = alloc_chrdev_region(&gpiobeep.devid, 0, GPIOBEEP_DEVID_CNT, GPIOBEEP_NAME);
        gpiobeep.major = MAJOR(gpiobeep.devid);
        gpiobeep.minor = MINOR(gpiobeep.devid);
    }
    if(ret < 0)
    {
		printk("%d:-------fail--------\r\n", __LINE__);
		goto fail_devid;
	}
    printk("%d: gpiobeep.devid = %u\r\n", __LINE__, gpiobeep.devid);
    printk("%d: gpiobeep.major = %u\r\n", __LINE__, gpiobeep.major);
    printk("%d: gpiobeep.minor = %u\r\n", __LINE__, gpiobeep.minor);

    // 2 初始化 cdev结构体,注册字符设备
    gpiobeep.cdev.owner = THIS_MODULE;
    cdev_init(&gpiobeep.cdev, &gpiobeep_fops); // 注意两个参数都是结构体指针
    ret = cdev_add(&gpiobeep.cdev, gpiobeep.devid, GPIOBEEP_DEVID_CNT);
    if(ret)
    {
		printk("%d:-------fail--------\r\n", __LINE__);
		goto fail_cdevadd;
	}

    // 3 自动创建设备节点
    // 一个 class结构体, 一个 device结构体 ,注意这两个结构体是以指针的形式存在于 自定义beep结构体 中的
    // 3.1 创建 class结构体
    gpiobeep.class = class_create(THIS_MODULE, GPIOBEEP_NAME);
    if(IS_ERR(gpiobeep.class))
    {
        ret = PTR_ERR(gpiobeep.class);
        goto fail_class;
    }
    // 3.2 创建 device结构体 
    // 第一个 NULL 表示副设备,  第二个 NULL 表示 drvdata
    gpiobeep.device = device_create(gpiobeep.class, NULL, gpiobeep.devid, NULL, GPIOBEEP_NAME);
    // IS_ERR include/linux/err.h
    if(IS_ERR(gpiobeep.device))
    {
        ret = PTR_ERR(gpiobeep.device);
        goto fail_device;
    }

    // 4. 获取 设备树节点 
    gpiobeep.nd = of_find_node_by_path("/gpiobeep");
    if(gpiobeep.nd == NULL)
    {
        ret = -1;
        goto fail_findnd;
    }

    // 5 获取 beep 对应的 gpio 标号,第二个参数属性名要和设备树文件中的一致
    gpiobeep.beep_gpio = of_get_named_gpio(gpiobeep.nd, "beep-gpios", 0);
    if(gpiobeep.beep_gpio < 0)
    {
        ret = -1;
        printk("%d:-------fail--------\r\n", __LINE__);
        goto fail_findnd;
    }
    printk("%d:beep_gpio num = %u\r\n", __LINE__, gpiobeep.beep_gpio);

    // 6 申请 gpio 标号
    ret = gpio_request(gpiobeep.beep_gpio, "beep-gpio");
    if(ret)
    {
        printk("%d:---------fail-----------\r\n", __LINE__);
        ret = -1;
        goto fail_findnd;
    }
    
    // 7 申请 gpio 标号 
    ret = gpio_direction_output(gpiobeep.beep_gpio, 1);
    if(ret)
    {
        printk("%d:---------fail------------\r\n", __LINE__);
        ret = -1;
        goto fail_findsetdir;
    }

    //8 设置 GPIO 输出低电平
    gpio_set_value(gpiobeep.beep_gpio, 0);

    return 0;

fail_findsetdir: // 释放 gpio标号
    gpio_free(gpiobeep.beep_gpio);
fail_findnd: // 删除设备
    device_destroy(gpiobeep.class, gpiobeep.devid);
fail_device: // 删除类
    class_destroy(gpiobeep.class);
fail_class: // 删除字符设备
    cdev_del(&gpiobeep.cdev);
fail_cdevadd: // 释放设备号
    unregister_chrdev_region(gpiobeep.devid, GPIOBEEP_DEVID_CNT); 
fail_devid:
    printk("%d:---------------\r\n", __LINE__);
    return ret;
}

// 驱动出口函数
static void __exit beep_exit(void)
{
	// 退出驱动时关闭蜂鸣器
	gpio_set_value(gpiobeep.beep_gpio, 1);
    // 删除字符设备
    cdev_del(&gpiobeep.cdev);
    // 释放设备号
    unregister_chrdev_region(gpiobeep.devid, GPIOBEEP_DEVID_CNT);
    // 删除设备
    device_destroy(gpiobeep.class, gpiobeep.devid);
    // 删除类
    class_destroy(gpiobeep.class);
    // 释放 gpio 标号
    gpio_free(gpiobeep.beep_gpio);
}


module_init(beep_init);
module_exit(beep_exit);
MODULE_LICENSE("GPL");

五、总结

  • 1、添加 pinctrl 信息
    iomuxc 结点下的 imx6ul-evk 子节点下,创建一个新的节点
    这个新的节点表示某一外设所用到的所有 pin 的配置信息(复用和电气属性)
pinctrl_gpiobeep: beepgrp {   // 节点标签一定要是 “pinctrl_xxx”
			fsl,pins = <    // 属性名一定要是 “fsl,pins”
				MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0
				/*MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10b0*/
			>;
		};
  • 2、检查当前设备树中要使用的 IO 是否被其他设备使用,若有则要进行处理(屏蔽或者禁用)
  • 3、添加设备节点
    在设备节点中创建一个属性,此属性所使用的 GPIO
gpiobeep {
		compatible = "alientek,gpiobeep" ;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_gpiobeep>;		<----- 蜂鸣器 pin 的配置信息
		led-gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;	<----- 由此属性知道使用哪个 GPIO
		status = "okay";
	};
  • 4、编写驱动,获取对应的 GPIO 编号,申请 IO,成功以后使用 GPIO子系统 提供的 API函数 来操作 GPIO

六、相关函数

  • 1、int of_get_named_gpio (struct device_node *np, const char *propname, int index)
    描述:用于获取 gpio编号
    np :设备节点结构体指针
    propname :包含要获取 GPIO 信息的属性名(要和设备书中的属性名一致)
    index :gpio索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO 的编号,若只有一个 GPIO信息 的话,此参数为 0
    返回值:正值,获取到的 gpio编号。负值,失败
  • 2、int gpio_request (unsigned gpio, const char *label)
    描述:用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request 进行申请
    gpio :要申请的 gpio标号
    label :给 gpio 设置一个名字
    返回值 :0,申请成功。其他值,申请失败
    若申请失败,一般都是因为这个 pin 被占用了
    例如:开发板上的 led 使用的 gpio 是 gpio1 io03,检查设备树文件 dts
&tsc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_tsc>;
	xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;			<----- 此行要注释掉
	measure-delay-time = <0xffff>;
	pre-charge-time = <0xfff>;
	status = "okay";
};

...

pinctrl_tsc: tscgrp {
	fsl,pins = <
		MX6UL_PAD_GPIO1_IO01__GPIO1_IO01	0xb0
		MX6UL_PAD_GPIO1_IO02__GPIO1_IO02	0xb0
		MX6UL_PAD_GPIO1_IO03__GPIO1_IO03	0xb0  	<----- 此行要注释掉
		MX6UL_PAD_GPIO1_IO04__GPIO1_IO04	0xb0
	>;
};

  • 3、void gpio_free (unsigned gpio)
    描述:若不使用某个 GPIO 了,那么就要调用此函数进行释放
    gpio :要释放的 gpio 标号

  • 4、int gpio_direction_input (unsigned gpio)
    描述:此函数用于设置某个 gpio 为输入
    gpio :要设置为输入的 GPIO 标号
    返回值:0,设置成功;负值,设置失败

  • 5、int gpio_direction_output (unsigned gpio, int value)
    描述:此函数用于设置某个 gpio 为输入,并且设置默认值
    gpio :要设置为输出的 GPIO 标号
    value :gpio 默认输出值
    返回值:0,设置成功;负值,设置失败

  • 6、#define gpio_get_value __gpio_get_value
    int __gpio_get_value (unsigned gpio)
    描述:此函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏
    gpio :要获取的 GPIO 标号
    返回值:非负值,得到的 GPIO 值;负值,获取失败

  • 7、#define gpio_set_value __gpio_set_value
    void __gpio_set_value(unsigned gpio, int value)
    描述:此函数用于设置某个 GPIO 的值,此函数是个宏
    gpio :要设置的 GPIO 标号
    value :要设置的值

  • 8、int of_gpio_named_count(struct device_node *np, const char *propname)
    描述:用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的是空的 GPIO 信息也会被统计到
    在这里插入图片描述
    np :设备节点
    propname :要统计的 gpio 属性
    返回值:正值,统计到的 GPIO 数量。负值,失败

  • 9、int of_gpio_count(struct device_node *np)
    描述:和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息
    np :设备节点
    返回值:正值,统计到的 GPIO 数量;负值,失败

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值