一口Linux

文章数:1385 被阅读:1972833

推荐内容
账号入驻

【i.MX6ULL】驱动开发——Pinctrl子系统与GPIO子系统点亮LED

最新更新时间:2021-12-14
    阅读数:

前面的两篇文章( 寄存器配置点亮LED 设备树版的点亮LED ),其本质都是通过寄存器配置,来控制LED的亮灭。

  • 使用 直接操作寄存器 的方式,是将与LED有关的寄存器信息,直接写到了LED的驱动代码中,这也是一种比较常规的控制方式。但当芯片的寄存器发了变动,就要对底层的驱动进行重写。
  • 使用 设备树 的方式,是将与LED有关的寄存器信息,写到了设备树文件中,这样,当设备的信息修改了,还可以通过设备树的接口函数,来获取设备信息,提高了驱动代码的复用能力。
  • 本篇介绍的 Pinctrl子系统与GPIO子系统 的方式,不需要再直接操作寄存器了,因为这两个子系统已经替我们实现了对寄存器的操作,我们只需要操作这两个子系统提供的API函数即可。

1 Pinctrl子系统

Pintrl子系统,顾名思义,就是管理pin引脚的一个系统,比如要点亮LED,即要控制LED对应引脚的高低电平,就要先通过Pintrl子系统将LED对应的引脚复用为GPIO功能(这一点是不是和之前 寄存器配置时使用的MUX寄存器 的功能有点像)。

1.1 设备树中iomuxc节点

如何使用Pintrl子系统呢?其实它也是要依赖设备树的,先来了解一下设备树里的 iomuxc节点 ,这个节点是IOMUXC外设对应的节点,负责IO功能的复用。

打开自己开发板对应的设备树文件(我的是imx6ull-myboard.dts),然后找到iomuxc节点,先来看一下其基本结构:

&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-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_csi1: csi1grp {
fsl,pins = <
MX6UL_PAD_CSI_MCLK__CSI_MCLK 0x1b088
MX6UL_PAD_CSI_PIXCLK__CSI_PIXCLK 0x1b088
MX6UL_PAD_CSI_VSYNC__CSI_VSYNC 0x1b088
MX6UL_PAD_CSI_HSYNC__CSI_HSYNC 0x1b088
MX6UL_PAD_CSI_DATA00__CSI_DATA02 0x1b088
MX6UL_PAD_CSI_DATA01__CSI_DATA03 0x1b088
MX6UL_PAD_CSI_DATA02__CSI_DATA04 0x1b088
MX6UL_PAD_CSI_DATA03__CSI_DATA05 0x1b088
MX6UL_PAD_CSI_DATA04__CSI_DATA06 0x1b088
MX6UL_PAD_CSI_DATA05__CSI_DATA07 0x1b088
MX6UL_PAD_CSI_DATA06__CSI_DATA08 0x1b088
MX6UL_PAD_CSI_DATA07__CSI_DATA09 0x1b088
>;
};
//省略...

这里以 pinctrl_hog_1插拔子节点 为例进行分析,它是和热插拔有关的Pin集合,比如USB OTG的ID引脚,pinctrl_csi1子节点是csi外设所使用的PIN,本篇需要控制LED的亮灭,就需要新建一个对应的节点,然后将这个自定义外设的所有Pin配置信息都放到这个子节点中。

1.2 宏定义的含义解析

对于pinctrl_hog_1这个字节点,注意其中的:

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19	    0x17059 /* SD1 CD */

这就是对Pin引脚的配置,配置包括两方面:一是设置Pin的 复用功能 ,二是设置Pin的 电气特性

