• Linux驱动BSP (pinctrl&gpio子系统)


    一、pinctrl 子系统

    1、简介

    注册和编写不用管Linux内核调用机制,按规则编写注册就好了,大不了底层配置demo没有的需要自己配置PIN信息,其他按步就班编写就好了

    作用: pinctrl 子系统主要用于管理芯片的引脚配置对应的工作模式。imx6ull 芯片拥有众多的片上外设,大多数外设需要通过芯片的引脚与外部设备(器件)相连实现相对应的控制。

    • pinctrl 子系统是由芯片厂商来实现的, 简单来说用于帮助我们管理芯片引脚并自动完成引脚的初始化,而我们要做的只是在设备树中按照规定的格式写出想要的配置参数即可。
    • 获取设备树中 pin 信息。
    • 根据获取到的 pin 信息来设置 pin 的复用功能。
    • 根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。

    目的:学会在设备树下修改或者添加自己的pinctrl

    2、 iomuxc

    iomuxc设备节点pinctrl 子系统的平台驱动做匹配

    • 子节点追加数据,不同的外设使用的 PIN 不同、其配置也不 同,意思是某个外设所使用的所有 PIN 都组织在一个子节点里面。
    • 比如串口就是串口名字然后配置所有的PIN引脚信息。
    • 各个厂商会提供不同的文件节点,比如正点引用在imx6ull-alientek-emmc.dts野火./arch/arm/boot/dts/imx6ull-mmc-npi.dts中通过&iomuxc追加。

    3、怎么看设备树里面pinctrl子系统每个外设的PIN 配置

    在这里插入图片描述
    第一个参数为复用,具体配置是配置芯片引脚的功能,第二个参数一般为电气属性配置其上下拉等一些电气属性,一般按照官方demo配置就没有问题的

    4、添加设备树中 pinctrl 节点模板

    		pinctrl_hog_1: hoggrp-1 {
    			fsl,pins = <
    MX6UL_PAD_UART1_RTS_B__GPIO1_IO19	0x17059 /* SD1 CD */
    				
    			>;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    1. 节点前缀一定要为“pinctrl_”
    pinctrl_xxx: xxx
    
    • 1
    1. 添加“fsl,pins”属性
    fsl,pins = <
    
    	>;
    
    
    • 1
    • 2
    • 3
    • 4
    1. 在“fsl,pins”属性中添加 PIN 配置信息
    			fsl,pins = <
    MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
    MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */
    MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058 /* USB_OTG1_ID */
    			>;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    二、 gpio 子系统

    作用:在pinctrl设置好引脚的复用与电气属性以后,如果是使用GPIO的功能使用GPIO子系统配置引脚的输入输出,高低电平

    1、设备树的体现

    在需要外设使用GPIO子系统下的设备节点添加对应的gpio属性

    • 比如在SD卡中检测是否插入或者拔掉的办法
    • cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
    &usdhc1 {
       pinctrl-names = "default", "state_100mhz", "state_200mhz";
       pinctrl-0 = <&pinctrl_usdhc1>;
       pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
       pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
    /* pinctrl-3 = <&pinctrl_hog_1>; */
       cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
       keep-power-in-suspend;
       enable-sdio-wakeup;
       vmmc-supply = <&reg_sd1_vmmc>;
       status = "okay";
    };
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2、对于驱动开发人员,设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定的 GPIO

    • gpio_request 函数
    • gpio_free 函数
    • gpio_direction_input 函数
    • gpio_direction_output 函数
    • gpio_get_value 函数
    • gpio_set_value 函数

    OF函数

    • of_gpio_named_count
    • of_gpio_count
    • of_get_named_gpio

    3、编写GPIO子系统一句话

    在注册为GPIO模式下需要的节点下面添加
    gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;

    三、编写测试程序实操

    1、修改设备树,添加pinctrl&gpio子系统,并且检测系统中引脚是否复用

    1. 编写设备节点,在节点添加注册pinctrl子系统pinctrl_0 = <&pinctrl_gpioled>与注册GPIO子系统led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;注册完以后驱动程序就可以使用gpio注册函数编写代码了
    	liqi_gpioled{
    		compatible = "liqi-led"; 
    		pinctrl_name ="default";
    		pinctrl_0 = <&pinctrl_gpioled>;
    		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
    		#address-cells = <1>;
    		#size-cells = <1>;
    		status = "okay";
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 在pinctrl子系统下面配置GPIO属性
    	pinctrl_gpioled: gpioledgrp-1 {
    			fsl,pins = <
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 	0x10B0
    			>;
    		};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 利用pinctrl和gpio系统引脚配置检测设备树有没有重复使用引脚的。在linux下一个引脚仅支持复用为一种功能的代码

    在dts源文件下搜索GPIO1_IO03或者gpio1 3

    • 设备树修改完成

    在这里插入图片描述

    2、编写驱动程序

    先改设备树使用GPIO子系统gpio驱动函数,再注册设备驱动

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    /***************************************************************
    Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
    文件名		: gpioled.c
    作者	  	: 左忠凯
    版本	   	: V1.0
    描述	   	: 采用pinctrl和gpio子系统驱动LED灯。
    其他	   	: 无
    论坛 	   	: www.openedv.com
    日志	   	: 初版V1.0 2019/7/13 左忠凯创建
    ***************************************************************/
    #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");
    MODULE_AUTHOR("zuozhongkai");
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207

    3、编译测试

    1. 修改makefile文件
    obj-m := xxx.o
    
    • 1

    make -j32
    编译成功以后就会生成一个名为“gpioled.ko”的驱动模块文件。

    1. 编译测试 APP
    arm-linux-gnueabihf-gcc xxx.c -o xxx
    
    • 1

    生成可执行文件

    1. gpioled.ko 和 ledApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目 录中,重启开发板,进入到目录 lib/modules/4.1.15 中
    cp xx xxx /home/alientek/linux/nfs/rootfs/lib/modules/4.1.15 -f
    
    • 1
    1. 加载驱动
      depmod modprobe xx.ko
      查看驱动输出信息,cat /proc/devices 查看当前注册设备节点

    在这里插入图片描述
    5. 测试应用程序
    ./liqiledAPP dev/dtsled 1
    ./liqiledAPP dev/dtsled 0

    1. 实验现象led亮灭
      在这里插入图片描述

    总结

    联系

    交个博友,技术分享交流联系作者Q群:586025772

  • 相关阅读:
    SpringCloud案例day03.md
    关于 国产麒麟系统Qt强制退出应用程序qApp->exit()无效 的解决方法
    学习太极创客 — MQTT 第二章(七)ESP8266 MQTT 遗嘱应用
    数组多项时最后一项不要逗号
    【基于C的排序算法】插入排序之直接插入排序
    jmeter录制https脚本
    Vue3 + ts 开发一个ProTable
    实现ALV页眉页脚
    关注我,我们一起彻底学会java
    Python的张量运算
  • 原文地址:https://blog.csdn.net/liqifff/article/details/125721387