一、pinctl和gpio子系统
pinctrl子系统
设置引脚的复用关系和电气属性。
2、Linux Pinctrl 子系统提供的功能是什么?
( 1 ) 管理系统中所有的可以控制的 pin , 在系统初始化的时候,枚举所有可以控制的 pin , 并标识这些 pin 。
( 2 ) 管理这些 pin 的复用( Multiplexing )。对于 SOC 而言,其引脚除了配置成普通 的 GPIO 之外,若干个引脚还可以组成一个 pin group,形成特定的功能。
( 3 ) 配置这些 pin 的特性。例如使能或关闭引脚上的 pull-up 、pull-down 电阻,配置引脚的 driver strength 。
3、不同 soc 厂家的 pin controller 的节点这些节点里都是把某些引脚复用成某些功能。
4、不同 soc 厂家的 pin controller 的节点里面的属性都是什么意思?
可以通过 Linux 源码目录 /Documentation/devicetree/bindings 下的 txt 文档查看。
5、怎么在代码里面使用 pin controller 里面定义好的节点?
例 1 :
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
含义:
<1>pinctrl-names = “default”;
设备的状态,可以有多个状态, default 为状态 0
<2>pinctrl-0 = <&pinctrl_hog_1>;
第 0 个状态所对应的引脚配置,也就是 default 状态对应的引脚在 pin controller 里面定义好 的节点 pinctrl_hog_1 里面的管脚配置
例 2 :
pinctrl-names = "default" , "wake up";
pinctrl-0 = <&pinctrl_hog_1>;
pinctrl-1 = <&pinctrl_hog_2>;
含义
<1>pinctrl-names = “default” , “wake up”;
设备的状态,可以有多个状态, default 为状态 0 , wake up 为状态 1
<2>pinctrl-0 = <&pinctrl_hog_1>;
第 0 个状态所对应的引脚配置,也就是 default 状态对应的引脚在 pin controller 里面定义好 的节点 pinctrl_hog_1 里面的管脚配置
<3>pinctrl-1 = <&pinctrl_hog_2>;
第 1 个状态所对应的引脚配置,也就是 wake up 状态对应的引脚在 pin controller 里面定义 好的节点 pinctrl_hog_2 里面的管脚配置
例 3 :
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1 &pinctrl_hog_2>;
含义
<1>pinctrl-names = “default”; 设备的状态,可以有多个状态, default 为状态 0
<2>pinctrl-0 = <&pinctrl_hog_1 &pinctrl_hog_2>;
第 0 个状态所对应的引脚配置,也就是 default 状态对应的引脚在 pin controller 里面定义好 的节点 , 对应pinctrl_hog_1 和 pinctrl_hog_2 这俩个节点的管脚配置。
gpio 子系统
那么我们先来看一下怎么在设备树中 pinctrl 和 gpio 子系统描述一个 gpio 。
当pinctrl 把引脚设置为gpio子系统之后,就可以用gpio子系统来控制引脚了。
<1> 设备树使用 pinctrl 和 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>;
};
<2> 常用 gpio 子系统提供的 api 函数
1 、 gpio_request 函数
作用: gpio_request 函数用于申请一个 GPIO 管脚
函数原型:
int gpio_request(unsigned gpio, const char *label)
参数:
gpio : 要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属 性信息,此函数会返回这个 GPIO 的标号。
label : 给 gpio 设置个名字。 返回值: 0 , 申请成功;其他值,申请失败。
2 、 gpio_free 函数
作用:如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。
函数原型: void gpio_free(unsigned gpio)
参数: gpio : 要释放的 gpio 标号。
返回值:无。
3 、 gpio_direction_input 函数
作用: 此函数用于设置某个 GPIO 为输入
函数原型:
int gpio_direction_input(unsigned gpio)
参数 : gpio : 要设置为输入的 GPIO 标号。
返回值:设置成功返回 0 ; 设置失败返回负值
4 、 gpio_direction_output 函数
作用:此函数用于设置某个 GPIO 为输出,并且设置默认输出值
函数原型:
int gpio_direction_output(unsigned gpio, int value)
参数:
gpio : 要设置为输出的 GPIO 标号。
value : GPIO 默认输出值。 返回值:设置成功返回 0 ; 设置失败返回负值
5 、 gpio_get_value 函数
作用: 此函数用于获取某个 GPIO 的值 (0 或 1)
函数原型:
int __gpio_get_value(unsigned gpio)
参数:
gpio : 要获取的 GPIO 标号。
返回值:成功返回 GPIO 值,失败返回负值。
6 、 gpio_set_value 函数
作用: 此函数用于设置某个 GPIO 的值 函数原型:
void __gpio_set_value(unsigned gpio, int value)
参数: gpio : 要设置的 GPIO 标号。
value : 要设置的值。 返回值:无
7 、 of_get_named_gpio
函数作用: 此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编 号,此函数会将设备树中类似 <&gpio1 3 GPIO_ACTIVE_LOW> 的属性信息转换为对应的 GPIO 编号,函数原型如下:
int of_get_named_gpio(struct device_node *np,const char *propname,int index)
参数:
np : 设备节点。
propname : 包含要获取 GPIO 信息的属性名。
index : 因为一个属性里面可能包含多个 GPIO ,此参数指定要获取哪个 GPIO 的编号,如果 只有一个 GPIO 信息的话此参数为 0 。
返回值:
成功返回到的 GPIO 编号,失败返回一个负数。
实践课
使用gpio子系统控制蜂鸣器,基本配置文件如下所示:
.
.
.
.
.
.
.
beep_gpio = of_get_named_gpio(test_device_node,"beep_gpio",0);
if (beep_gpio < 0){
printk("of_get_named_gpio is erron\n");
return -1;
}
printk("beep_gpio is %d\n",beep_gpio);
ret = gpio_request(beep_gpio,"beep_gpio");
if (ret<0)
{
printk("gpio_request is erron\n");
return -1;
}
gpio_direction_output(beep_gpio,1);//引脚配置
ret = misc_register(&misc_dev);//注册杂项设备
if (ret<0)
{
printk("misc_register is error\n");
return -1;
}
在杂项设备中,颠覆了以往使用蜂鸣器,而使用gpio子系统控制蜂鸣器。
if (kbuf[0] == 1)
gpio_set_value(beep_gpio,1);//open 寄存器操作换成了GPIO子系统的函数
else if (kbuf[0] == 0)
gpio_set_value(beep_gpio,0);//close
ioctl接口
unlocked_ioctl 接口
1、什么是 unlocked_ioctl 接口?
unlocked_ioctl 就是 ioctl 接口,但是功能和对应的系统调用均没有发生变化。
2、 unlocked_ioctl 和 read/write 函数有什么相同点和不同点?
相同点:都可以往内核写数据。
不同点: read 函数只能完成读的功能,write 只能完成写的功能。 但是读取大数据的时候效率高。
ioctl 既可以读也可以写。读取大数据的时候效率不高。
3、unlocked_ioctl 接口命令规则。
一共是32位。
第一个分区:0-7,命令的编号,范围是 0-255。
第二个分区:8-15 ,命令的幻数。
第一个分区和第二个分区主要作用是用来区分命令的。编号一样,幻数不能一样。
第三个分区: 16-29 表示传递的数据大小。
第四个分区: 30-31 代表读写的方向。因为ioctl既可以读也可以写。
00 : 表示用户程序和驱动程序没有数据传递
10 : 表示用户程序从驱动里面读数据
01 : 表示用户程序向驱动里面写数据
11 : 先写数据到驱动里面,然后在从驱动里面把数据读出来。(用得不多)
4、 命令的合成宏与分解宏
合成宏:
_IO(type,nr) : 用来定义没有数据传递的命令
_IOR(type,nr,size) : 用来定义从驱动中读取数据的命令
_IOW(type,nr,size) : 用来定义向驱动写入数据的命令
_IOWR(type,nr,size) :用来定义数据交换类型的命令,先写入数据,再读取数据这类命令。
参数: type : 表示命令组成的魔数,也就是 8~15 位
nr : 表示命令组成的编号,也就是 0~7 位
size : 表示命令组成的参数传递大小,注意这里不是传递数字,而是数据类型,如要传递 4 字节,就可以写成 int
分解宏:
_IOC_DIR(nr) 分解命令的方向,也就是上面说 30~31 位的值
_IOC_TYPE(nr) 分解命令的魔数,也就是上面说 8~15 位的值
_IOC_NR(nr) 分解命令的编号,也就是上面说 0~7 位
_IOC_SIZE(nr) 分解命令的复制数据大小,也就是上面说的 16~29 位
参数说明: nr : 要分解的命令
实践课
通过应用层传递参数到驱动层控制硬件io。
驱动层代码:
#define CMD_TEST0 _IO('L',0)
#define CMD_TEST1 _IO('L',1)
#define CMD_TEST2 _IOW('L',2,int)
#define CMD_TEST3 _IOW('L',3,int)
#define CMD_TEST4 _IOW('L',4,int)
long misc_ioctl(struct file *file,unsigned int cmd, unsigned long value)
{
int val;
switch(cmd){
case CMD_TEST0:
printk("LED ON!!!\n");
printk("value is %ld\n",value);
break;
case CMD_TEST1:
printk("LED OFF!!!\n");
printk("value is %ld\n",value);
break;
case CMD_TEST4:
val = 12;
//从驱动中读取数据
if(copy_to_user((int *)value,&val,sizeof(val))!=0)
{
printk("copy to user error\n");
return -1;
}
break;
}
return 0;
}
struct file_operations misc_fops ={
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_close,
.read = misc_read,
.write = misc_write,
.unlocked_ioctl = misc_ioctl//添加结构体成员
};
应用层代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define CMD_TEST0 _IO('L',0)
#define CMD_TEST1 _IO('L',1)
// #define CMD_TEST1 _IO('A',1)
#define CMD_TEST2 _IOW('L',2,int)
#define CMD_TEST3 _IOW('L',3,int)
#define CMD_TEST4 _IOW('L',4,int)
// #define CMD_TEST3 _IOR('L',3,int)
int main(int argc, char **argv)
{
int fd;
int value;
fd = open("/dev/hello_misc",O_RDWR);
if (fd < 0)
{
printf("open error\n");
return fd;
}
while (1){
ioctl(fd,CMD_TEST4,&value);
printf("value is %d\n",value);
sleep(2);
}
return 0;
}