提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、设备树中是如何描述引脚的?
- 1.pinctrl子系统
- 2.gpio子系统
- 二、使用步骤
- 总结
前言
RK3568的引脚资源还是相当多的,一共有5组GPIO,每组有A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分,当然这里面也有一些引脚是专用的,比如mipi的引脚,就不能作为普通的引脚使用,本小节就来学习如何在设备树的支持下使用引脚
一、设备树中是如何描述引脚的?
一个引脚的使用无非就是使能该引脚的时钟,复用功能,电特性,中断等操作,这些如果实在裸机中是要自己去查看芯片手册的寄存器,但是这里使用设备树就相对简单了。
1.pinctrl子系统
pinctrl子系统就是专门来初始化引脚的状态的,比如该引脚是否使用复用功能,电特性(上拉、下拉等),对于我们使用者来说,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由pinctrl 子系统来完成,pinctrl 子系统源码目录 drivers/pinctrl
。
不同 soc 厂家的 pin controller 的节点是不一样的,但是这些节点里都是把某些引脚复用成某些功能。如
NXP 的 pinctrl 子系统如下所示:
瑞星微 pinctrl 子系统如下所示:
i2c2 {
/omit-if-no-ref/
i2c2m0_xfer: i2c2m0-xfer {
rockchip,pins =
/* i2c2_sclm0 */
<0 RK_PB5 1 &pcfg_pull_none_smt>,
/* i2c2_sdam0 */
<0 RK_PB6 1 &pcfg_pull_none_smt>;
};
/omit-if-no-ref/
i2c2m1_xfer: i2c2m1-xfer {
rockchip,pins =
/* i2c2_sclm1 */
<4 RK_PB5 1 &pcfg_pull_none_smt>,
/* i2c2_sdam1 */
<4 RK_PB4 1 &pcfg_pull_none_smt>;
};
};
这里解释一下瑞芯微的:
具体的可以参考瑞芯微写好的设备树,或者看帮助文档下面的设备树绑定的文档
2.gpio子系统
前面我们使用pinctrl子系统可以很方便的配置我们要使用的引脚的复用、电特性等操作,但是如果想要在我们驱动中读取某个引脚的值,或者设置引脚的方向的话只靠pinctrl是不够的,这个时候就需要GPIO子系统,有了这个子系统之后,我们就可以轻松的调用内核提供的API来获取到GPIO的资源达到我们控制引脚的目的。
使用gpio还有一个好处:
之前我们控制一个 GPIO 可以直接来操作我们的寄存器,还有一种方法是使用 SOC 厂家实现的配置函数,例如三星的配置函数为 s3c_gpio_cfgpin 等,这样带来的问题就是各家有各家的接口函数与实现方式,不但内核的代码复用率低而且开发者很难记住这么多的函数,如果要使用多种平台的话背函数都是很麻烦的,所以在引入设备树后对 GPIO 子系统进行了大的改造,使用设备树来实现并提供统一的接口。
下面来看看设备树中GPIO长什么样:
leds: leds {
compatible = "gpio-leds";
work_led: work {
lable = "user1";
gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;//表示使用gpio0,B7 高电平有效
linux,default-trigger = "none";
default-state = "off";
};
};
设置好以后,我们就可以调用内核提供的同统一接口来调用GPIO
经过 GPIO 子系统,我们可以通过如下的方式来配置 GPIO
gpio_request(leddev.led0, "led0");
gpio_direction_output(leddev.led0, 1);
gpio_set_value(leddev.led0, 0);
gpio_direction_input(key.irqkeydesc[0].gpio)
gpio_get_value(keydesc->gpio);
gpio_free(leddev.led0);
还有很多gpio相关的函数,具体可以参考#include <linux/gpio.h>
和 #include <linux/of_gpio.h>
二、使用步骤
一般情况下我们会查看对应硬件的引脚原理图,之后使用pinctrl节点来复用引脚(如果没有复用功能可以不用),再之后添加自己的节点调用pinctrl和gpio,这样我们就可以在驱动中调用内核的API函数来使用GPIO:
这里以点灯为例:
原理图:
修改设备树:
这里我把需要的led节点添加到rk3568-evb1-ddr4-v10.dtsi
的/下面
leds: leds {
compatible = "gpio-leds";
work_led: work {
lable = "user1";
gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "none";
default-state = "off";
};
};
由于这个引脚默认就是普通gpio,所以可以不用Pinctrl
编写驱动
#include<linux/init.h>
#include<linux/fs.h>
#include<linux/module.h>
#include<linux/types.h>
#include<linux/kdev_t.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/platform_device.h>
#include<linux/of.h> //设备树相关的节点函数
#include<linux/of_gpio.h> //设备树gpio
#include<linux/interrupt.h> //中断
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#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>
dev_t dev_num;//设备号
struct cdev cdev;
struct class *class;//创建的类
struct device * device;//设备节点
//gpio
int led_num;
static int char_open (struct inode *inode, struct file *file)
{
printk("open dev ok\n ");
return 0;
}
ssize_t char_read (struct file *file, char __user *user, size_t size, loff_t *lofft)
{
return 0;
}
ssize_t char_write (struct file *file, const char __user *ubuf, size_t size, loff_t *lofft)
{
// 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 ", kbuf[0]);
//如果传递进的数据是 1,则 led 亮
if (kbuf[0] == 1)
{
gpio_set_value(led_num, 1);
}
//如果传递进的数据是 0,则 led 不亮
else if (kbuf[0] == 0)
gpio_set_value(led_num, 0);
return 0;
}
struct file_operations fops={
.open = char_open,
.read = char_read,
.write = char_write,
.owner =THIS_MODULE
};
int probe(struct platform_device *dev)
{
int ret;
printk("probe is ok \n");
//获取led的GPIO端口号
led_num = of_get_named_gpio(dev->dev.of_node,"gpios",0);//参数说明:找到的设备树节点结构,从那个属性里获取gpio,索引
if (led_num < 0)
{
printk("of_get_namd_gpio led_num is error \n");
return -1;
}
printk("of_get_namd_gpio led_num is %d\n",led_num);
//设置led的GPIO端口的方向
gpio_direction_output(led_num,0);
//led的GPIO申请
ret = gpio_request(led_num,"led_gpio");
if (ret < 0)
{
printk("gpio_request led_num is error \n");
return -1;
}
//2.注册杂项设备/字符设备
ret = alloc_chrdev_region(&dev_num,0,1,"my_char");//1.自动分配设备号
if(ret < 0)
{
printk("alloc_chrdev_region fale\n");
return -1;
}
printk("alloc_chrdev_region ok\n");
cdev.owner = THIS_MODULE;
cdev_init(&cdev,&fops);//初始化一个cdev结构体
cdev_add(&cdev,dev_num,1);//2.把设备号添加到内核
class = class_create(THIS_MODULE,"my_class");//创建一个类,这个类在/sys/class里面
device = device_create(class,NULL,dev_num,NULL,"my_led_char_platform");//3.在创建的类下面创建设备节点文件
return 0;
}
int remove(struct platform_device *dev)
{
printk("remove is ok \n");
return 0;
}
const struct of_device_id of_match_device_tree[] ={
{.compatible = "myled"},//设备树节点匹配名字,和compatible = "button_interrupt";对应
{},
};
struct platform_driver drv={
.probe = probe,
.driver={
.name = "my_platfrom",//和device.c匹配的名称
.owner =THIS_MODULE,
.of_match_table = of_match_device_tree//和设备树节点匹配的名称
},
.remove = remove
};
static int platfrom_init(void)
{
platform_driver_register(&drv);//1.注册设备驱动平台
return 0;
}
static void platfrom_exit(void)
{
platform_driver_unregister(&drv);
unregister_chrdev_region(dev_num,1);
cdev_del(&cdev);
device_destroy(class,dev_num);
class_destroy(class);
gpio_free(led_num);//释放gpio
printk("byby ok\n");
}
//驱动模块的入口和出口
module_init(platfrom_init);
module_exit(platfrom_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LJM");
编译、加载成功之后会在/dev
目录下出现如下的设备文件
使用如下命令加载模块:insmod driver.ko
编写应用文件
这个就相关简单了;
#include<stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#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/my_led_char_platform",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;
}
之后交叉编译即可:
测试如下:
此时led也会亮
总结
本小节主要是针对设备树如何描述一个引脚,和在驱动中我们如何使用内核提供好的API使用引脚,这个只是看似简单,但也是操作其他的一个基础。