【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十五章 Pinctrl和GPIO子系统实验

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、

【公众号】迅为电子

【粉丝群】258811263


第五十五章 Pinctrl和GPIO子系统实验

本章导读

本章节将以实践课的方式讲解LED驱动例程。

55.1章节对实验需求进行分析

55.2章节修改设备树文件

55.3章节编写驱动文件

55.4章节编写测试APP

55.5章节运行测试,实现了控制LED的亮灭。

本章内容对应视频讲解链接(在线观看):

"pinctl和gpio子系统 (二) " → https://www.bilibili.com/video/BV1Vy4y1B7ta?p=30

"pinctl和gpio子系统 (三) " → https://www.bilibili.com/video/BV1Vy4y1B7ta?p=31

程序源码在网盘资料"iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\012-Pinctrl和GPIO子系统实验"路径下。

55.1 需求分析

通过第54章的学习,我们已经明白什么是pinctrl子系统和GPIO子系统,pinctrl子系统就是设置引脚的复用关系和电气属性的,GPIO子系统就是当pinctrl子系统将复用关系设置为GPIO以后,我们使用GPIO子系统来操作我们的GPIO。那么本章节我们来用pinctrl子系统和GPIO子系统来控制我们的引脚。第55章我们学习了在设备树下的平台总线模型,当匹配成功以后会在设备树文件中拿资源,然后我们再进行相关的操作。

本章节我们要编写pinctrl子系统和GPIO子系统,我们可以在以前代码的基础上进行编写,框架并没有变,我们也是让驱动和设备进行匹配,匹配成功之后将相关的操作,唯一变的地方是之前使用的寄存器操作我们的GPIO,现在我们换成了GPIO子系统提供的API函数来操作我们的GPIO,比原来的方法更先进了一些。

我们以iTOP-IMX8MM开发板为例来控制LED,本章节内容会将以前学习的驱动理论融会贯通,学以致用。LED 设备注册的流程:

  • 硬件原理图分析,确定控制LED的GPIO信息,参考42.2硬件分析章节,蜂鸣器复用GPIO11_IO13
  • 根据GPIO信息在设备树文件中添加pinctrl信息
  • 在设备树文件中创建蜂鸣器的节点,并加入GPIO信息,修改完设备树编译烧写新的设备树
  • 编写LED设备驱动,加载驱动模块
  • 编写应用程序测试LED。

55.2 修改设备树文件

我们修改设备树文件/home/topeet/linux/linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk.dtsi,修改test节点如下图所示:

cpp 复制代码
	test:test{
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "test";
		//reg = <4 0x30200000 4 0x30200004>;
		pinctrl-names = "default";
		pinctrl-0 = <&led_gpio>;
		gpios = <&gpio1 13 0>;

	};
&test{
	compatible = "test1234";
	status = "okay";
};

在&pinctrl里面追加pinctrl_beep内容,如下图所示:

修改完后保存文件,参考开发板的使用手册编译源码,然后重新烧写编译好的镜像。

55.3 LED 驱动程序编写

这里我们以iTOP-IMX8MM开发板为例。我们在Ubuntu的/home/topeet/imx8mm/12下新建driver文件,如下图所示。

cpp 复制代码
/*
 * @Author: topeet
 * @Description: LED驱动
 */

#include <linux/init.h>            //初始化头文件
#include <linux/module.h>          //最基本的文件,支持动态添加和卸载模块。
#include <linux/platform_device.h> //platform平台设备相关的头文件
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/miscdevice.h> //包含了miscdevice结构的定义及相关的操作函数。
#include <linux/fs.h>         //文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/uaccess.h>    //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/io.h>         //包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/gpio.h>
#include <linux/of_gpio.h>
int size;
int led_gpio = 0;
int ret = 0;
u32 out_values[2] = {0};
const char *str;
struct device_node *test_device_node;
struct property *test_node_property;

ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    printk("misc_read\n ");
    return 0;
}
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
    /*应用程序传入数据到内核空间,然后控制led的逻辑,在此添加*/

    // kbuf保存的是从应用层读取到的数据
    char kbuf[64] = {0};
    // copy_from_user 从应用层传递数据给内核层
    if (copy_from_user(kbuf, ubuf, size) != 0)
    {
        // copy_from_user 传递失败打印
        printk("copy_from_user error \n "); 
        return -1;
    }
    //打印传递进内核的数据
    printk("kbuf is %d\n ", kbuf[0]); 
    //如果传递进的数据是1,则led亮
    if (kbuf[0] == 1)
    {
        gpio_set_value(led_gpio, 1);
    }
    //如果传递进的数据是0,则led不亮
    else if (kbuf[0] == 0)
        gpio_set_value(led_gpio, 0);
    return 0;
}

