RK-GPIO基本知识,开发,调试

简介

GPIO, 全称 General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。
RK3399有5组GPIO bank:GPIO0~GPIO4,每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分(不是所有 bank 都有全部编号,例如 GPIO4 就只有 C0~C7, D0~D2)。
在这里插入图片描述

所有的GPIO在上电后的初始状态都是输入模式,可以通过软件设为上拉或下拉,也可以设置为中断脚,驱动强度都是可编程的。 每个 GPIO 口除了通用输入输出功能外,还可能有其它复用功能,例如 GPIO2_B2,可以利用成以下功能:

  • SPI2_TXD
  • CIF_CLKIN
  • I2C6_SCL

每个 GPIO 口的驱动电流、上下拉和重置后的初始状态都不尽相同,详细情况请参考RK3399TRM! 的 “Chapter 10 GPIO” 一章。 RK3399 的 GPIO 驱动是在以下 pinctrl 文件中实现的:

drivers/pinctrl/pinctrl-rockchip.c

其核心是填充 GPIO bank 的方法和参数,并调用 gpiochip_add 注册到内核中

static int rockchip_gpiolib_register(struct platform_device *pdev,
                        struct rockchip_pinctrl *info)
{           
    struct rockchip_pin_ctrl *ctrl = info->ctrl;
    struct rockchip_pin_bank *bank = ctrl->pin_banks;
    struct gpio_chip *gc;
    int ret;
    int i;  
            
    for (i = 0; i < ctrl->nr_banks; ++i, ++bank) {
        if (!bank->valid) {
            dev_warn(&pdev->dev, "bank %s is not valid\n",
                ┊bank->name);
            continue;
        }   
            
        bank->gpio_chip = rockchip_gpiolib_chip;
            
        gc = &bank->gpio_chip;
        gc->base = ARCH_GPIO_BASE + bank->pin_base;
        gc->ngpio = bank->nr_pins;
        gc->dev = &pdev->dev;
        gc->of_node = bank->of_node;
        gc->label = bank->name;
            
        ret = gpiochip_add(gc);
        ....
    }


    ...
}

开发

DTS配置

在DTSI或者DTS文件中增加GPIO的资源描述:

    ak7755: ak7755@18 {     
        #sound-dai-cells = <0>;
        compatible = "akm,ak7755";
        reg = <0x18>;       
        clocks = <&cru SCLK_I2S_8CH_OUT>;
        clock-names = "mclk";
        pinctrl-names = "default";
        pinctrl-0 = <&i2s_8ch_mclk>;
        status = "okay";    
        ak7755,pdn-gpio = <&gpio1 1 GPIO_ACTIVE_HIGH>; 
        tpa-sdz-gpio=<&gpio2 9 GPIO_ACTIVE_LOW>;
    };   

RK3399 有7组GPIO 每组 4个BANK ,每个BANK有8个,
上面个的 gpio1 1 对应的GPIO1 A1 1 A1
GPIO2 9 对应GPIO2 B1 8+1 =9

这两种表示方式 kernel都允许。

在probe函数中 对DTS所添加的资源进行解析

	......
	ak7755->pdn_gpio = of_get_named_gpio(np, "ak7755,pdn-gpio", 0);
    if (ak7755->pdn_gpio < 0) {
    ┊   ak7755->pdn_gpio = -1;return -1; 
    }
    if( !gpio_is_valid(ak7755->pdn_gpio) ) {
    ┊   printk(KERN_ERR "ak7755 pdn pin(%u) is invalid\n", ak7755->pdn_gpio);return -1; 
    }             
    tpa_sdz = of_get_named_gpio(np,"tpa-sdz-gpio",0);
    ...... 
	ret = gpio_request(ak7755->pdn_gpio, "ak7755 pdn");
	gpio_direction_output(ak7755->pdn_gpio, 0);

    devm_gpio_request(&(ak7755->i2c->dev),tpa_sdz,"tpa-sdz-gpio");
    gpio_direction_output(tpa_sdz, 1);
    ......