前面的 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 这个宏定义, 定义在arch/arm/boot/dts/imx6ul-pinfunc.h中(注意 imx6ull.dtsi 会引用 imx6ull-pinfunc.h ,而imx6ull-pinfunc.h又会引用 imx6ul-pinfunc.h

这里一共有8 个以MX6UL_PAD_UART1_RTS_B开头的宏定义,分别代表这个IO的8种不同的功能。

另外,这个宏定义的值,被分为了5段,每段的值都有具体的含义:

  • 0x0090 mux_reg寄存器偏移地址

  • **0x031C **conf_reg寄存器偏移地址

  • 0x0000 input_reg寄存器偏移地址(这里无效)

  • 0x5 mux_reg寄存器的值

  • 0x0 input_reg寄存器值(这里无效)

2 GPIO子系统

GPIO子系统,顾名思义,就是管理GPIO功能的一个系统,其作用是初始化配置GPIO(这一点是不是和之前 寄存器配置时使用的PAD寄存器 的功能有点像),并提供对外的API接口。使用GPIO子系统后,就不需要自己操作寄存器,通过调用GPIO子系统提供的API函数即可实现对GPIO的控制。

2.1 设备树中gpio信息

仍以热插拔节点为例:

UART1_RTS_B复用为GPIO1_IO19,通过读取其高低电平来判断SD卡有没有插入。

那SD卡驱动程序怎么知道CD引脚连接的GPIO1_IO19呢?还是需要设备树告诉驱动,在设备树中SD卡节点下添加一个属性来描述SD卡的 CD 引脚就行了:

cd-gpios 描述了SD卡的CD引脚使用的哪个IO,属性值一共有三个:

  • &gpio1 表示CD引脚所使用的IO属于GPIO1组
  • 19 表示GPIO1组的第19号IO
  • GPIO_ACTIVE_LOW 表示低电平有效

根据上面这些信息,SD卡驱动程序就可以使用GPIO1_IO19来检测SD卡的CD信号了

2.2 gpio子系统API函数

2.2.1 gpio_request/free

  • gpio_request

用于申请一个GPIO管脚

/**
* gpio: 要申请的gpio标号(使用of_get_named_gpio函数从设备树获取指定GPIO属性信息时返回的标号)
* label: 给gpio设置个名字
* return: 0-申请成功 其他值-申请失败
*/

int gpio_request(unsigned gpio, const char *label)
  • gpio_free

用于释放一个GPIO管脚

/**
* gpio: 要释放的gpio标号
* return
*/

void gpio_free(unsigned gpio)

2.2.2 gpio_direction_input/output

  • gpio_direction_input

用于设置某个GPIO为 输入

/**
* gpio: 要设置为输入的GPIO标号
* return: 0-设置成功 负值-设置失败
*/

int gpio_direction_input(unsigned gpio)
  • gpio_direction_output

此函数用于设置某个GPIO为 输出 ,并且设置 默认输出值

/**
* gpio: 要设置为输出的GPIO标号
* value: GPIO默认输出值
* return 0-设置成功 负值-设置失败
*/

int gpio_direction_output(unsigned gpio, int value)

2.2.3 gpio_get_value/set_value

  • gpio_get_value

此函数用于 获取 某个GPIO的值(0 或 1)

#define gpio_get_value  __gpio_get_value
/**
* gpio: 要获取的gpio标号
* return: 非负值-得到的gpio值 负值-获取失败
*/

int __gpio_get_value(unsigned gpio)
  • gpio_set_value

用于 设置 某个GPIO的值

#define gpio_set_value  __gpio_set_value 
/**
* gpio: 要设置的gpio标号
* value: 要设置的值
* return
*/

void __gpio_set_value(unsigned gpio, int value)

2.3 与gpio相关的OF函数

2.3.1 of_gpio_named_count

用于获取设备树某个属性里面定义了几个GPIO信息

/**
* np: 设备节点
* propname: 要统计的gpio属性
* return: 正值-统计到的gpio数量 负值-失败
*/

int of_gpio_named_count(struct device_node *np, const char *propname)

2.3.2 of_gpio_count

统计“gpios”这个属性的gpio数量

/**
* np: 设备节点
* return: 正值-统计到的gpio数量 负值-失败
*/

int of_gpio_count(struct device_node *np)

2.3.3 of_get_named_gpio

获取GPIO编号

/**
* np: 设备节点
* propname: 包含要获取gpio信息的属性名
* index: gpio索引(一个属性里面可能包含多个gpio)
* return: 正值-获取到的gpio编号 负值-失败
*/

int of_get_named_gpio(struct device_node *np,
const char *propname,
int index)

3 Pinctr版LED驱动程序

上面介绍了Pinctrl子系统与GPIO子系统的基本情况,下面就来使用它们来实现LED的亮灭控制。

3.1 修改设备树文件

修改imx6ull-myboard.dts,在iomuxc节点的imx6ull-evk字节点下创建一个名为pinctrl_led的子节点,节点内容如下:

pinctrl_gpioled: ledgrp{
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x10b0
>;
};
  • MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 表示将该io复用为GPIO

  • 0x10b0 表示对PAD寄存器的配置值,具体含义为如下,之前的文章(驱动开发4--点亮LED(寄存器版))介绍过。

    /*寄存器SW_PAD_SNVS_TAMPER3设置IO属性
    *bit 16:0 HYS关闭
    *bit [15:14]: 00 默认下拉
    *bit [13]: 0 kepper功能
    *bit [12]: 1 pull/keeper使能
    *bit [11]: 0 关闭开路输出
    *bit [7:6]: 10 速度100Mhz
    *bit [5:3]: 110 R0/6驱动能力
    *bit [0]: 0 低转换率
    */

在根节点下创建名为gpioled的LED节点,内容如下:

/*pinctrl led*/
gpioled {
compatible = "myboard,gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpioled>;
led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
  • pinctrl-0 设置 LED所使用的PIN对应的pinctrl节点
  • led-gpio 指定了LED所使用的GPIO,这里是GPIO5的IO03,低电平有效

3.2 检查引脚是否使用冲突

因为我的开发板使用的设备树文件(imx6ull-myboard.dts)是从NXP官方提供的设备树文件(imx6ull-14x14-evk.dts)上修改而来的,可能某些引脚的配置与自己的开发板不一样,需要检查一下是否有使用冲突。

本次添加的这 MX6ULL_PAD_SNVS_TA MPER3__GPIO5_IO03 与文件中的其它引脚没有出现冲突,因此无需修改。

3.3 修改LED驱动文件

在上一篇的设备树版的驱动文件上进行修改,主要修改内容如下。

头文件需要添加一个:

#include 

设备结构体改为gpio_led:

/* 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设备 */

硬件初始化 部分是主要修改的内容,这次就不需要从设备树读取寄存器操作了,也不需要自己再进行I/O内存映射了,而实使用gpio子系统的API函数来对LED的GPIO进行配置:

static int gpioled_hardware_init(void)
{
int ret;

/* 获取设备树中的属性数据 */
/* 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编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(gpioled.led_gpio < 0)
{
printk("can't get led-gpio!\r\n");
return -EINVAL;
}
else
{
printk("led-gpio num = %d\r\n", gpioled.led_gpio);
}

/* 3、设置GPIO为输出, 并默认关闭LED */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0)
{
printk("can't set led-gpio!\r\n");
}

return 0;
}

开关LED时,也不需要再直接操作寄存器了,也是使用API函数来操作:

static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
//省略...

if(ledstat == LEDON)
{
gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
printk("led on!\n");
}
else if(ledstat == LEDOFF)
{
gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
printk("led off!\n");
}

return 0;
}