int misc_release(struct inode *inode, struct file *file)
{
    printk("hello misc_relaease bye bye \n ");
    return 0;
}
int misc_open(struct inode *inode, struct file *file)
{
    printk("hello misc_open\n ");
    return 0;
}
//文件操作集
struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
};
//杂项设备结构体
struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops,
};
/**
 * @description: platform 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
 * @param {*}pdev : platform 设备
 * @return {*}0,成功;其他负值,失败
 */
int led_probe(struct platform_device *pdev)
{
    printk("led_probe\n");
    //获得设备节点
    test_device_node = of_find_node_by_path("/test");
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error \n");
        return -1;
    }
    /*******我们要使用我们的GPIO就要从设备树来获取**********/

    /*  of_get_named_gpio函数获取 GPIO 编号,
    因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,
    此函数会将设备树中类似<&gpio1 13 1>的属性信息转换为对应的 GPIO 编号 */
    led_gpio = of_get_named_gpio(test_device_node, "gpios", 0);
    if (led_gpio < 0)
    {
        printk("of_get_named_gpio is error \n");
        return -1;
    }
    printk("led_gpio is %d \n", led_gpio);
    //申请一个 GPIO 管脚
    ret = gpio_request(led_gpio, "led");
    if (ret < 0)
    {
        printk("gpio_request is error \n");
        return -1;
    }
    //设置某个GPIO为输出,并且设置默认输出值
    gpio_direction_output(led_gpio, 1);
    //注册杂项设备
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
        printk("misc registe is error \n");
    }
    printk("misc registe is succeed \n");
    return 0;
}

int led_remove(struct platform_device *pdev)
{
    gpio_free(led_gpio);
    printk("led_remove\n");
    return 0;
}
const struct platform_device_id led_idtable = {
    .name = "led_test",
};
const struct of_device_id of_match_table_test[] = {
    {.compatible = "test1234"},
    {},
};
struct platform_driver led_driver = {
    //第三步 在led_driver结构体中完成了led_probe和led_remove
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "led_test",
        .of_match_table = of_match_table_test 
    },
    //第四步 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配
    .id_table = &led_idtable
};

static int led_driver_init(void)
{
    //第一步 我们看驱动文件要从init函数开始看
    int ret = 0;
    //第二步 在init函数里面注册了平台设备驱动platform_driver
    ret = platform_driver_register(&led_driver);
    if (ret < 0)
    {
        printk("platform_driver_register error \n");
    }
    printk("platform_driver_register ok \n");

    return 0;
}
static void led_driver_exit(void)
{
    printk("gooodbye! \n");
    misc_deregister(&misc_dev);
    platform_driver_unregister(&led_driver);
}
module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");

编译驱动代码为驱动模块,如下图所示:

55.4 编写测试APP

编写测试APP程序,完整代码如下所示:

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <stdlib.h>

int main(int argc,char *argv[])

{

int fd;

char buf[64] = {0};//定义buf缓存

//打开设备节点

fd = open("/dev/hello_misc",O_RDWR);

if(fd < 0)

{

//打开设备节点失败

perror("open error \n");

return fd;

}

// atoi()将字符串转为整型,这里将第一个参数转化为整型后,存放在buf[0]中

buf[0] = atoi(argv[1]);

//把缓冲区数据写入文件中

write(fd,buf,sizeof(buf));

printf("buf is %d\n",buf[0]);

close(fd);

return 0;

}

我们将app.c文件拷贝到Ubuntu的/home/topeet/imx8mm/12目录下,输入以下命令编译app.c

55.5 运行测试

我们将55.2章节新编译的镜像烧写进开发板以后,启动开发板。

我们来查看一下添加的设备树节点,请参考本手册51.1查看设备树节点方法章节,如下图所示:

我们进入共享目录,加载驱动模块,如下图所示:

insmod driver.ko

我们再运行应用程序,输入命令"./app 1"LED亮;输入命令"./app 0"LED灭,如下图所示:

./app 0

./app 1

至此,我们已经学会了在设备树中使用pinctrl子系统和GPIO子系统。

相关推荐
Wallace Zhang36 分钟前
STM32F103_Bootloader程序开发11 - 实现 App 安全跳转至 Bootloader
stm32·嵌入式硬件·安全
GodKK老神灭39 分钟前
STM32 CCR寄存器
stm32·单片机·嵌入式硬件
xuanzdhc2 小时前
Linux 基础IO
linux·运维·服务器
愚润求学2 小时前
【Linux】网络基础
linux·运维·网络
bantinghy3 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志4 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手4 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux
小米里的大麦9 天前
014 Linux 2.6内核进程调度队列(了解)
linux·运维·驱动开发
算法练习生9 天前
Linux文件元信息完全指南:权限、链接与时间属性
linux·运维·服务器