of_get_named_gpio_flags 从设备树中读取 gpio 的 GPIO 配置编号和标志,gpio_is_valid 判断该 GPIO 编号是否有效,gpio_request 则申请占用该 GPIO。
如果初始化过程出错,需要调用 gpio_free 来释放之前申请过且成功的 GPIO 。
在驱动中调用 gpio_direction_output 就可以设置输出高还是低电平,这里默认输出从DTS获取得到的有效电平GPIO_ACTIVE_HIGH,即为高电平,
如果驱动正常工作,可以用万用表测得对应的引脚应该为高电平。
实际中如果要读出 GPIO,需要先设置成输入模式,然后再读取值:

int val;
gpio_direction_input(your_gpio);
val = gpio_get_value(your_gpio);

GPIO 常用API

#include <linux/gpio.h>
#include <linux/of_gpio.h>  

enum of_gpio_flags {
     OF_GPIO_ACTIVE_LOW = 0x1,
};  
int of_get_named_gpio_flags(struct device_node *np, const char *propname,   
int index, enum of_gpio_flags *flags);  
int gpio_is_valid(int gpio);  
int gpio_request(unsigned gpio, const char *label);  
void gpio_free(unsigned gpio);  
int gpio_direction_input(int gpio);  
int gpio_direction_output(int gpio, int v);

中断

中断类型

IRQ_TYPE_NONE         //默认值,无定义中断触发类型
IRQ_TYPE_EDGE_RISING  //上升沿触发 
IRQ_TYPE_EDGE_FALLING //下降沿触发
IRQ_TYPE_EDGE_BOTH    //上升沿和下降沿都触发
IRQ_TYPE_LEVEL_HIGH   //高电平触发
IRQ_TYPE_LEVEL_LOW    //低电平触发

与上述过程基本一致,配置DTS,在probe函数中进行DTS解析,再中断注册申请

