<1>获取设备树中 pin 信息,管理系统中所有的可以控制的 pin, 在系统初始化的时候, 枚举所有可以控制的 pin, 并标识这些 pin。 <2>根据获取到的 pin 信息来设置 pin 的复用功能,对于 SOC 而言, 其引脚除了配置成普通的 GPIO 之外,若干个引脚还可以组成一个 pin group, 形成特定的功能。 <3>根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对应使用者来说,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成。
当使用 pinctrl 子系统将引脚的复用设置为 GPIO,可以使用 GPIO 子系统来操作GPIO,Linux 内核提供了 pinctrl 子系统和 gpio 子系统用于 GPIO 驱动。
通过 GPIO 子系统功能要实现:
<1>引脚功能的配置(设置为 GPIO,GPIO 的方向, 输入输出模式,读取/设置 GPIO 的值) <2>实现软硬件的分离(分离出硬件差异, 有厂商提供的底层支持; 软件分层。 驱动只需要调用接口 API 即可操作 GPIO) <3>iommu 内存管理(直接调用宏即可操作 GPIO)
gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。
这些节点都是把某些引脚复用成功能。
可以通过在Documentation/devicetree/bindings/下的txt文档查看。
例1:
pinctrl-names = "default";//设备的状态,可以有多种状态,default为状态0
pinctrl-0 = <&pinctrl_hog_1>;/*第0个状态所对应的引脚配置,也就是default状态对应的引脚在pin controller里面定义好的节点pinctrl_hog_1里面的管脚配置。*/
例2:
pinctrl-names = "default","wake up";//设备的状态,可以有多种状态,default为状态0,wake up为状态1,
pinctrl-0 = <&pinctrl_hog_1>;/*第0个状态所对应的引脚配置,也就是default状态对应的引脚在pin controller里面定义好的节点pinctrl_hog_1里面的管脚配置。*/
pinctrl-1 = <&pinctrl_hog_2>;/*第1个状态所对应的引脚配置,也就是default状态对应的引脚在pin controller里面定义好的节点pinctrl_hog_2里面的管脚配置。*/
例3:
pinctrl-names = "default";//设备的状态,可以有多种状态,default为状态0,wake up为状态1,
pinctrl-0 = <&pinctrl_hog_1 &pinctrl_hog_2>;/*第0个状态所对应的引脚配置,也就是default状态对应的引脚在pin controller里面定义好的节点pinctrl_hog_1和pinctrl_hog_2这两个节点的管脚配置。*/
总结:之前控制引脚的方法都是操作配置寄存器:
现在可以不用这种方法,linux有现成的框架,这个框架就是pinctl子系统和gpio子系统,可以pinctl子系统设置引脚的复用功能,设置引脚的电气属性。
上一个小节我们学习了pinctrl子系统,Linux内核提供了pinctrl子系统和gpio子系统用于GPIO驱动,当然pinctrl子系统负责不仅仅是GPIO的驱动,而是所有pin脚配置。pinctrl子系统是随着设备树的加入而加入的,依赖设备树。GPIO子系统在之前的内核也是存在的,但是pinctrl子系统的加入使得GPIO子系统有很大的改变。
在以前的内核版本中,如果要配置GPIO的话一般要使用SOC厂家实现的配置函数,例如三星的配置函数s3c_gpio_cfgpin等,这样带来的问题就是各家有个家的接口函数与是实现方式,不但内核的代码复用率低而且开发者很难记住这么多的函数,如果要使用多种平台的话背函数都是很麻烦的,所以在引入设备树后对GPIO子系统进行大的改造,使用设备树来实现并提供统一的接口。通过GPIO子系统功能主要实现引脚功能的配置,如设置为GPIO,特殊功能,GPIO的方向,设置为中断等。
那么我们先来看一下怎么在设备树中pinctrl和gpio子系统描述一个gpio。
test1:test{
#address-cells = <1>;
#size-cells = <1>;
compatible = "test";
reg = <0x20ac000 0x00000004>;//描述数据寄存器的地址
pinctrl-names = "default";
pintrl-0 = <&pinctrl_test>;
test-gpio = ;//gpio 表示第一组,3表示第一组第三个引脚,GPIO_ACTICE_LOW表示低电平
};
这些函数的定义在include\linux\gpio.h
作用: gpio_request函数用于申请一个gpio管脚。
int gpio_request(unsigned gpio, const char *label)
参数:
gpio:要申请的gpio标号,使用of_get_named_gpio函数从设备树获取指定的GPIO属性信息,此函数会返回这个GPIO标号。
label:给gpio设置个名字。
返回值:0,申请成功,其他值申请失败。
作用:如果不使用某个GPIO了,那么就可以调用gpio_free函数进行释放。
void gpio_free(unsigned gpio);
参数:
gpio:要释放的gpio标号。
返回值:无
作用:此函数用于设置某个GPIO为输入。
int gpio_direction_input(unsigned gpio);
参数:
gpio:要设置为输入的GPIO标号。
返回值:0,设置成功,其他值设置失败。
作用:此函数用于设置某个GPIO为输出,并且设置默认输出值。
int gpio_direction_output(unsigned gpio, int value);
参数:
gpio:要设置为输出的GPIO标号。
value:GPIO默认输出值。
返回值:0,设置成功,设置失败返回负值。
作用:此函数用于获取某个GPIO的值(0或1)
int gpio_get_value(unsigned int gpio);
gpio:要获取的gpio标号
返回值:0,成功,失败返回负值。
作用:此函数用于获取某个GPIO的值(0或1)
void gpio_set_value(unsigned int gpio, int value);
gpio:要设置的gpio标号
value:要设置的值。
返回值:无。
pinctl子系统的作用就是设置引脚的复用功能和电气属性。gpio子系统就是当pinctl子系统把引脚设置成GPIO功能以后就可以使用gpio子系统来操作我们引脚了,比如说设置输入、输出或者引脚的高低电平等等。
pinctrl子系统就是设置引脚的复用关系和电气属性,gpio子系统就是当pinctrl把引脚设置成设置为gpio以后我们使用gpio子系统来操作gpio。
在arch/arm/boot/dts/imx6ul-pinfunc.h:
每个宏定义都对应一个管脚的复用功能。一个引脚有怎么多复用功能,但是只能使能一个,所以在设备树下需要检察是否有其他复用功能被使用,有就需要在设备树文件其他使用的地方注释掉:
Linux驱动学习—设备树及设备树下的platform总线-CSDN博客
实现设备树学习中的7.3未实现的部分,即在probe函数注册一个杂项设备驱动用于对蜂鸣器的操作。这里对引脚的操作不是相之前一样对地址寄存器的操作实现gpio电平值的改变,而是通过gpio子系统的api函数是实现。
#include
#include
#include
#include
#include
struct device_node *test_device_node;
struct property *test_node_property;
int size;
u32 out_values[2]={0};
const char *str=NULL;
unsigned int *vir_gpio_dr;
int beep_gpio = 0;
static const of_device_id of_match_table_test[] = {//匹配表
{.compatible = "test1234"},
};
static const platform_device_id beep_id_table ={
.name = "beep_test",
};
int misc_open(struct inode *inode, struct file *file)
{
printk("misc_open\n");
return 0;
}
int misc_release(struct inode *inode, struct file *file)
{
printk("misc_relese\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_wirie(struct file *file,char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = {0};
if(copy_form_user(kbuf, ubuf, strlen(kbuf)) != 0) {
printk("copy_form_user error\n");
return -1;
}
printk("kbuf is %s\n",kbuf);
if(kbuf[0] == 1)
get_set_value(beep_gpio, 1);
else if(kbuf[0] == 0)
get_set_value(beep_gpio, 0);
return 0;
}
struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.write = misc_wirie,
.read = misc_read
};
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = hello_misc,
.fops = &misc_fops
};
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{
int ret = 0;
printk("beep_probe\n");
//查找要查找的节点
test_device_node = of_find_node_by_path("/test");
if (test_device_node == NULL) {
printk("test_device_node find error\n");
return -1;
}
printk("test_device_node name is %s\n",test_device_node->name);//test
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 name is %d\n",beep_gpio);
ret = gpio_request(beep_gpio, "beep");
if (ret < 0) {
printk("gpio_request error\n");
return -1;
}
ret = misc_register(&misc_dev);
if (ret < 0) {
printk("misc_register error\n");
return -1;
}
return 0;
}
int beep_remove(struct platform_device *pdev)
{
pritnk("beep_remove \n");
return 0;
}
strcut platform_driver beep_device = {
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "123",
.of_match_table = of_match_table_test,//匹配表
},
.id_table = &beep_id_table,
};
static int beep_driver_init(void)
{
int ret = -1;
ret = platform_driver_register(&beep_device);
if(ret < 0) {
printk("platform_driver_register error \n");
}
printk("platform_driver_register ok\n");
return 0;
}
static void beep_driver_exit(void)
{
platform_driver_unregister(&beep_device);
printk("beep_driver_exit \n");
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
加载驱动,可以看到杂项设备节点生成,对这个设备节点写入1就表示引脚电平设置为高,,对这个设备节点写入0就表示引脚电平设置为低,
echo 1 > /dev/hello_misc
echo 0 > /dev/hello_misc