文章目录
- 一、pinctrl 子系统简介
- 二、pinctrl子系统的配置形式分析
- 1.主要功能
- 2.配置格式
- 3.pinctrl驱动匹配
- 三、gpio子系统
- 1.gpio系统使用流程
- 四、程序举例-led
- 五、总结
一、pinctrl 子系统简介
在led操作设备树的实验中,对于gpio的初始化是直接操作的寄存器,包括引脚复用,引脚的电气属性(上下拉,速度等),输入/输出都是操作的寄存器,在stm32中是使用库函数实现引脚的初始化,也没有直接去配置寄存器,所以在Linux中,提供了pinctrl子系统来实现IO的配置的。
二、pinctrl子系统的配置形式分析
1.主要功能
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成,pinctrl 子系统源码目录为 drivers/pinctrl。
2.配置格式
举例说明:以imx6ull.dtsi文件(是imx6ull芯片的公共节点配置文件)中的iomuxc节点为例。
手册内容如下:IOMUXC控制器的起始地址就是0x20e0000
向节点iomuxc节点追加内容如下:在设备树中进行的信息追加
在配置GPIO的时候分为两部分,第一部分是引脚的复用功能,第二部分是电气属性信息,现在看下:
fsl,pins中的宏定义含义:打开imx6ull-pinfunc.h文件
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
在文件中定义了宏定义MX6UL_PAD_UART1_RTS_B__GPIO1_IO19内容,其格式如下
/*
* 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_reg:含义是复用寄存器地址偏移地址:0x20e0000+ 0x0090的值(iomuxc节点的地址+0x0090),对比手册
也就是UART1_RTS_B这个引脚
conf_reg:也是寄存器的偏移地址:0x20e0000+0x031C=0x20e031C是UART1_RTS_B这个引脚电气属性配置但在这个conf_reg并没有看见他的值是多少,
在哪呢,在宏定义MX6UL_PAD_UART1_RTS_B__GPIO1_IO19后边,也就是0x17059这个值
intput_reg:的值偏移为0表示UART1_RTS_B这个引脚没有input的这个功能.
mux_mode:复用模式配置:0x5,二进制101看下图0x20e0090的ALT5的值也是101所以引脚被复用为GPIO1_IO19
input_val:就是写入到 intput_reg的值
3.pinctrl驱动匹配
如何找到imx6ull的pinctrl子系统呢?设备树里的节点又是如何与驱动匹配的呢?
驱动与设备树里的节点是通过compatible属性匹配,原理是在驱动中存在一个驱动兼容表也就是 OF 匹配表,此表是个字符串数组,里边保存着compatible 属性值,如果设备树的节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动.
举例:iomuxc节点compatible属性值为:compatible = “fsl,imx6ul-iomuxc”;
在Linux中搜索可以找到,其pinctrl驱动在pinctrl-imx6ul.c中。当pinctrl驱动匹配后,执行probe函数(这个函数是个总称不是说就要执行这个probe函数)。
probe函数获取节点方式:如下代码所示。
imx6ul_pinctrl_probe
->imx_pinctrl_probe
-> 此函数会初始化imx_pinctrl_desc,为pinctrl_desc类型的结构体。重点是:
imx_pinctrl_desc->pctlops = &imx_pctrl_ops;
imx_pinctrl_desc->pmxops = &imx_pmx_ops;
imx_pinctrl_desc->confops = &imx_pinconf_ops;
最后通过pinctrl_register函数向系统注册一个imx_pinctrl_desc,也就是ops函数
->imx_pinctrl_probe_dt
->imx_pinctrl_parse_functions
->imx_pinctrl_parse_groups
-> pin_reg->mux_reg = mux_reg;
pin_reg->conf_reg = conf_reg;
pin->input_reg = be32_to_cpu(*list++);
pin->mux_mode = be32_to_cpu(*list++);
pin->input_val = be32_to_cpu(*list++);
config = be32_to_cpu(*list++);
if (config & IMX_PAD_SION)这里用来设置电气属性值0x17059
pin->mux_mode |= IOMUXC_CONFIG_SION;
pin->config = config & ~IMX_PAD_SION;
imx_pinconf_set函数设置PIN的电气属性
imx_pmx_set函数设置PIN的复用
三、gpio子系统
pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。
gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO。
1.gpio系统使用流程
1、gpio在设备树中的表示方法:
&usdhc1 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc1>;在pinctrl_usdhc1节点
pinctrl-1 = <&pinctrl_usdhc1_100mhz>;在pinctrl_usdhc1_100mhz节点
pinctrl-2 = <&pinctrl_usdhc1_200mhz>;在pinctrl_usdhc1_200mhz节点
cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
keep-power-in-suspend;
enable-sdio-wakeup;
vmmc-supply = <®_sd1_vmmc>;
status = "okay";
};
pinctrl_usdhc1: usdhc1grp {
fsl,pins = <
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x17059
MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x10071
MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x17059
MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x17059
MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x17059
MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x17059
>;
};
pinctrl_usdhc1_100mhz: usdhc1grp100mhz {
fsl,pins = <
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x170b9
MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x100b9
MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170b9
MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170b9
MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170b9
MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170b9
>;
};
pinctrl_usdhc1_200mhz: usdhc1grp200mhz {
fsl,pins = <
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x170f9
MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x100f9
MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170f9
MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170f9
MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170f9
MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170f9
>;
};
定义了一个cd-gpios属性,这里引用了gpio1节点(gpio控制器):关于gpio控制器内的格式,见devicetree\bindings\gpio\fsl-imx-gpio.txt
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>;
};
这样只要读取cd-gpios 属性,就可以操作gpio了,那如何从设备树中获取信息呢,使用of函数获取。
流程如下:
驱动中对gpio的操作函数
1、首先,获取到GPIO所处的设备节点,比如of_find_node_by_path。
2、获取GPIO编号, of_get_named_gpio函数,返回值就是GPIO编号。
3、请求此编号的GPIO,gpio_request函数(不使用后记得释放gpio)
4、设置GPIO,输入或输出,gpio_direction_input或gpio_direction_output。
5、如果是输入,那么通过gpio_get_value函数读取GPIO值,如果是输出,通过gpio_set_value设置GPIO值。
四、程序举例-led
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/export.h>
#include <linux/types.h>
#include <linux/uaccess.h> // copy_to_user() & copy_from_user
#include <asm/io.h> //ioremap
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
MODULE_AUTHOR("Jim Cromie <jim.cromie@gmail.com>");
MODULE_DESCRIPTION("NatSemi/Winbond PC-8736x GPIO Pin Driver");
MODULE_LICENSE("GPL");
#define DEVNAME "gpioled"
/*设备结构体*/
struct gpioled_dev{
dev_t devid;/*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
struct cdev cdev;/*字符设备结构体*/
struct class *class;/*类*/
struct device *device;/*设备*/
struct device_node *node;/*设备节点*/
int led_gpio;
};
struct gpioled_dev gpioled;/*led结构体变量*/
static int gpioled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled;
printk("gpioled_open\n\r");
return 0;
}
static int gpioled_release(struct inode *inode, struct file *filp)
{
//struct gpioled *gpioled = (struct gpioled *)filp->private_data;
//printk("gpioled_open\n\r");
return 0;
}
static ssize_t gpioled_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
//struct gpioled *gpioled = (struct gpioled *)filp->private_data;
//printk("gpioled_open\n\r");
return 0;
}
static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct gpioled_dev *dev = (struct gpioled_dev *)filp->private_data;
int ret = 0;
unsigned char databuff[1];
ret = copy_from_user(databuff, buf, count);
if(ret < 0)
{
printk("copy_from_user error\n\r");
return -EPERM;
}
if(databuff[0] == 0)
{
gpio_set_value(dev->led_gpio, 0);
}
if(databuff[0] == 1)
{
gpio_set_value(dev->led_gpio, 1);
}
//printk("gpioled_open\n\r");
return 0;
}
const struct file_operations gpioled_fops = {
//.owner = THIS_MODULE,
.open = gpioled_open,
.release = gpioled_release,
.read = gpioled_read,
.write = gpioled_write,
};
static int __init gpioled_init(void)
{
/*注册*/
int ret = -1;
/*设备号申请*/
if(gpioled.major){
gpioled.devid = MKDEV(gpioled.major,0);
ret = register_chrdev_region(gpioled.devid, 1, DEVNAME);
}else{
ret = alloc_chrdev_region(&gpioled.devid, 0, 1, DEVNAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
if (ret < 0)
{
printk("alloc_chrdev_region failed\n");
goto devid_fail;
}
/*字符设备初始化*/
cdev_init(&gpioled.cdev,&gpioled_fops);
gpioled.cdev.owner = THIS_MODULE;
gpioled.cdev.ops = &gpioled_fops;
ret = cdev_add(&gpioled.cdev, gpioled.devid,1);
if (ret < 0)
{
printk("cdev_add failed\n");
goto cdev_add_fail;
}
/*类创建*/
gpioled.class = class_create(THIS_MODULE, DEVNAME);
if (IS_ERR(gpioled.class))
{
printk("class_create failed\n");
goto class_create_fail;
}
/*设备创建*/
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, DEVNAME);
if (IS_ERR(gpioled.device))
{
printk("device_create failed\n");
goto device_create_fail;
}
//获取设备属性信息
//1获取节点
gpioled.node = of_find_node_by_path("/gpioled");
if(gpioled.node == NULL)
{
printk("of_find_node_by_path error\n\r");
goto of_find_node_by_path_failed;
}
//2获取led灯gpio的编号
gpioled.led_gpio = of_get_named_gpio(gpioled.node,"led_gpios",0);
if(gpioled.led_gpio < 0)
{
printk("of_get_named_gpio error\n\r");
goto of_get_named_gpio_failed;
}
printk("of_get_named_gpio=%d\n\r",gpioled.led_gpio);
//3申请gpio,申请成功要记得释放
ret = gpio_request(gpioled.led_gpio, "led-gpio");
if (ret) {
printk("gpio_request failed\n");
goto gpio_request_failed;
}
//4使用io,设置输出
ret = gpio_direction_output(gpioled.led_gpio, 1);
if (ret) {
printk("gpio_direction_output failed\n");
goto gpio_direction_output_failed;
}
//5设置io为低电平
gpio_set_value(gpioled.led_gpio, 0);
return 0;
gpio_direction_output_failed:
gpio_free(gpioled.led_gpio);
gpio_request_failed:
of_get_named_gpio_failed:
of_find_node_by_path_failed:
device_create_fail:
class_destroy(gpioled.class);
class_create_fail:
cdev_del(&gpioled.cdev);
cdev_add_fail:
unregister_chrdev_region(gpioled.devid, 1);
devid_fail:
return ret;
}
static void __exit gpioled_exit(void)
{
gpio_set_value(gpioled.led_gpio, 1);
/*注销*/
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, 1);
printk(KERN_INFO "gpioled_exit\n");
/*释放io*/
gpio_free(gpioled.led_gpio);
}
/*驱动入口函数和出口函数*/
module_init(gpioled_init);
module_exit(gpioled_exit);
五、总结
1,添加pinctrl信息,
2,检查当前设备树中要使用的IO有没有被其他设备使用,如果有的话要处理。
分两个地方检查:
1)查看引脚的定义是否有被使用(pinctrl系统)。
2)查看gpio属性,led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;是否被使用。
3,添加设备节点,在设备节点中创建一个属性,此属性描述所使用的gpio。
4,编写驱动,获取对应的gpio编号,并申请IO,成功以后即可使用此Io。