......
 if (gpio_is_valid(button->gpio)) {  
                                        
        error = devm_gpio_request_one(&pdev->dev, button->gpio,GPIOF_IN, desc);        
        if (error < 0) {                
            dev_err(dev, "Failed to request GPIO %d, error %d\n",
                button->gpio, error);   
            return error;               
        }                               
                                        
        if (button->debounce_interval) {
            error = gpio_set_debounce(button->gpio,                                                                                             
                    button->debounce_interval * 1000);
            /* use timer if gpiolib doesn't provide debounce */
            if (error < 0)              
                bdata->software_debounce =        
                        button->debounce_interval;
        }                               
                                        
        if (button->irq) {              
            bdata->irq = button->irq;   
        } else {                        
            irq = gpio_to_irq(button->gpio); 

        .....

        INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
                     
        isr = gpio_keys_gpio_isr;
        irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
        ......
    	/*               
    	┊* Install custom action to cancel release timer and
    	┊* workqueue item.
    	┊*/              
    

    	error = devm_add_action(&pdev->dev, gpio_keys_quiesce_key, bdata);
    	if (error) {     
        	dev_err(&pdev->dev,
            	"failed to register quiesce action, error: %d\n",
            	error);  
        	return error;
    	}                
                     
    	/*               
    	┊* If platform has specified that the button can be disabled,
    	┊* we don't want it to share the interrupt line.
    	┊*/              
    	if (!button->can_disable)
        	irqflags |= IRQF_SHARED;
                     
    	error = devm_request_any_context_irq(&pdev->dev, bdata->irq,isr, irqflags, desc, bdata);


调用gpio_to_irq把GPIO的PIN值转换为相应的IRQ值
调用gpio_request申请占用该IO口
irq 号 绑定 ISR

复用

TRM!中获取复用相关信息 IOMUX在 General Register Files (GRF) 中
GRF_GPIO4C_IOMUX:

3:2 RW 0x0
gpio4c1_sel
GPIO4C[1] iomux select
2’b00: gpio
2’b01: i2c3hdmi_scl
2’b10: uart2dbgb_sout
2’b11: hdmii2c_scl
1:0 RW 0x0
gpio4c0_sel
GPIO4C[0] iomux select
2’b00: gpio
2’b01: i2c3hdmi_sda
2’b10: uart2dbgb_sin
2’b11: hdmii2c_sda

寄存器不同值,对应复用不一致

DTS资源

    pinctrl: pinctrl {
        compatible = "rockchip,rk3399-pinctrl";
        rockchip,grf = <&grf>;
        rockchip,pmu = <&pmugrf>;
        #address-cells = <0x2>;
        #size-cells = <0x2>;
        ranges;

        i2c3 {
            i2c3_xfer: i2c3-xfer {
                rockchip,pins =
                    <4 17 RK_FUNC_1 &pcfg_pull_none>,
                    <4 16 RK_FUNC_1 &pcfg_pull_none>;
            };
    
            i2c3_gpio: i2c3_gpio {
                rockchip,pins =
                    <4 17 RK_FUNC_GPIO &pcfg_pull_none>,
                    <4 16 RK_FUNC_GPIO &pcfg_pull_none>;
            };
    
        };        


	...
    i2c3: i2c@ff130000 {                                                                                                                        
        compatible = "rockchip,rk3399-i2c";
        reg = <0x0 0xff130000 0x0 0x1000>;
        clocks = <&cru SCLK_I2C3>, <&cru PCLK_I2C3>;
        clock-names = "i2c", "pclk";
        interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH 0>;
        pinctrl-names = "default";
        pinctrl-0 = <&i2c3_xfer>;
        pinctrl-1 = <&i2c3_gpio>;   
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
    };

复用控制相关的是 pinctrl- 开头的属性

  • pinctrl-names 定义了状态名称列表:
  • pinctrl-0 定义了状态 0 (即 default)时需要设置的 pinctrl: &i2c3_xfer
  • pinctrl-1 定义了状态 1 (即 gpio)时需要设置的 pinctrl: &i2c3_gpio

在复用时,如果选择了 “default” ,系统会应用 otp_gpio 这个 pinctrl;
而如果选择了 pinctrl-1 ,系统会应用 pinctrl: &otp_out 功能。

我们看看 i2c 的驱动程序 kernel/drivers/i2c/busses/i2c-rockchip.c 是如何切换复用功能的:

static int rockchip_i2c_probe(struct platform_device *pdev)
{    
    struct rockchip_i2c *i2c = NULL;
    struct resource *res;
    struct device_node *np = pdev->dev.of_node;
    int ret;
    ... 
    i2c = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_i2c), GFP_KERNEL);
    ...
    strlcpy(i2c->adap.name, "rockchip_i2c", sizeof(i2c->adap.name));
    i2c->dev = &pdev->dev;
    i2c->adap.owner = THIS_MODULE;
    i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
    i2c->adap.retries = 2;
    i2c->adap.timeout = msecs_to_jiffies(100);
    i2c->adap.algo = &rockchip_i2c_algorithm;
    i2c_set_adapdata(&i2c->adap, i2c);
    i2c->adap.dev.parent = &pdev->dev;
    i2c->adap.dev.of_node = np;
     
    spin_lock_init(&i2c->lock);
    init_waitqueue_head(&i2c->wait);
     
    /* map the registers */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    i2c->regs = devm_ioremap_resource(&pdev->dev, res);
	...
    i2c->check_idle = true;                                                                                                                                                  
    of_property_read_u32(np, "rockchip,check-idle", &i2c->check_idle);
    if (i2c->check_idle) {
        i2c->sda_gpio = of_get_gpio(np, 0);/*step 1*/
        if (!gpio_is_valid(i2c->sda_gpio)) {
            dev_err(&pdev->dev, "sda gpio is invalid\n");
            return -EINVAL;
        }        
        ret = devm_gpio_request(&pdev->dev, i2c->sda_gpio, dev_name(&i2c->adap.dev));/*step 2*/
        if (ret) {
            dev_err(&pdev->dev, "failed to request sda gpio\n");
            return ret;
        }        
        i2c->scl_gpio = of_get_gpio(np, 1);/*step 1*/
        if (!gpio_is_valid(i2c->scl_gpio)) {
            dev_err(&pdev->dev, "scl gpio is invalid\n");
            return -EINVAL;
        }        
        ret = devm_gpio_request(&pdev->dev, i2c->scl_gpio, dev_name(&i2c->adap.dev));/*step 2*/
        if (ret) {
            dev_err(&pdev->dev, "failed to request scl gpio\n");
            return ret;
        }        
        i2c->gpio_state = pinctrl_lookup_state(i2c->dev->pins->p, "gpio");/*step 3*/
        if (IS_ERR(i2c->gpio_state)) {
            dev_err(&pdev->dev, "no gpio pinctrl state\n");
            return PTR_ERR(i2c->gpio_state);
        }        
        pinctrl_select_state(i2c->dev->pins->p, i2c->gpio_state);
        gpio_direction_input(i2c->sda_gpio);
        gpio_direction_input(i2c->scl_gpio);
        pinctrl_select_state(i2c->dev->pins->p, i2c->dev->pins->default_state);/*step 4*/
    }            
                 
    /* setup info block for the i2c core */
    ret = i2c_add_adapter(&i2c->adap);
    if (ret < 0) {
        dev_err(&pdev->dev, "failed to add adapter\n");
        return ret;
    }            
                 
    platform_set_drvdata(pdev, i2c);
                 
    /* find the clock and enable it */
    i2c->clk = devm_clk_get(&pdev->dev, NULL);
    if (IS_ERR(i2c->clk)) {
        dev_err(&pdev->dev, "cannot get clock\n");
        return PTR_ERR(i2c->clk);
    }                        
    /* find the IRQ for this unit (note, this relies on the init call to
    ┊* ensure no current IRQs pending
    ┊*/          
    i2c->irq = ret = platform_get_irq(pdev, 0);
      
                 
    ret = devm_request_irq(&pdev->dev, i2c->irq, rockchip_i2c_irq, 0,
            dev_name(&i2c->adap.dev), i2c);
                       
    ret = clk_prepare(i2c->clk);
         
    i2c->i2c_rate = clk_get_rate(i2c->clk);
                 
    rockchip_i2c_init_hw(i2c, 100 * 1000);
    dev_info(&pdev->dev, "%s: Rockchip I2C adapter\n", dev_name(&i2c->adap.dev));
                 
    of_i2c_register_devices(&i2c->adap);
    mutex_init(&i2c->suspend_lock);
                 
    return 0;    
}   
  • step 1 调用 of_get_gpio 取出DTS i2c3 结点的 gpios 属于所定义的两个 gpio
  • step 2 调用 devm_gpio_request 来申请 gpio
  • step 3 调用 pinctrl_lookup_state 来查找 “gpio” 状态,而默认状态 “default” 已经由框架保存到 i2c->dev-pins->default_state
  • step 4 调用 pinctrl_select_state 来选择是 “default” 还是 “gpio” 功能。

复用API

#include <linux/pinctrl/consumer.h> 
struct device {
	//...
	#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
	#endif
	//...
}; 
struct dev_pin_info {
    struct pinctrl *p;
    struct pinctrl_state *default_state;
#ifdef CONFIG_PM
    struct pinctrl_state *sleep_state;
    struct pinctrl_state *idle_state;
    #endif
}; 
struct pinctrl_state * pinctrl_lookup_state(struct pinctrl *p, const char *name); 
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);

