本文主旨在于介绍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.c1
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.h1
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作用
- 管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin.
- 管理这些pin的复用(Multiplexing),对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,行程特定的功能。pin control subsystem需管理所有的pin group。
- 配置这些pin的特性,例如使能或关闭引脚上的pull-up,pull-down电阻,配置引脚的driver strength;
pinctr的使用demo:
1 | static int ir_tx_request_gpio(struct sunxi_ir_tx_data *ir_tx_data) |
pinctrl debug 方法
#参考资料