概述
文章目录
- 一、6ull 的 gpio 使用步骤
- 二、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_reg | conf_reg | input_reg | mux_mode | input_val |
---|---|---|---|---|
0x0090 | 0x031C | 0x0000 | 0x5 | 0x0 |
iomuxc 节点 首地址
为 0x020e0000
,0x0090
是偏移地址,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 = <®_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_input
或gpio_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:--------------rn", __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--------rn", __LINE__);
goto fail_devid;
}
printk("%d: gpiobeep.devid = %urn", __LINE__, gpiobeep.devid);
printk("%d: gpiobeep.major = %urn", __LINE__, gpiobeep.major);
printk("%d: gpiobeep.minor = %urn", __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--------rn", __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--------rn", __LINE__);
goto fail_findnd;
}
printk("%d:beep_gpio num = %urn", __LINE__, gpiobeep.beep_gpio);
// 6 申请 gpio 标号
ret = gpio_request(gpiobeep.beep_gpio, "beep-gpio");
if(ret)
{
printk("%d:---------fail-----------rn", __LINE__);
ret = -1;
goto fail_findnd;
}
// 7 申请 gpio 标号
ret = gpio_direction_output(gpiobeep.beep_gpio, 1);
if(ret)
{
printk("%d:---------fail------------rn", __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:---------------rn", __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 数量;负值,失败
最后
以上就是机智泥猴桃为你收集整理的45 pinctrl子系统 和 gpio子系统的全部内容,希望文章能够帮你解决45 pinctrl子系统 和 gpio子系统所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复