调试方法

IO指令

读取寄存器GPIO调试有一个很好用的工具,那就是IO指令,RK3399的Android系统默认已经内置了IO指令,使用IO指令可以实时读取或写入每个IO口的状态,这里简单介绍IO指令的使用。 首先查看 io 指令的帮助:

1|rk3399_mid:/ $ io -help
Raw memory i/o utility - $Revision: 1.5 $

io -v -1|2|4 -r|w [-l <len>] [-f <file>] <addr> [<value>]

    -v         Verbose, asks for confirmation
    -1|2|4     Sets memory access size in bytes (default byte)
    -l <len>   Length in bytes of area to access (defaults to
               one access, or whole file length)
    -r|w       Read from or Write to memory (default read)
    -f <file>  File to write on memory read, or
               to read on memory write
    <addr>     The memory address to access
    <val>      The value to write (implies -w)

Examples:
    io 0x1000                  Reads one byte from 0x1000
    io 0x1000 0x12             Writes 0x12 to location 0x1000
    io -2 -l 8 0x1000          Reads 8 words from 0x1000
    io -r -f dmp -l 100 200    Reads 100 bytes from addr 200 to file
    io -w -f img 0x10000       Writes the whole of file to memory

Note access size (-1|2|4) does not apply to file based accesses.

