Linux pinctrl 和 gpio 子系统实验-基于正点原子IMX6ULL开发板

之前几个led驱动本质都是配置 LED 灯所使用的 GPIO 寄存器,驱动开发方式和裸机基本没啥区别。Linux 内核提供了 pinctrl gpio 子系统用于GPIO 驱动。本实验我们就来学习一下如何借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发。

1 pinctrl 子系统
1.1 pinctrl 子系统简介

以前的实验都是先设置某个 PIN 的复用功能、速度、上下拉等,然后再设置 PIN 所对应的 GPIO 。其实对于大多数的 32 SOC 而言,引脚的设置基本都是这两方面,因此 Linux 内核针对 PIN 的配置推出了 pinctrl 子系统,对于 GPIO 的配置推出了 gpio 子系统
大多数 SOC pin 都是支持复用的,我们不仅要配置pin的复用功能,还需要配置 pin 的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。pinctrl 子系统就是为了解决这个问题而引入的, pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。

我们使用者只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成pinctrl 子系统源码目录为 drivers/pinctrl。

1.2 I.MX6ULL 的 pinctrl 子系统驱动
内容较多,参考正点原子《I.MX6U 嵌入式 Linux 驱动开发指南 V1.6》第45.1.2章节

1.3 设备树中添加 pinctrl 节点模板
 我们虚拟一个名为“test
”的设备,test 使用了 GPIO1_IO00 这个 PIN GPIO 功能,pinctrl 节点添加过程如下:

1、创建对应的节点
同一个外设的 PIN 都放到一个节点里面,打开 imx6ull-alientek-emmc.dts ,在 iomuxc 节点中的“imx6ul-evk ”子节点下添加“ pinctrl_test ”节点,注意!节点前缀一定要为“ pinctrl_ ”。添加完成以后如下所示:
 
 pinctrl_test: testgrp {
 /* 具体的 PIN 信息 */
 };
2、添加“fsl,pins”属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“ fsl,pins ”, 因为对于 I.MX 系列 SOC 而言, pinctrl 驱动程序是通过读取“ fsl,pins ”属性值来获取 PIN 的配置信息完成以后如下所示:
pinctrl_test: testgrp {

fsl,pins = <
/* 设备所使用的 PIN 配置信息 */
 >;
};
3 、在“ fsl,pins ”属性中添加 PIN 配置信息
最后在“ fsl,pins ”属性中添加具体的 PIN 配置信息,完成以后如下所示:
 
pinctrl_test: testgrp {

fsl,pins = <
MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
 >;
};
2 gpio 子系统
2.1 gpio 子系统简介
pinctrl 子系统重点是设置 PIN( 有的 SOC 叫做 PAD) 的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。 gpio子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO 为输入输出,读取 GPIO 的值等
 
gpio 子系统的主要目的就是方便驱动开发者使用 gpio ,驱动开发者 在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。

2.2 I.MX6ULL
gpio 子系统驱动
内容较多,参考正点原子《I.MX6U 嵌入式 Linux 驱动开发指南 V1.6》第45.2.2章节

 

2.3 gpio 子系统 API 函数
常用的API函数如下所示

1、int gpio_request(unsigned gpio, const char *label)

2、void gpio_free(unsigned gpio)

3、int gpio_direction_input(unsigned gpio)

4、int gpio_direction_output(unsigned gpio, int value)​​​​​​​

5、#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)

6、#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)
2.4 设备树中添加 gpio 节点模板
1.3中向“test”设备添加了pinctrl节点,现在 创建 test 设备的 GPIO 节点。​​​​​​​
1 、创建 test 设备节点
在根节点“/”下创建 test 设备子节点,如下所示:
test {
 /* 节点内容 */
 };

2、添加 pinctrl 信息​​​​​​​

1.3 中我们创建了 pinctrl_test 节点,此节点描述了 test 设备所使用的 GPIO1_IO00 这个 PIN 的信息,我们要将这节点添加到 test 设备节点中,如下所示:
 test {
 pinctrl-names = "default";
 pinctrl-0 = <&pinctrl_test>;
 /* 其他节点内容 */
 };
2 行,添加 pinctrl-names 属性,此属性描述 pinctrl 名字为“ default ”。
3 行,添加 pinctrl-0 节点,此节点引用 45.1.3 中创建的 pinctrl_test 节点,表示 tset 设备
的所使用的 PIN 信息保存在 pinctrl_test 节点中。

3
、添加 GPIO 属性信息
 
我们最后需要在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚,添加完成以后如下所示:​​​​​​​
 test {
 pinctrl-names = "default";
 pinctrl-0 = <&pinctrl_test>;
 gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
 };
4 行, test 设备所使用的 gpio
2.5 gpio 相关的 OF 函数
2.4 中,我们定义了一个名为“ gpio ”的属性, gpio 属性描述了 test 这个设 备所使用的 GPIO 。在驱动程序中需要读取 gpio 属性内容, Linux 内核提供了几个与 GPIO 有关 的 OF 函数,常用的几个 OF 函数如下所示:
1、int of_gpio_named_count(struct device_node *np, const char *propname)
//获取设备树某个属性里面定义了几个 GPIO 信息

2、int of_gpio_count(struct device_node *np)
//获取“gpios”这个属性的 GPIO 数量

3、int of_get_named_gpio(struct device_node *np, const char *propname, int index)
//此函数获取 GPIO 编号

3
硬件原理图分析

4 实验程序编写

本次实验我们通过设备树向 dtsled.c 文件传递相应的寄存器物理地址,然后在驱动文件中配置寄存器。本章实验我们使用 pinctrl 和 gpio 子系统来完成 LED 灯驱动。
4.1 修改设备树文件

