gpio pinctrl的使用demo

本文主旨在于介绍gpio功能的使用,故不会去细剖pinctrl中的代码框架;为了介绍gpio在全志平台的使用,分为三个部分,uboot中的gpio使用,内核中的gpio使用,内核中的pinctrl使用以及debug;
并附上简单的demo驱动,方便驱动工程师快速使用全志平台进行gpio相关的驱动开发;

uboot中gpio的使用

对于gpio的操作,封装在include/sys_config.h,用户可在查阅其他驱动(如显示屏)借鉴完成所需的代码;以下是uboot中实现gpio_led的量灭简单demo;

uboot中使用gpio demo

gpio_led.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

#include <common.h>
#include <sys_config.h>
#include <asm/arch/platform.h>
#include <fdt_support.h>
#include <sys_config_old.h>


static __u32 gpio_red_led_hd;
static __u32 gpio_blue_led_hd;

int gpio_led_init(void){
u8 ret;
user_gpio_set_t gpio_init;
memset(&gpio_init, 0, sizeof(user_gpio_set_t));

ret = script_parser_fetch("gpio_led", "gpio_red_led", (void *)&gpio_init, sizeof(user_gpio_set_t)>>2);
if (!ret) {
//获取gpio句柄
gpio_red_led_hd = gpio_request(&gpio_init, 1);
if(!gpio_red_led_hd){
printf("gpio_red_led_hd request err\n");
}
} else {
printf("gpio red init err\n");
}
ret = script_parser_fetch("gpio_led", "gpio_blue_led", (void *)&gpio_init, sizeof(user_gpio_set_t)>>2);
if (!ret) {
gpio_blue_led_hd = gpio_request(&gpio_init, 1);
if(!gpio_blue_led_hd){
printf("gpio_blue_led_hd request err\n");
}
} else {
printf("gpio blue init err\n");
}
return 0;
}
int gpio_red_led_set_value(int value){
u8 ret;
设置gpio
ret = gpio_write_one_pin_value(gpio_red_led_hd, value, "gpio_red_led");
if (ret) {
printf("gpio_red_led_set_value%d err\n",value);
return 0;
}
int gpio_blue_led_set_value(int value){
u8 ret;
ret = gpio_write_one_pin_value(gpio_blue_led_hd, value, "gpio_blue_led");
if (ret) {
printf("gpio_blue_led_set_value%d err\n",value);
}
return 0;
}

gpio_led.h

1
2
3
4
5
6
#ifndef __GPIO_LED_H
#define __GPIO_LED_H
extern int gpio_led_init(void);
extern int gpio_red_led_set_value(int value);
extern int gpio_blue_led_set_value(int value);
#endif

调用接口流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
--- a/u-boot-2014.07/board/sunxi/common/secondary_main.c
+++ b/u-boot-2014.07/board/sunxi/common/secondary_main.c
@@ -34,6 +34,7 @@
#include <asm/arch/platsmp.h>
#include <cputask.h>
#include <smc.h>
+#include <gpio_led.h>
#include <securestorage.h>
#include <fdt_support.h>
#include <bmp_layout.h>
@@ -196,6 +197,7 @@ static int sunxi_probe_power_state(void)
int ret = 0, __power_on_cause;
SUNXI_BOOT_POWER_STATE_E __boot_state;
int pmu_bat_unused = 0;
+ int gpio_led_used = 0;

/* power_start
0: not allow boot by insert dcin,boot condition:
@@ -223,6 +225,13 @@ static int sunxi_probe_power_state(void)
__power_source = axp_probe_power_source();
pr_notice("PowerBus = %d( %d:vBus %d:acBus other: not exist)\n",
__power_source,AXP_VBUS_EXIST,AXP_DCIN_EXIST);
+ script_parser_fetch("gpio_led", "gpio_led_used", &gpio_led_used, 1);
+ if (gpio_led_used){
+ printf("gpio_led_used 11\n");
+ gpio_led_init();
+ gpio_red_led_set_value(0);
+ gpio_blue_led_set_value(1);
+ }

如果在uboot中调用了gpio设置方向,那么内核中probe就不必去调用gpio_direction_output(led_red,0),这样才不会导致uboot中设置的电源状态被修改;

内核中的gpio使用

对于驱动工程师,使用gpio驱动是件无法逃避的一件事情,gpio的常见操作包括,输入输出,驱动能力,上下拉,电平;

内核中gpio的一般使用步骤

一:添加所要申请的gpio

方法一:sys_config.fex添加对应的描述

pmu_type_c_sel             = port:PH10<1><default><default><0>

方法二:dts的配置方法

pmu_type_c_sel=<&pio PH 10 1 1 1 0>;
        |         |  |   | | | | |-------------------电平
        |         |  |   | | | |----------------------上下拉
        |         |  |   | | |-------------------------驱动力
        |         |  |   | |----------------------------复用类型,0-GPIOIN 1-GPIOOUT..
        |         |  |   |------------------------------pin bank内偏移.
        |         |  |---------------------------------哪个bank 
        |         |--------------------------------------指向哪个pio,属于cpus要用&r_pio
        |-----------------------------------------------------属性名字,相当sys_config子键名

二:获取相关的gpio端口

 chg_dev->pmu_type_c_sel.gpio =
        of_get_named_gpio(pdev->dev.of_node, "pmu_type_c_sel", 0);
if (!gpio_is_valid(chg_dev->pmu_type_c_sel.gpio)) {
        pr_err("get pmu_type_c_sel failed\n");
} else {
        ret = gpio_request(
                chg_dev->pmu_type_c_sel.gpio,
                "pmu_type_c_sel");
        if (ret != 0) {
                pr_err("ERR: pmu_type_c_sel request failed\n");
                return -EINVAL;
        }   
        ret = gpio_direction_output(chg_dev->pmu_type_c_sel.gpio, 0); 
        if (ret < 0) {
                pr_err("can't request output direction pmu_type_c_sel %d\n",
                                chg_dev->pmu_type_c_sel.gpio);
                return ret;
        }   
}   

三:根据需求设置相关的电平

static int bmu1760_set_sel_mode(struct axp_charger_dev *cdev, int mode)
{
    if (mode) {
            gpio_set_value(cdev->pmu_type_c_sel.gpio, 1); 
    } else {
            gpio_set_value(cdev->pmu_type_c_sel.gpio, 0); 
    }   
    AXP_DEBUG(AXP_SPLY, cdev->chip->pmu_num,
            "set_sel_mode = %x\n", mode);
    return 0;
}

四:设置管脚的驱动能力

正常以上三个步骤已经满足基本需求,但如果对管脚的驱动能力不满意;以下对PL管脚进行设置

DTS设置如下:
gpio_led_led = <&r_pio PL 10 1 1 3 0>

--- a/drivers/misc/sunxi-rf/sunxi-wlan.c
+++ b/drivers/misc/sunxi-rf/sunxi-wlan.c
@@ -367,6 +367,8 @@ static int sunxi_wlan_probe(struct platform_device *pdev)
        struct device *dev = &pdev->dev;
        struct sunxi_wlan_platdata *data;
        struct gpio_config config;
+       unsigned long drv_level;
+       char pin_name[SUNXI_PIN_NAME_MAX_LEN];
        u32 val;
        const char *power, *io_regulator;
        int ret = 0;
@@ -439,6 +441,14 @@ static int sunxi_wlan_probe(struct platform_device *pdev)
                                data->gpio_wlan_regon);
                        return ret;
                }   
+               printk("%s %d\n",__func__,__LINE__);
+               sunxi_gpio_to_name(config.gpio, pin_name);
+               if (config.drv_level != GPIO_DRVLVL_DEFAULT) {
+                       printk("%s %d\n",__func__,__LINE__);
+                       drv_level = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DRV, config.drv_level);
+                       pin_config_set(SUNXI_R_PINCTRL, pin_name, drv_level);
+               }
+
    }   

如果使用pinctrl的设置方式的话,pinctrl会为我们初始化好设置的东西,GPIO的设置方式则需要手动配置;

讲完输出以及电平的设置,另外一个管脚常用的功能中断的申请步骤这里也顺便讲一下

中断管脚申请流程

dts配置如下
     pmu_acin_det               = <&pio PH 10 0 1 1 0>

chg_dev->axp_acin_det.gpio =  of_get_named_gpio(pdev->dev.of_node,
                            "pmu_acin_det_gpio", 0);
if (!gpio_is_valid(chg_dev->axp_acin_det.gpio)) {
    pr_err("ERR: get pmu_acin_det_gpio is fail\n");
    return -EINVAL;
}

 ret = gpio_request( chg_dev->axp_acin_det.gpio, "pmu_acin_det_gpio");
 if (ret != 0) {
     pr_err("ERR: pmu_acin_det gpio_request failed\n");
     return -EINVAL;
}
id_irq_num = gpio_to_irq(chg_dev->axp_acin_det.gpio);
if (IS_ERR_VALUE((unsigned long)id_irq_num)) {
    pr_err("ERR: map pmu_acin_det gpio to virq failed, err %d\n",
               id_irq_num);
    return -EINVAL;
}
ret = request_irq(id_irq_num, axp_acin_gpio_isr, irq_flags,
            "pmu_acin_det_gpio", chg_dev);
if (IS_ERR_VALUE((unsigned long)ret)) {
    pr_err("ERR: request pmu_acin_det virq %d failed, err %d\n",
             id_irq_num, ret);
    return -EINVAL;
}

gpio需要在休眠时唤醒系统:

全志目前的话只支持小cpu的gpio口唤醒系统,如果要唤醒系统,需要加入这两个步骤

一:enable_gpio_wakeup_src(data->gpio_wlan_hostwake);
二:request_irq的时候需要加入标志位 IRQF_NO_SUSPEND

讲完使用的步骤,我们讲下GPIO的函数调用流程,顺便带出pinctrl

gpio_set_value
    __gpio_set_value
        gpiod_set_raw_value
            _gpiod_set_raw_value
                chip->set(chip, gpio_chip_hwgpio(desc), value);
                    sunxi_pinctrl_gpio_set
                        writel(regval, pctl->membase + reg); 

从以上流程可知,GPIO的API是建立在pinctrl之上的接口,其他流程也类似,不在赘述,开始讲pinctrl

pinctrl

从上面的gpio的调用流程我们可以看到gpio类似于pinctrl的client或者consumer;但对于管脚,其不止有gpio的基本功能,输入输出;当gpio组合的时候,配合芯片的控制器,他可以作为IIC,SPI的控制引脚;

pinctrl作用

  1. 管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin.
  2. 管理这些pin的复用(Multiplexing),对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,行程特定的功能。pin control subsystem需管理所有的pin group。
  3. 配置这些pin的特性,例如使能或关闭引脚上的pull-up,pull-down电阻,配置引脚的driver strength;

pinctr的使用demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static int ir_tx_request_gpio(struct sunxi_ir_tx_data *ir_tx_data)     
{
ir_tx_data->pctrl = devm_pinctrl_get(&ir_tx_data->pdev->dev);
if (IS_ERR(ir_tx_data->pctrl)) {
IRTX_ERR("devm_pinctrl_get() failed!\n");
return -1;
}

return ir_tx_select_gpio_state(ir_tx_data->pctrl,
PINCTRL_STATE_DEFAULT);
}

static int ir_tx_select_gpio_state(struct pinctrl *pctrl, char *name)
{
int ret = 0;
struct pinctrl_state *pctrl_state = NULL;

pctrl_state = pinctrl_lookup_state(pctrl, name);
if (IS_ERR(pctrl_state)) {
IRTX_ERR("IR_TX pinctrl_lookup_state(%s) failed! return %p \n",
name, pctrl_state);
return -1;
}

ret = pinctrl_select_state(pctrl, pctrl_state);
if (ret < 0)
IRTX_ERR("IR_TX pinctrl_select_state(%s) failed! return %d \n",
name, ret);

return ret;
}
static int sunxi_ir_tx_remove(struct platform_device *pdev)
{
struct sunxi_ir_tx_data *ir_tx_data = platform_get_drvdata(pdev);

....
devm_pinctrl_put(ir_tx_data->pctrl);

....
return 0;
}

pinctrl debug 方法

pinctrl debug

#参考资料

linux内核中的GPIO系统之(5):gpio subsysem和pinctrl subsystem之间的耦合