4 实验测试

4.1 编译程序

  • 编译设备树文件(.dtb),和上篇设备树点亮LED的实验一样,先将设备树文件复制到nfs文件系统位置,再从网络启动开发板,串口中查看设备树中是否有添加的gpioled节点:
  • 编译LED驱动文件(.ko),复制到rootfs/lib/modules/4.1.15目录中:
  • LED应用程序不需要改,仍使用之前寄存器版点亮LED实验时使用的程序即可。

4.2 测试

测试方式与之前的一样,都是先加载驱动文件,然后调用应用程序来控制LED的亮灭:

效果和之前的 寄存器版点亮LED 设备树版点亮LED 的效果一样

5 总结

本篇介绍了使用 Pinctrl子系统与GPIO子系统 的方式来点亮LED,与之前的 寄存器版点亮LED 设备树版点亮LED 的最大区别在于 不需要直接操作寄存器 了,而是使用API函数来配置GPIO,具体操作寄存器在过程在API函数内部实现,我们无需在进行繁琐的寄存器操作。

本篇与上一篇的 设备树版点亮LED 的程序编写流程基本一致,因为都是要使用 设备树 ,与上一篇的主要区别就在于,不需要将寄存器信息写入设备树,再从设备树获取出来手动配置寄存器了。

end



一口Linux


关注,回复【 1024 】海量Linux资料赠送


精彩文章合集

文章推荐

【专辑】 ARM
【专辑】 粉丝问答
【专辑】 所有原创
专辑 linux 入门
专辑 计算机网络
专辑 Linux驱动


点击“ 阅读原文 ”查看更多分享,欢迎 点分享、收藏、点赞、在看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: TI培训

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved