概述
设备树:顾名思义利用树形结构描述设备的文件。
Pinctrl:依据Linux设计分离的思想,简化驱动对芯片引脚的控制。驱动通过pinctrl来控制引脚,各个芯片厂家已经对pinctrl相关代码封装好了。只需要通过文档直接调用相关API即可,大大简化了驱动编写的工作量。
查找相关文档:由于每个厂家的硬件不一样,所以使用方法也略有差异,我们需要通过相关文档查看官方给出的例程。路径为内核目录下的/Documentation/devicetree/bindings/pinctrl
我使用的是三星的芯片,因此只关注samsung-pinctrl.txt
在Pinctrl中添加需要的引脚:打开文件arch/arm/boot/dts/exynos4412-pinctrl.dtsi
自己创建的设备树节点添加的位置,我使用的是 L0-2引脚。
打开4412的datasheet如下:
L0的基地址为0x11000000,因此需要寻找dtsi文件中对应的一级设备树节点。
在官方源码中发现pinctrl1的一级节点地址与之对应。因此我们需要将自己的节点添加到这里。如下所示:
/*添加自己需要用得到的引脚,添加到pinctrl_1下,和1级节点的地址有关,不要加错地方!*/
my_gpio1_high:my_gpio1_high {
samsung,pins = "gpl2-0" ; /*引脚为l2-0*/
samsung,pin-function = <1>; /*引脚功能设置为输出模式 */
samsung,pin-val = <1>; /*引脚初始值设置为1*/
samsung,pin-pud = <EXYNOS_PIN_PULL_UP>; /*引脚设置为上拉模式*/
};
my_gpio1_low:my_gpio1_low {
samsung,pins = "gpl2-0" ; /*引脚为l2-0*/
samsung,pin-function = <1>; /*引脚功能设置为输出模式 */
samsung,pin-val = <0>; /*引脚初始值设置为0*/
samsung,pin-pud = <EXYNOS_PIN_PULL_UP>; /*引脚设置为上拉模式*/
};
这样仅仅是添加了和pinctrl相关的节点,我们还要再设备树的根节点下添加供自己编写的驱动调用的设备树节点。
在相同目录下我们打开文件arch/arm/boot/dts/exynos4412-itop-elite.dts 在根节点下创建自己的一级子节点。
这里需要注意的是此处的pinctrl-0和上面添加代码的pinctrl不是一个意思。这里的pinctrl-0表示对应的pinctrl-name的第一个属性值,如果pinctrl-name有多个属性值,用逗号隔开。那么各个属性值按顺序会依次对应 pinctrl-0,pinctrl-1,pinctrl-2 ………
到这里我们设备树添加设备相关的工作就已经做完。
在内核目录下使用 make dtbs 编译修改后的设备树文件,如果报错可以使用root模式再次尝试,把生成的exynos4412-itop-elite.dtb 文件更新到芯片。打开超级终端查看是否设置成功。
在超级终端中进入linux根文件系统:
在/proc/device-tree目录下有所有的node的信息表明节点已经创建成功
在/sys/devices/platform/目录下有所有设备node的信息。表明设备注册了,最后驱动才能进入probe
编写对应驱动调用GPIO
这里我们需要在代码中编写两部分内容
1:杂项设备的注册,这样才可以生成设备节点供上层应用调用
2:注册驱动,注册驱动的compatible与设备树中的匹配才能调用驱动
驱动源代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/pinctrl/consumer.h>
/*三星对pinctrl功能的头文件,需要在文档查看*/
#include <dt-bindings/pinctrl/samsung.h>
/*定义宏,设置此驱动的节点名字,用于应用层的调用,主要是对上层*/
#define DEVICE_NAME "my_gpio_dev"
/*定义宏,设置设备树节点名字,主要用于驱动匹配设备树*/
#define DRIVER_NAME "my_GPIO1"
struct pinctrl *myGPIO1_pinctrl; /*用于保存驱动返回的pinctrl句柄*/
struct pinctrl_state *myGPIO1_state; /*用于设置引脚的状态,高低电平*/
/****************************************************************************************/
/* 应用层控制相关代码,是为可以让上层调用,挂载到杂项设备下 */
/***************************************************************************************/
/*应用层调用驱动控制函数*/
static long demo_ioctl( struct file *files, unsigned int cmd, unsigned long arg){
printk("cmd is %d,arg is %dn",cmd,arg);
/*低电平,关灯*/
if(arg==0)
{
/*获取相关引脚的属性,这里的name为设备树中 pinctrl-name中的字符串内容*/
myGPIO1_state = pinctrl_lookup_state(myGPIO1_pinctrl,"my_gpio1_low");
/*拉低电平*/
pinctrl_select_state(myGPIO1_pinctrl,myGPIO1_state);
}
/*高电平,点灯*/
else
{
/*获取相关引脚的属性,这里的name为设备树中 pinctrl-name中的字符串内容*/
myGPIO1_state = pinctrl_lookup_state(myGPIO1_pinctrl,"my_gpio1_high");
/*拉低电平*/
pinctrl_select_state(myGPIO1_pinctrl,myGPIO1_state);
}
return 0;
}
/*应用层调用驱动打开文件*/
static int demo_open(struct inode *inode, struct file *file){
printk(KERN_EMERG "demo openn");
return 0;
}
/*驱动注册需要的结构体变量*/
static struct file_operations demo_ops = {
.owner = THIS_MODULE,
.open = demo_open,
.unlocked_ioctl = demo_ioctl,
};
/*TMISC_DYNAMIC_MINOR表示杂项设备的设备号自动分配*/
static struct miscdevice demo_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &demo_ops,
};
/**************************************************************************************/
/* 驱动注册相关代码 */
/*************************************************************************************/
/*注册驱动函数,设备加载正常代码会自动进入probe执行里面的内容*/
static int myGPIO1_probe(struct platform_device * pdev)
{
int ret; /*保存错误信息返回值*/
/*成功进入probe*/
printk("myGPIO1 init successfuln");
/*注册杂项设备*/
misc_register(&demo_dev);
/*驱动挂载成功后打印驱动名字和驱动ID*/
printk(KERN_EMERG "pdv->name is %sn",pdev->name);
printk(KERN_EMERG "pdv->id is %dn",pdev->id);
pdev->dev.release(&pdev->dev);
/*linux内核devm机制,用于自动申请内存和释放内存*/
myGPIO1_pinctrl = devm_pinctrl_get(&pdev->dev);
/*返回句柄失败,因为这个句柄为指针类型,因此Linux提供IS_ERR函数用于转换类型*/
if (IS_ERR(myGPIO1_pinctrl)) {
printk("myGPIO1_pinctrl,failed,%dn",PTR_ERR(myGPIO1_pinctrl));
return -1;
}
/*获取相关引脚的属性,这里的name为设备树中 pinctrl-name中的字符串内容*/
myGPIO1_state = pinctrl_lookup_state(myGPIO1_pinctrl,"my_gpio1_high");
if (IS_ERR(myGPIO1_state)) {
printk("myGPIO1_state,failed,%dn",PTR_ERR(myGPIO1_state));
return -1;
}
/*设置引脚状态,这里可以控制引脚的高低电平*/
ret = pinctrl_select_state(myGPIO1_pinctrl, myGPIO1_state);
if(ret<0){
printk("pinctrl_select_state,failedn");
return -1;
}
printk("my_GPIO1 init okn");
return 0;
}
/*驱动注销*/
static int myGPIO1_remove(struct platform_device * pdev)
{
printk(KERN_ALERT "remove!n");
/*卸载杂项设备*/
misc_deregister(&demo_dev);
return 0;
}
/*从设备树获取ID,用于匹配驱动设备节点*/
static const struct of_device_id of_myGPIO1_dt_match[] = {
{.compatible = DRIVER_NAME},
{},
};
MODULE_DEVICE_TABLE(of,of_myGPIO1_dt_match);
/*驱动相关结构体*/
static struct platform_driver myGPIO1_driver = {
.probe = myGPIO1_probe,
.remove = myGPIO1_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_myGPIO1_dt_match,
},
};
/*模块加载函数*/
static int myGPIO1_init(void)
{
printk(KERN_ALERT "myGPIO1_init_modules!n");
return platform_driver_register(&myGPIO1_driver);
return 0;
}
/*模块卸载函数*/
static void myGPIO1_exit(void)
{
/*获取相关引脚的属性,这里的name为设备树中 pinctrl-name中的字符串内容*/
myGPIO1_state = pinctrl_lookup_state(myGPIO1_pinctrl,"my_gpio1_low");
/*拉低电平*/
pinctrl_select_state(myGPIO1_pinctrl,myGPIO1_state);
/*释放申请的引脚*/
devm_pinctrl_put(myGPIO1_pinctrl);
printk(KERN_ALERT "myGPIO1_exit_modules!n");
platform_driver_unregister(&myGPIO1_driver);
}
module_init(myGPIO1_init);
module_exit(myGPIO1_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Xiao Xiao ji shu zhai");
编写驱动Makefile文件:
源代码如下:
KERNELDIR := /home/yl/linux/Yang4412/Kernel_DTS/itop4412_kernel_4_14_2_bsp/linux-4.14.2_iTop-4412_scp
CURRENT_PATH := $(shell pwd)
obj-m := GPIO_pinctrl.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
在控制台执行 make 命令 编译出 GPIO_pinctrl.ko 文件
打开超级终端加载模块测试驱动:
输入命令:insmod GPIO_pinctrl.ko
输入命令 cd /dev
在/dev下ls 查看设备节点是否成功添加
此时设备节点已经成功添加。
编写linux应用调用此节点:
应用源代码如下:
/*调用printf等基本函数*/
#include <stdio.h>
/*基本系统数据类型,可以自动判断系统位数*/
#include <sys/types.h>
/*系统调用函数的头文件*/
#include <sys/stat.h>
/*定义了open函数*/
#include <fcntl.h>
/*定义了close函数*/
#include <unistd.h>
/*定义了ioctl函数*/
#include <sys/ioctl.h>
/*定义节点路径,用于上层调用驱动文件*/
#define NODE_PATH "/dev/my_gpio_dev"
int main(int argc, char *argv[])
{
/*定义了文件句柄,方便后面对文件处理。相当于文件的id*/
int fd;
/*用于存储传入的数值*/
int value;
/*定义了文件路径,因为驱动注册的设备节点在这里*/
char *demo_node = NODE_PATH;
value=atoi(argv[1]);
/*O_RDWR只读打开,O_NDELAY非阻塞方式,首先要打开文件获得句柄,打开以后才能对文件进行操作*/
if((fd = open(demo_node,O_RDWR|O_NDELAY))<0){
printf("APP open %s failedn",demo_node);
}
/*文件打开成功以后向文件发送参数,驱动会处理收到的参数*/
else{
printf("APP open %s successn",demo_node);
printf("argc = %dn",argc);
printf("value = %dn",value);
ioctl(fd,0,value);
}
/*关闭文件*/
close(fd);
}
编写Makefile文件源代码如下:
#定义一个变量赋值给objects
objects = Demo_APP.c
#可执行文件是依赖于赋值的这个C文件生成的
Demo_APP: $(objects)
#使用交叉编译器变异出可执行文件,注意这里的编译器需要在 /etc/profile 中的最后一行声明,这里用的是arm-2009q3。重启以后使用arm查看
arm-none-linux-gnueabi-gcc -o Demo_APP $(objects) -static
.PHONY : clean
clean:
rm Demo_APP
在控制台执行make命令 得到 Demo_APP文件
在超级中断测试:
输入命令 ./Demo_APP 0
Led灯熄灭
输入命令 ./Demo_APP 1
Led灯点亮
至此就成功利用设备树点亮了夜空中最亮的灯!
最后
以上就是优秀小蚂蚁为你收集整理的三星4412利用设备树pinctrl控制GPIO之夜空中最亮的灯 的全部内容,希望文章能够帮你解决三星4412利用设备树pinctrl控制GPIO之夜空中最亮的灯 所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复