目录
pinctl子系统(引脚控制相关)
区别
- 以前我们都是控制寄存器的值来控制gpio,这是一种方法,但用了设备树后,厂家会提供对应的设备树文件,里面都会定义好了一些功能,也定义好了功能需要用到的gpio,我们调用就可以了,这就是pinctl子系统
功能
- 管理系统中所有的可以控制的 pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些 pin。
- 管理这些pin 的复用。对于 SOC而言,其引脚除了配置成普通的 GPIO 之外,若干个引脚还可以组成一个 pin group,形成特定的功能。
- 配置这些 pin 的特性。例如使能或关闭引脚上的 pull-up、pull-down 电阻,配置引脚的 driver strength。
如何与设备树关联在一起
- 我们可以把某一个外设要用到的gpio都写到一个节点里
- 我们要用的时候在从这个节点里取出来
- 不同厂家的pin controller 的节点都是把某些引脚复用成某些功能
在设备树中如何描述
- 在根节点中找到一个名为 iomuxc 的节点
&iomuxc{ pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hog_1>; imx6ul-evk{ pinctrl_hog_1: hoggrp-1{//功能1 fsl,pins = <//引脚配置 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0X17059//SD1 CD MX6UL_PAD__GPIO1_IO05__USDHC1_VSELECT 0X17059//SD1 VSELECT MX6UL_PAD__GPIO1_IO00__ANATOP_OTG1_ID 0x13058//USB_OTG1_ID >; }; pinctrl_csi1: csi1grp{ fsl,pins = < ...... >; }; pinctrl_enet1: enet1grp { fsl,pins = < ...... >; }; pinctrl_enet2: enet2grp{ fsl,pins = < ...... >; }; } }
- 节点中有很多功能,每个功能都定义了相对应的引脚,引脚定义中的前面是宏定义,后面是定义属性
- 不同开发板定义的结构不一样,有的省去了fsl,pins这个属性,有些是变换了属性的名字,但都是这一个意思,把功能用到的引脚都定义在一起
- 不同开发板的属性都是什么意思可以在/linux源码目录/Documentation/devicetree/bindings下面的txt文档查看
如何使用
例子1
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
- 含义
- 第一行:设备的状态,可以有多个状态,default为状态0
- 第二行:第0个状态所对应的引脚配置,也就是default状态对应得引脚在pin controller里面定义好的节点pinctrl_hog_1里面的管脚配置
例子2
pinctrl_names = "default", "wake up";
pinctrl-0 = <&pinctrl_hog_1>;
pinctrl-1 = <&pinctrl_hog_2>;
- 含义
- 第一行:设备的状态,可以有多个状态,default为状态0,wake up为状态1
- 第二行:第0个状态所对应的引脚配置,也就是 default 状态对应的引脚在 pin controller 里面定义好的节点 pinctrl_hog_1 里面的管脚配置
- 第三行:第1个状态所对应的引脚配置,也就是 wake up 状态对应的引脚在 pin controller 里面定义好的节点 pinctrl_hog_2 里面的管脚配置
例子3
pinctrl_names = "default";
pinctrl-0 = <&pinctrl_hog_1
&pinctrl_hog_2>;
- 含义
- 第一行:设备的状态,可以有多个状态,default为状态0
- 第二行:第0个状态所对应的引脚配置,也就是 default 状态对应的引脚在 pin controller 里面定义好的节点,对应pinctrl_hog_1 和 pinctrl_hog_2 这俩个节点的管脚配置
gpio子系统(通用输入输出)
功能
- Linux 内核提供了 pinctrl 子系统和 gpio 子系统用于 GPIO 驱动,
- 当然 pinctr! 子系统负责的就不仅仅是 GPIO 的驱动了而是所有 pin脚的配置。
- pinctr! 子系统是随着设备树的加入而加入的,依赖于设备树。G
- PIO 子系统在之前的内核中也是存在的,但是 pinctr 子系统的加入 GPIO 子系统也是有很大的改变在以前的内核版本中,
- 如果要配置 GPIO 的话一般要使用 SOC 厂家实现的配置函数例如三星的配置函 s3c_gpio_cfgpin 等,
- 这样带来的问题就是各家有各家的接口函数与实现方式,不但内核的代码复用率低而且开发者很难记住这么多的函数,
- 如果要使用多种平台的话背函数都是很麻烦的,所以在引入设备树后对 GPIO 子系统进行了大的改造,使用设备树来实现并提供统一的接口。
- 通过 GPIO 子系统功能主要实现引脚功能的配置,如设置为 GPIO特殊功能,GPIO 的方向,设置为中断等。
如何在设备树中描述gpio
test1:test{
#address-cells = <1>;
#size-cells = <1>;
compatible = "test";
reg = <0x20ac000 0x0000004>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_test>;
test-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
};
- 新建一个节点
- 最后一行表示,在gpio1组里面的第3个gpio口低电平
gpio常用api
gpio_request
- 申请一个GPIO管脚
- 原型:
int gpio_request(unsigned gpio,const char* label)
- 参数:
- gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数(前面19章有讲)从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。
- label:给 gpio 设置个名字。
- 返回值:0申请成功,其他值申请失败。
gpio_free
- 如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。
- 原型:
void gpio_free(unsigned gpio)
- 参数:
- gpio:要释放的 gpio 标号
- 返回值:无
gpio_direction_input
- 此函数用于设置某个 GPIO 为输入
- 原型:
int gpio_direction_input(unsigned gpio)
- 参数:
- gpio:要设置的 gpio 标号
- 返回值:0成功,负值失败
gpio_direction_output
- 此函数用于设置某个 GPIO 为输出,并设置默认输出值
- 原型:
int gpio_direction_output(unsigned gpio)
- 参数:
- gpio:要设置的 gpio 标号
- 返回值:0成功,负值失败
gpio_get_value
- 此函数用于获取某个 GPIO 的值(0或1)
- 原型:
int gpio_get_value(unsigned gpio)
- 参数:
- gpio:要获取的 gpio 标号
- 返回值:成功返回gpio值,失败负值
gpio_set_vlaue
- 此函数用于设置某个 GPIO 的值(0或1)
- 原型:
void gpio_set_vlaue(unsigned gpio,int value)
- 参数:
- gpio:要设置的 gpio 标号
- value:要设置的值
- 返回值:无
实战(用pinctl和gpio来控制蜂鸣器引脚)
步骤
- 先在设备树根节点中添加我们测试的节点,我们使用的是前面添加的自定义节点
- 在之前的自定义节点之前添加pinctl子系统属性
- 设备状态
- 定义状态所对应的节点
- 定义gpio的属性,到时用来将这个属性转换为上面gpio要使用的gpio标号
/{ test1:test{ //别名为test1,节点名称为test compatible = "test";//匹配的名字 #address-cells = <1>; #size-cells = <1>; reg = <0x20ac000 0x0000004>;//寄存器地址 pinctrl-names = "default";//设备状态 pinctrl-0 = <&pinctrl_beep>;//状态绑定节点 beep-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;//申请gpio所需的属性 }; }; &test1{ compatible = "test1";//匹配的名字 status = "okay"; };
- 在之前的自定义节点之前添加pinctl子系统属性
- 添加状态对应的新节点,在iomuxc或者iomuxc_snvs节点中添加
- 找到功能对应引脚的gpio口名字
- 在手册上找到对应名字的gpio口复用成gpio第几组的第几个io口
- 仿照其他的节点来写
- 但对应的宏定义在对应板中的pinfunc.h中查找到,每个宏定义都代表那个gpio口复用成对应的功能,如iic,usart,spi等等,宏定义其实是地址偏移量
- 电气属性就是gpio口寄存器配置成什么样的值
- 检查用到的宏定义对应gpio口的其他功能宏定义是否在其他地方用到,用ctrl+f来查询
- 如果定义了,就要找到对应的节点,把该功能注释掉,才不影响我们的使用
pinctrl_beep:beep{ fsl,pins = < 对应gpio口要复用功能的宏定义 电气属性 >; };
- 如果定义了,就要找到对应的节点,把该功能注释掉,才不影响我们的使用
- 在driver.c中添加我们调用的代码
- 在probe函数中
- 获取节点的名称
- 申请gpio标签
- 申请gpio
- 设置gpio输入还是输出
- 注册杂项设备或者字符设备来控制gpio(这里示例注册杂项设备)
- 注册杂项设备
- 定义miscdevice杂向设备结构体
- 定义文件操作集结构体和各个函数
- 其中在写函数中判断写入的字符来进行拉低还是拉高
设置gpio输入还是输出
- 在exit函数中
- 注销杂项设备
- 释放gpio
#include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/uaccess.h> #include <linux/cdev.h> #include <linux/io.h> #include <linux/device.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/gpio.h> #include <linux/of_gpio.h> int size; u32 out_value[2]={0}; const char* str; int beep_gpio = 0; struct device_node *test_device_node; struct property *test_node_property; unsigned int *vir_addr; int misc_open(struct inode *inode,struct file *file) { printk("hello misc_open\n"); return 0; } int misc_release(struct inode*inode,struct file *file) { printk("hello misc_release bye bye\n"); return 0; } ssize_t misc_read(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t) { char kbuf[64] = "heheh"; if (copy_to_user(ubuf,kbuf,strlen(kbuf)) != 0) { printk("copy_to_user error\n"); return -1; } return 0; } ssize_t misc_write(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t) { char kbuf[64] = {0}; if (copy_from_user(kbuf,ubuf,size) != 0) { printk("copy_from_user error\n"); return -1; } printk("kbuf is %s\n",kbuf); if (kbuf[0] == 1) { gpio_set_value(beep_gpio,1); }else{ gpio_set_value(beep_gpio,0); } 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 } int beep_probe(struct platform_device *pdev) { printk("beep_probe\n"); int ret = 0; //获取节点的名称 test_device_node = of_find_node_by_path("/test"); if (test_device_node==NULL) { printk("of_find_node_by_path error \n"); return -1; } printk("test_device_node is %s\n",test_device_node->name); //申请gpio标签 beep_gpio = of_get_named_gpio(test_device_node,"beep-gpio",0); if (beep_gpio < 0) { printk("of_get_named_gpio error \n"); return -1; } printk("beep_gpio is %s\n",beep_gpio); //申请gpio ret = gpio_request(beep_gpio,"beep");//beep为起的名字 if (ret < 0) { printk("gpio_request error \n"); return -1; } //设置gpio输入还是输出 gpio_direction_output(beep_gpio,1); //注册杂项设备和字符设备来操控gpio //杂项 //注册杂项设备 return 0; } int beep_remove(struct platform_device *pedv) { printk("beep_remove\n"); return 0; }; const struct platform_device_id beep_id_table = { .name = "123" }; const struct of_device_id *of_match_table_test[] = { {.compatible = "test"}, {} }; struct platform_driver beep_device = { .probe = beep_probe, .remove = beep_remove, .driver = { .owner = THIS_MODULE, .name = "beep_test" .of_match_table= of_match_table_test; }, .id_table = &beep_id_table }; static int beep_driver_init(void) { int ret = 0; ret = platform_driver_register(&beep_device); if (ret<0) { printk("platform_driver_register error\n"); return ret; } printk("platform_driver_register ok\n"); return 0; } static void beep_driver_exit(void) { printk("beep goodbye"); //注销杂项设备 misc_deregister(&misc_dev); //释放gpio gpio_free(beep_gpio); platform_driver_unregister(&beep_device); } module_init(beep_driver_init); module_exit(beep_driver_exit); MODULE_LICENSE("GPL");
- 在probe函数中
- 检查设备树中节点的compatible属性与driver.c中的name匹不匹配
- 编译driver.c成ko模块,放到开发板中
- 将修改后的设备树编译成dtb,烧写进开发板
- 加载ko模块,确保不会报错
- 查看/dev/目录里有没有生成hello_misc节点
- 写一个.c来验证能否写入控制
- 打开节点,写入参数
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(*int argc,char *argv[]) { int fd; char buf[64] = {0}; fd = open("/dev/hello_misc",O_RDWR); if (fd < 0) { perror("open error\n"); return fd; } buf[0] = atoi(argv[1]); write(fd,buf,sizrof(buf)); close(fd); return 0; }
- 打开节点,写入参数
- 交叉编译,./app 1 ./app 0测试