读写一个寄存器
io -4 -r addr //读从addr起的4位寄存器的值
io -4 -w addr //写从addr起的4位寄存器的值

例子 查看i2c3 复用情况

i2c3 SDA/SCL对应的 GPIO为 4 16 / 4 17 几位GPIO4 BANK C

GPIO4—C 查询datasheet对应的寄存器为:
GRF_GPIO4C_IOMUX
Address: Operational Base + offset (0x0e028)
GPIO4C iomux control
GRF基地址为:FF77_0000

GPIO4C iomux control地址为:0xff7700+0x0e028 0xff77e028

rk3399_mid:/ # io -4 -r 0xff77e028
ff77e028:  0000855f

bit0-bit3 11 11
2’b11: hdmii2c_scl
2’b11: hdmii2c_sda

  • 如果想设置调试GPIO,直接写入对应的值

GPIO调式接口

Debugfs文件系统目的是为开发人员提供更多内核数据,方便调试。 这里GPIO的调试也可以用Debugfs文件系统,获得更多的内核信息。
GPIO在Debugfs文件系统中的接口为 /sys/kernel/debug/gpio,可以这样读取该接口的信息:

rk3399_mid:/ # cat /sys/kernel/debug/gpio                                      
GPIOs 1000-1031, platform/pinctrl, gpio0:
 gpio-1004 (                    |bt_default_wake_host) in  lo    
 gpio-1005 (                    |power               ) in  hi    
 gpio-1009 (                    |bt_default_reset    ) out hi    
 gpio-1010 (                    |reset               ) out hi    

GPIOs 1032-1063, platform/pinctrl, gpio1:
 gpio-1034 (                    |int-n               ) in  hi    
 gpio-1045 (                    |enable              ) out hi    
 gpio-1046 (                    |vsel                ) out lo    
 gpio-1049 (                    |vsel                ) out lo    
 gpio-1054 (                    |mpu6500             ) in  lo    

GPIOs 1064-1095, platform/pinctrl, gpio2:
 gpio-1064 (                    |vbus-5v             ) out lo    
 gpio-1069 (                    |power33             ) out hi    
 gpio-1070 (                    |power               ) out hi    
 gpio-1071 (                    |reset               ) out hi    
 gpio-1072 (                    |stanby              ) out hi    
 gpio-1073 (                    |power18             ) out hi    
 gpio-1074 (                    |csi-ctl             ) out lo    
 gpio-1076 (                    |int                 ) in  hi    
 gpio-1083 (                    |bt_default_rts      ) out lo    
 gpio-1090 (                    |bt_default_wake     ) out hi    

GPIOs 1096-1127, platform/pinctrl, gpio3:
 gpio-1111 (                    |mdio-reset          ) out hi    

GPIOs 1128-1159, platform/pinctrl, gpio4:
 gpio-1150 (                    |?                   ) out hi    
 gpio-1153 (                    |vcc5v0_host         ) out hi    
 gpio-1157 (                    |enable              ) out hi    
 gpio-1158 (                    |vcc_lcd             ) out hi

从读取到的信息中可以知道,内核把GPIO当前的状态都列出来了,以GPIO0组为例,gpio-1004(GPIO0_A4)作为BT模块的唤醒脚(bt_default_wake_host),输入低电平(in lo)。

当IO指令读出来的值都是0x00000000时,请确认该GPIO的CLK是不是被关了,GPIO的CLK是由CRU控制,
可以通过读取datasheet下面CRU_CLKGATE_CON* 寄存器来查到CLK是否开启,
如果没有开启可以用io命令设置对应的寄存器,从而打开对应的CLK,打开CLK之后应该就可以读到正确的寄存器值了。

在这里插入图片描述

  • 6
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lin_AIOS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值