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子系统。