打开 imx6ull-alientek-emmc.dts。
1、添加 pinctrl 节点
iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_gpioled”的子节点

		pinctrl_gpioled: ledprp{
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0
			>;
		};

3 行,将 GPIO1_IO03 这个 PIN 复用为 GPIO1_IO03,电气属性值为 0X10B0

2、添加 LED 设备节点

根节点“/” 下创建 LED 灯节点,节点名为“ gpioled ”,节点内容如下:
 
	gpioled{
		compatible = "alientek,gpioled";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_gpioled>;
		led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
		status= "okay";
	};
4 行, pinctrl-0 属性设置 LED 灯所使用的 PIN 对应的 pinctrl 节点。
5 行, led-gpios  属性指定了 LED 灯所使用的 GPIO ,在这里就是 GPIO1 IO03 ,低电平
有效。稍后编写驱动程序的时候会获取 led-gpio 属性的内容来得到 GPIO 编号,因为 gpio 子系
统的 API 操作函数需要 GPIO 编号。

3
、检查 PIN 是否被其他外设使用
 
先检查 GPIO1_IO03 这个 PIN 有没有被其他的 pinctrl 节点使用,在 imx6ull-alientek-emmc.dts中搜索 GPIO1_IO03”​​​​​​​  ,找到一个屏蔽一个。

再查找有没有其他的外设使用了 GPIO1_IO03,在 imx6ull-alientek-emmc.dts 中搜索 “gpio1 3”
找到一个屏蔽一个。

设备树编写完成以后使用 make dtbs 命令重新编译设备树

使用新编译出来的 imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。启动成功以后进入“
/proc/device-tree ”目录中 查看“gpioled ”节点是否存在

4.2 LED 灯驱动程序编写
 
新建工程,工程创建好以后新建 gpioled.c 文件,在 gpioled.c 里面输入如下内容:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.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 GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"
#define LEDOFF 0
#define LEDON 1

/*gpioled 设备结构体*/
struct gpioled_dev
{
    dev_t devid;
    int major;
    int minor;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int led_gpio;
};

struct gpioled_dev gpioled; /*LED*/

static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled;
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{

    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t count, loff_t *ppos)
{
    int ret;
    unsigned char databuf[1];
    struct gpioled_dev *dev = filp->private_data;

    ret = copy_from_user(databuf, buf, count);
    if (ret < 0)
    {
        return -EINVAL;
    }

    if (databuf[0] == LEDON)
    {
        gpio_set_value(dev->led_gpio, 0);
    }
    else if (databuf[0] == LEDOFF)
    {
        gpio_set_value(dev->led_gpio, 1);
    }
    return 0;
}

/*操作集*/
static const struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
    .release = led_release,
};

/*驱动入口函数*/
static int __init led_init(void)
{
    int ret = 0;

    /*1,注册字符设备驱动*/
    gpioled.major = 0;
    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, &led_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);
    }

    /***************本次实验重点添加内容**********************/
    /*1,获取设备节点*/
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (gpioled.nd == NULL)
    {
        ret = -EINVAL;
        goto fail_findnode;
    }

    /*2,获取LED设备所对应的GPIO编号*/
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
    if (gpioled.led_gpio < 0)
    {
        printk("can't find led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnode;
    }
    printk("led gpio num =%d\r\n", gpioled.led_gpio);

    /*3,申请GPIO引脚*/
    ret = gpio_request(gpioled.led_gpio, "led-gpio");
    if (ret)
    {
        printk("Failed to request the led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnode;
    }

    /*4,设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯*/
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if (ret)
    {
        goto fail_setoutput;
    }

    /*5,输出低电平,点亮led灯*/
    gpio_set_value(gpioled.led_gpio, 0);

    return 0;

fail_setoutput:
    gpio_free(gpioled.led_gpio);
fail_findnode:

    return ret;
}

/*驱动出口函数*/
static void __exit led_exit(void)
{
    /*关灯*/
    gpio_set_value(gpioled.led_gpio, 1);

    /*注销字符设备驱动*/
    cdev_del(&gpioled.cdev);
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);

    /*释放IO*/
    gpio_free(gpioled.led_gpio);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");

4.3 编写测试 APP
同 《新字符设备驱动实验》测试APP一样:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

/*
 *argc:应用程序参数个数
 * argv[]:具体的参数内容,字符串形式
 * ./ledAPP <filename> <0:1> 0 关灯,1 开灯
 * ./ledAPP /dev/dtsled 0 关灯
 * ./ledAPP /dev/dtsled 1 开灯
 */

#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];

    if (argc != 3)
    {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("file %s open failed!\r\n", filename);
        return -1;
    }

    databuf[0] = atoi(argv[2]); /*将字符转化为数字*/
    retvalue = write(fd, databuf, sizeof(databuf));
    if (retvalue < 0)
    {
        printf("LED Control Failed ! \r\n");
        close(fd);
        return -1;
    }
    
    close(fd);

    return 0;
}

5 运行测试

5.1
编译驱动程序和测试 APP
1、编译驱动程序
编写
Makefile 文件

KERNELDIR := /home/znn/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PAHT := $(shell pwd)
obj-m := gpioled.o

build :kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PAHT) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PAHT) clean

​​​​​​​编译驱动模块



2、编译测试 APP


5.2 运行测试

将上一小节编译出来的 gpioled.ko ledApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15
录中

重启开发板,进入到目录 lib/modules/4.1.15 中,加载驱动模块

打开led

 

关闭led

 

卸载驱动
​​​​​​​

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值