前言
本文将介绍GPIO子系统与pinctrl子系统的使用方法,并给出相应的设备树配置与驱动框架。日后开发如果忘记了,可以常来看看。
GPIO
设备树
当你想使用gpio驱动外设时(如点灯),你需要熟悉gpio的节点以及如何使用gpio节点。不同的厂商gpio节点的描述方法可能不同,但大体上不会差太多,如下图为全志h3设备树中对gpio节点的描述:
pio: pinctrl@01c20800 {
/* compatible is in per SoC .dtsi file */
reg = <0x01c20800 0x400>; /*起始地址与偏移量*/
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
clock-names = "apb", "hosc", "losc";
gpio-controller;/*只有一个节点含有gpio-controller时,才能代表它是一个gpio节点*/
#gpio-cells = <3>;/*引用gpio节点时,需要调用的参数数量*/
interrupt-controller;
#interrupt-cells = <3>;
......
};
在全志h3的设备树中,当引用gpio节点时,需要三个参数:主引脚号,次引脚号,以及引脚默认状态(高低电平),下面将介绍如何引用gpio引脚,将gpio引脚的熟悉添加到自己创建的节点中。
添加一个led的gpio节点:
my_led{
compatible = "myled";
pinctrl-names = "default";
pinctrl-0 =<&led_pins>;
led-gpios = <&pio 0 6 GPIO_ACTIVE_LOW>;
status = "okay";
};
驱动
子系统的核心作用就是提供对上层统一的接口,使用gpio子系统控制外设,可以不关心底层的细节,把驱动和设备分开,当设备修改时,不需要再修改驱动,只需要修改设备树即可。
gpio子系统分为gpiolib和gpio chip driver,分别由内核设计者与SOC厂商提供。
第一种方法
#include <linux/gpio/consumer.h>
struct gpio_desc * gpio_pointer;
定义一个gpio设备指针
static inline struct gpio_desc *__must_check gpiod_get(struct device *dev, const char *con_id,enum gpiod_flags flags)
__must_check是一个标识符,告诉编译器必须检验返回值。
返回值为gpio子系统的结构体
参数分别是平台设备结构体指针,gpio的标识符,输出模式
void gpiod_set_value(struct gpio_desc *desc, int value)
设置gpio的值
gpiod_direction_output()
设置输出方式
int gpiod_get_value(const struct gpio_desc *desc)
读gpio的值
(待补充)
第二种方法
//伪代码
#include <linux/gpio.h>
#include <linux/of_gpio.h>
struct my_gpio_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
struct device_node *node; /* LED设备节点 */
int ledgpio; /* LED灯GPIO标号 */
};
struct my_gpio_dev led = {
}
......
......
probe()
{
...
led_node = of_find_node_by_name(NULL,"my_led");//probe匹配后,获取设备节点
if(led_node==NULL)
{
printk("find node faile!\n");
return -1;
}
ledgpio = of_get_named_gpio(led_node, "led-gpios", 0);
printk("gpioid=%d\n",ledgpio);
gpio_request(ledgpio, "led0");/*申请资源*/
gpio_direction_output(ledgpio,0);/*设置Gpio模式,初始电平值*/
printk("led probe ok\n");
...
}
remove()
{
gpio_set_value(ledgpio,0);
gpio_free(ledgpio);
}
open/ioctrl/write/...()
{
gpio_set_value(ledgpio,1);//亮灯
}
pinctrl
在上述例子中,我们已经看见了pinctrl的身影,那么pinctrl是什么呢?如果有学过单片机的朋友肯定清楚,如stm32中不同的引脚可以配置不同的功能来满足需要,设备树中的pinctrl也是用来定义引脚配置的。对于pinctrl的学习,一般分为客户端与服务端。
设备树
- 客户端
客户端主要是写在自己创建的节点中,用来引用服务端的内容,格式相对统一,如上述的my_led节点就是pinctrl的客户端。由通常由两个属性组成:pinctrl-names
和pinctrl-X
pinctrl-names
一般使用default默认值就行
pinctrl-X
中X表示引脚控制器子系统的编号,未级联的引脚控制器一般用0就行。
引用的&led_pins
为服务端内容,我们在下面会介绍。 - 服务端
服务端的pinctrl对于每个厂商都有不同的写法,我将以全志h3为例子写一个led_pins
的服务端。
//写根节点外面,且要加标签,不加标签没法引用
&pio{
led_pins:led_pins{
pins = "PA6";
function = "gpio_out";
};
};
不难体会服务端的主要属性就是引脚编号以及引脚模式,即使其他的厂商写法不一,但中心思想都是不会变的。
总结
内容还未完全写完,待修改