关于GPIO的故事


一、 概述

这里所说的gpio是指General Purpose Input/Output,也就是通用输入输出。对于imx258芯片,可以用作的GPIO的引脚有128个。128个又分为4组,每组32个。Gpio具有比较多的功能,例如上拉,下拉,方向可设和中断特性可设等。本文的目的正是为了更方便的了解gpio和gpio的常用操作等。

二、 软件管理架构

(一)起始

GPIO框架的初始化是从系统的一个架构接口开始,且看源代码

static int __init customize_machine(void)

{

       /* customizes platformdevices, or adds new ones */

       if (init_machine)                         //如果存在板初始化函数,则调用

              init_machine();

       return 0;

}

arch_initcall(customize_machine);        // 架构初始化入口

顾名思义,这是一个自定义机器级的初始化函数,如果存在自定义内容则执行。是否存在自定义内容是用过函数指针init_machine传递的。GPIO归属于芯片的一个模块,芯片则是处理器系列中的一个定制处理器。可以预见,总有一天会谈及GPIO模块初始化的。回到init_machine,在setup_arch函数中,对该指针进行赋值。且看源代码。

...

static void (*init_machine)(void) __initdata;

...

void __init setup_arch(char **cmdline_p)

{

...

init_machine= mdesc->init_machine;

...

}

Mdesc是一个板级结构体,具体定义在板相关mx25_3stack.c文件中。且看源代码

MACHINE_START(MX25_3DS, "Freescale MX25 3-Stack Board")

       /* Maintainer: FreescaleSemiconductor, Inc. */

       .phys_io =AIPS1_BASE_ADDR,

       .io_pg_offst =((AIPS1_BASE_ADDR_VIRT) >> 18) & 0xfffc,

       .boot_params = PHYS_OFFSET+ 0x100,

       .fixup =fixup_mxc_board,

       .map_io = mx25_map_io,

       .init_irq =mxc_init_irq,

       .init_machine =mxc_board_init,                 // 板初始化函数

       .timer = &mxc_timer,

MACHINE_END

Mxc_board_init是属于bsp部分。这里用了一个kernel惯用的技俩,就是分离技术。这里将bsp需要关心的内容和通用的内容分开,这样嵌入式移植的时候,只需要关心自己板相关部分(对于芯片商便要处理的bsp)即可。Gpio针对不同板,有不同的特性,自然也属于bsp部分。且看源代码

static void __init mxc_board_init(void)

{

...

       mxc_register_gpios();                  // 注册gpio软件架构

       mx25_3stack_gpio_init();             //初始化gpio初状态

...

}

(二)GPIO软件架构

GPIO的软件架构可以理解成底层IO管理方案和针对驱动而封装的API。

1.        IO管理方案

针对本CPU的特性,软件上采用四个struct mxc_gpio_port对象来描述四组GPIO。通过struct gpio_chip对象来描述每个具体的GPIO控制信息(通过全局数据static struct gpio_descgpio_desc[ARCH_NR_GPIOS]来记录所有的GPIO,控制信息对象structgpio_chip最终会通过gpiochip_add函数注册到该全局数据)。对于struct mxc_gpio_port,软件上采用全局变量来记录。

struct mxc_gpio_port mxc_gpio_ports[] = {

       [0]= {

        .chip.label = "gpio-0",

        .base = IO_ADDRESS(GPIO1_BASE_ADDR),

        .irq = MXC_INT_GPIO1,

        .irq_high = 0,

        .virtual_irq_start = MXC_GPIO_IRQ_START

        },

       [1]= {

        .chip.label = "gpio-1",

        .base = IO_ADDRESS(GPIO2_BASE_ADDR),

        .irq = MXC_INT_GPIO2,

        .irq_high = 0,

        .virtual_irq_start = MXC_GPIO_IRQ_START + 32

        },

       [2]= {

        .chip.label = "gpio-2",

        .base = IO_ADDRESS(GPIO3_BASE_ADDR),

        .irq = MXC_INT_GPIO3,

        .irq_high = 0,

        .virtual_irq_start = MXC_GPIO_IRQ_START + 32 *2

        },

       [3]= {

        .chip.label = "gpio-3",

        .base = IO_ADDRESS(GPIO4_BASE_ADDR),

        .irq = MXC_INT_GPIO4,

        .irq_high = 0,

        .virtual_irq_start = MXC_GPIO_IRQ_START + 32 *3

        }

};

struct mxc_gpio_port对象的成员分别表示GPIO的组名,基地址,中断号和虚拟中断号等。对于structgpio_chip对象则是注册gpio软件架构指定的。大致列举一下过程。回到前面的一个函数int __initmxc_register_gpios(void):

int __initmxc_register_gpios(void)

{

       return mxc_gpio_init(mxc_gpio_ports,ARRAY_SIZE(mxc_gpio_ports));

}

这里指定了mxc_gpio_ports。通过mxc_gpio_init注册到系统(填充一个mxc_gpio_ports全局指针)。mxc_gpio_init函数主要的作用是指定或者建立每个GPIO脚的structgpio_chip对象和每个structmxc_gpio_port对象的中断处理例程。简单看一下struct gpio_chip对象的原型。

structgpio_chip {

    const char         *label;

    struct device          *dev;

    struct module               *owner;

 

    int          (*request)(struct gpio_chip *chip,unsigned offset);

    Void        (*free)(structgpio_chip *chip, unsigned offset);

 

    Int          (*direction_input)(struct gpio_chip*chip, unsigned offset);

    int          (*get)(struct gpio_chip *chip, unsignedoffset);

    int       (*direction_output)(structgpio_chip *chip, unsigned offset, int value);

    void       (*set)(structgpio_chip *chip, unsigned offset, int value);

 

    int          (*to_irq)(struct gpio_chip *chip,unsigned offset);

 

    void       (*dbg_show)(structseq_file *s, struct gpio_chip *chip);

    int          base;

    u16       ngpio;

    char              **names;

    unsigned  can_sleep:1;

    unsigned  exported:1;

};

可见,struct gpio_chip对象定义了该GPIO的设置方向的实现函数指针,输入输出实现函数的指针,基地址和/proc/目录下的文件显示方法等等。

2.        GPIO控制

有四个比较重要的函数mxc_gpio_direction_input、mxc_gpio_direction_ouput和mxc_gpio_get、mxc_gpio_set。前两个是设置方向,后两个是获取或设置gpio的状态。此四个可以理解为属性的初始化。这四个函数都是struct gpio_chip对象的一个属性成员。这里只介绍两个函数mxc_gpio_direction_ouput和mxc_gpio_get。既然已经开始设计硬件了,简单介绍一下imx258的gpio模块。GPIO包括如下特性:

•GPIO逻辑单元功能

— 驱动可通过数据寄存器DR控制输出

— 驱动可通过GPIO方向寄存器控制GPIO方向

• GPIO中断功能

— 支持所有GPIO引脚中断源

— 可确认边缘中断方式

— 可高电平,低电平,上升沿和下降沿触发

下面分析一下源代码。

static int mxc_gpio_direction_output(struct gpio_chip *chip,

                                unsigned offset, int value)

{

       mxc_gpio_set(chip,offset, value);        // 设置输出值

       _set_gpio_direction(chip,offset, 1);     // 设置方向

       return 0;

}

GPIO的方向关系到IO的负载能力和输入阻抗。可见,设置方向之前应该首先设置输出值,然后再设置方向。关注一下设置输出值的方法,且看源代码

static void mxc_gpio_set(struct gpio_chip *chip, unsigned offset,int value)

{

...

       l = (__raw_readl(reg)& (~(1 << offset))) | (value << offset);    // 设置DR的对应位

       __raw_writel(l, reg);

}

设置输出值,其实就是DR的对应位。在看一下如何设置方向,看源代码

static void _set_gpio_direction(struct gpio_chip *chip, unsignedoffset,

                            int dir)

{

...

       l =__raw_readl(port->base + GPIO_GDIR);

       if (dir)

              l |= 1 << offset;

       else

              l &= ~(1 << offset);

       __raw_writel(l,port->base + GPIO_GDIR);

}

同样,只需要把GPIO_GDIR的对应位改过来即可。再看一下获取状态的函数mxc_gpio_get,看源代码

static int mxc_gpio_get(struct gpio_chip *chip, unsigned offset)

{

...

       l =__raw_readl(port->base + GPIO_GDIR);

       if(l&(1 <<offset))        //output

       {

              return (__raw_readl(port->base + GPIO_DR) >> offset)& 1;

       }

       else                              // input

       {

              return (__raw_readl(port->base + GPIO_PSR) >> offset)& 1;

       }

}

从if的条件也可以知道,output的输出值保存在DR寄存器;input的状态值保存在PSR寄存器。只需要根据当前的方向获取即可。

(三)封装API

对于GPIO来说,只需要6个API,分别是申请,释放,设置GPIO方向为输入,设置GPIO方向为输出,设置状态和获取状态。分别介绍各个对应的函数。

v  函数:    intgpio_request(unsigned gpio, const char *label)

描述:    申请GPIO(标记FLAG_REQUESTED)

参数:    @gpio     gpio_desc的索引号

@label    gpio使用标记

返回值:0成功;其他失败

v  函数:    voidgpio_free(unsigned gpio)

描述:    释放GPIO(清楚FLAG_REQUESTED标记)

参数:    @gpio     gpio_desc的索引号

返回值:无

v  函数:    intgpio_direction_input(unsigned gpio)

描述:    设置GPIO方向为输入

参数:    @gpio     gpio_desc的索引号

返回值:0成功;其他失败

v  函数:    intgpio_direction_output(unsigned gpio, int value)

描述:    设置GPIO方向为输出,并设输出值为value

参数:    @gpio     gpio_desc的索引号

@value   输出值

返回值:0成功;其他失败

v  函数:    int__gpio_get_value(unsigned gpio)

描述:    获取GPIO的状态值

参数:    @gpio     gpio_desc的索引号

返回值:获取得到值

v  函数:    void__gpio_set_value(unsigned gpio, int value)

描述:    设置GPIO的输出值

参数:    @gpio     gpio_desc的索引号

@value   设置之后的值

返回值:无

 

三、 GPIO中断

(四)概述

每组gpio共享一个中断号。这里的注册便是总的处理函数,然后调用每个gpio对应的虚拟中断号对应的由request_irq申请时指明的处理函数。因为本文主要关心GPIO

(五)管理

中断子系统里面通过全局变量irq_desc记载了所有的中断号的描述符。中断的信息也记录在里面。本章主要讲述GPIO的中断。此处不宜多讲。

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {

       [0 ... NR_IRQS-1] = {

              .status = IRQ_DISABLED,

              .chip = &no_irq_chip,

              .handle_irq = handle_bad_irq,

              .depth = 1,

              .lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),

       }

};

(六)申请

Linux的中断子系统申请中断的方式都是一样的。介绍一下。

函数:    request_irq(unsigned int irq, irq_handler_thandler,

unsigned longflags,const char *name, void *dev)

描述:    申请中断

参数:    @irq       中断号

@handler响应函数

@flags    标志,主要是上升沿,下降沿和双边沿等

@name   中断程序的名字(自定义)

@dev      设备标识(可以作为私有数据)

返回值:0成功;其它失败

NOTE:申请中断的过程中会建立一个struct irqaction对象。然后将该对象绑定到irq_desc[irq]的action成员中。绑定之后,在/proc/irq/建立相应中断号的目录和文件。最后便是调用desc->chip->enable(irq)使能对应的中断。不过,不幸的是GPIO的中断enable函数没有指定。当然,中断还是需要使能的。申请中断的时候会通过irq_chip_set_defaults函数来设定默认使能函数为default_enable函数。default_enable函数通过desc->chip->unmask(irq);来打开中断。可喜可贺的是,GPIO模块的gpio_irq_chip指定了unmask函数。

static struct irq_chip gpio_irq_chip = {

       .ack= gpio_ack_irq,

       .mask= gpio_mask_irq,

       .unmask= gpio_unmask_irq,

       .set_type= gpio_set_irq_type,

       .set_wake= gpio_set_wake_irq,

};

详细一下gpio_irq_chip指定的函数。

中断响应函数gpio_ack_irq

static voidgpio_ack_irq(u32 irq)

{

       u32 gpio = irq_to_gpio(irq);

       _clear_gpio_irqstatus(&mxc_gpio_ports[gpio/ 32], gpio & 0x1f);

}

==》

static void_clear_gpio_irqstatus(struct mxc_gpio_port *port, u32 index)

{

       __raw_writel(1 << index,port->base + GPIO_ISR);   // 清除中断状态位

}

中断响应函数,顾名思义就是中断响应时调用的函数。由上面的代码可知,该函数的只做了清除中断状态的作用。ISR寄存器的意义,可以参考imx25用户手册。

中断屏蔽函数gpio_mask_irq

static voidgpio_mask_irq(u32 irq)

{

       u32 gpio = irq_to_gpio(irq);

       _set_gpio_irqenable(&mxc_gpio_ports[gpio/ 32], gpio & 0x1f, 0);

}

==》

static void_set_gpio_irqenable(struct mxc_gpio_port *port, u32 index,

                            intenable)

{

       u32 l;

 

       l = __raw_readl(port->base +GPIO_IMR);         // 获取中断屏蔽寄存器值

       l = (l & (~(1 << index))) |(!!enable << index);   // 修改需要屏蔽的位

       __raw_writel(l, port->base +GPIO_IMR);         // 设置中断屏蔽寄存器值

}

中断屏蔽函数,顾名思义就是屏蔽中断的API。最终是通过修改中断屏蔽寄存器实现的。IMR寄存器的意义,可以参考imx25用户手册。

中断开启函数gpio_unmask_irq

中断开启函数与屏蔽的执行相反动作,也是通过修改中断屏蔽寄存器实现的。

设置中断触发方式函数gpio_set_irq_type

static intgpio_set_irq_type(u32 irq, u32 type)

{

...

       switch (type) {

       case IRQ_TYPE_EDGE_RISING:      

              edge= GPIO_INT_RISE_EDGE;        // 上升沿触发0x10

              break;

       case IRQ_TYPE_EDGE_FALLING:

              edge= GPIO_INT_FALL_EDGE;        // 下降沿触发0x11

              break;

       case IRQ_TYPE_EDGE_BOTH:                 // 双边沿触发

              val= mxc_gpio_get(&port->chip, gpio & 31);

              if(val) {

                     edge = GPIO_INT_LOW_LEV;

                     pr_debug("mxc: set GPIO %d to lowtrigger\n", gpio);

              }else {

                     edge = GPIO_INT_HIGH_LEV;

                     pr_debug("mxc: set GPIO %d to hightrigger\n", gpio);

              }

              port->both_edges|= 1 << (gpio & 31);

              break;

       case IRQ_TYPE_LEVEL_LOW:

              edge= GPIO_INT_LOW_LEV;           // 低电平触发0x00

              break;

       case IRQ_TYPE_LEVEL_HIGH:

              edge= GPIO_INT_HIGH_LEV;          // 高电平触发0x01

              break;

       default:

              return-EINVAL;

       }

 

       reg += GPIO_ICR1 + ((gpio & 0x10)>> 2); /* lower or upper register */

       bit = gpio & 0xf;

       val = __raw_readl(reg) & ~(0x3<< (bit << 1));

       __raw_writel(val | (edge << (bit<< 1)), reg);

       _clear_gpio_irqstatus(port, gpio &0x1f);

 

       return 0;

}

无他,最终还是修改中断配置寄存器即可。但是,应该注意双边沿触发是利用软件实现的。实现方法便是根据当前IO的电平来设置下一次中断,响应的时候再重新切换。

(七)响应

3.        通用中断响应过程

1)    一级跳转

在ARM V4及V4T以后的大部分处理器中,中断向量表的位置可以有两个位置:一个是0,另一个是0xffff0000。可以通过CP15协处理器c1寄存器中V位(bit[13])控制。系统起来的时候,通过early_trap_init函数或者trap_init函数将中断向量表搬移到0x0或者0xffff0000处(笔者的系统使用的是0xffff 0000,后面以这个来介绍)。

中断向量表是一个跳转表(可以理解为一级跳转表,位置为0xffff 0000),如下:

__vectors_start:
        swiSYS_ERROR0:
        b vector_und+ stubs_offset             // 复位异常:
        ldr pc,.LCvswi + stubs_offset             //未定义指令异常:
        b vector_pabt+ stubs_offset              // 软件中断异常:
        b vector_dabt+ stubs_offset              // 数据异常:
        b vector_addrexcptn+ stubs_offset          // 保留:
        b vector_irq+ stubs_offset                // 普通中断异常:
        b vector_fiq+ stubs_offset                      // 快速中断异常:
        .globl__vectors_end:
    __vectors_end:

2)    二级跳转

一级跳转之后会到达二级跳转表(位置为__vectors_start + 200,即0xffff 0200)

       105 c000b740 T __stubs_start                                                

       106 c000b740 tvector_irq                                                                                       

      107 c000b7c0 t vector_dabt                                                                                      

      108 c000b840 t vector_pabt                                                                                      

      109 c000b8c0 t vector_und                                                                                       

      110 c000b940 t vector_fiq                                                                                       

      111 c000b944 t vector_addrexcptn                                                                                

      112 c000b964 T __stubs_end

所有的二级跳转都是通过一个汇编宏和一个三级跳转位置组成。列举一下宏

vector_\name:

       .if \correction

       sub  lr,lr, #\correction          @ 修正返回值

       .endif

 

       @

       @ Save r0, lr_<exception> (parentPC) and spsr_<exception>

       @ (parent CPSR)

       @

       stmia       sp,{r0, lr}     @ save r0, lr

       mrs  lr,spsr

       str    lr,[sp, #8]             @ save spsr

 

       @

       @ Prepare for SVC32 mode.  IRQs remain disabled.

       @ 进入SVC模式

       mrs  r0,cpsr

       eor   r0,r0, #(\mode ^ SVC_MODE)

       msr  spsr_cxsf,r0

 

       @

       @ the branch table must immediatelyfollow this code

       @

       and  lr,lr, #0x0f            @ 获取进入中断前的模式

       mov r0,sp

       ldr   lr,[pc, lr, lsl #2]     @ 如果中断前是usr,则取出PC+4*0的内容(__irq_usr)

@ 如果中断前是usr,则取出PC+4*3的内容(__irq_svc)

       movs       pc,lr              @branch to handler in SVC mode

ENDPROC(vector_\name)

       .endm

三级跳转位置组成部分其实就是一个地址。

   .long   __irq_usr               @  0 (USR_26 / USR_32)

   .long   __irq_invalid                 @  1 (FIQ_26 / FIQ_32)

   .long   __irq_invalid                 @  2 (IRQ_26 / IRQ_32)

   .long   __irq_svc               @  3 (SVC_26 / SVC_32)

...

3)    三级跳转

三级跳转获取了当前进程的描述符struct task_struct。

__irq_usr:

...

   get_thread_info tsk

...

   irq_handler

宏irq_handler定义了获取中断号和中断的处理函数,然后通过asm_do_IRQ进入四级跳转。

   .macro  irq_handler

   get_irqnr_preamble r5, lr

1: get_irqnr_and_base r0, r6, r5, lr

   movne   r1, sp

    @

    @routine called with r0 = irq number, r1 = struct pt_regs *

    @

   adrne   lr, 1b

   bne asm_do_IRQ

4)    四级跳转

四级跳转会进入了C函数。跳转过程如下:

asm_do_IRQ

=》generic_handle_irq(获取struct irq_desc)

=》generic_handle_irq_desc(从struct irq_desc中提取handle_irq)

=》desc->handle_irq(irq, desc);

4.        GPIO中断响应

GPIO中断的响应过程分为了三级,1.硬件中断响应函数(每组GPIO共享一个硬件中断);2.GPIO中断(具体GPIO对应的响应);3.处理action链(共享中断处理级)。

1)    一级响应

通用中断响应过程获取完中断号,并根据中断号寻找到中断描述符struct irq_desc。中断描述符记录在全局的数组struct irq_desc irq_desc[NR_IRQS]中。MX25具有四组GPIO,每组GPIO共享一个中断,也就是每组GPIO共享一个struct irq_desc对象。从中断描述符可以获取到申请中断时候注册的handle。对于GPIO中断,注册的handle为gpio_irq_handler。

gpio_irq_handler根据GPIO模块的ISR(GPIOInterrupt Status Register)寄存器和IMR(GPIOInterrupt Mask Register)寄存器判断实际发生中断的IO。然后在执行发生中断的IO对应的handler。对应的handler处理过程就是二级响应过程。

2)    二级响应

一级响应和二级响应都是GPIO中断的一个通用过程,在GPIO驱动建立的时候(详情可分析mxc_gpio_init函数)指定一级响应和二级响应。二级响应的函数为handle_edge_irq。

二级响应的工作是,1.调用desc->chip->ack()清除中断状态(也就是前面谈及的  中断响应函数gpio_ack_irq);2.响应struct irqaction链表(主要考虑到中断共享)。struct irqaction链表其中的一个节点就是申请中断的时候建立的。

3)    三级响应

三级响应是驱动开发者自定义的。也就是request_irq函数指定的handler。当然,这里无法介绍。

四、 IO复用

(一)概述

Imx25是一块功能强大的芯片,引脚是有限的,故而引脚都是多功能复用。

(二)硬件复用原理

由上图可知,每个IO最多可以由七个功能复用。由SW_MUX_CTL_Register寄存器指定当前使用的功能。并且输入和输出路线独立,输入和输出的电路特性也自然不一样。且看,输入和输出是利用三级管的方向来决定输入和输出阻抗的。输入和输出的选择在模块内部决定的。例如,gpio的方向就由内部的DIR寄存器决定。SW_PAD_CTL_Register寄存器决定的是IO的一些共用属性,例如IO电压,上拉或者下拉,驱动能力等等。SW_INPUT_SELECT_Register决定是IO连接的模块。这里要注意,SW_MUX_CTL_Register选择的是IO连接模块的链路,SW_INPUT_SELECT_Register是模块选择IO的链路。可见,人生很多东西一厢情愿是行不通的。

(三)软件复用管理

1.        GPIO引脚表示

针对硬件复用的原理,软件上采用4个域的方法来表示IO脚。

v  组域(MUX_IO_P),范围0 ~ 3,分别对应于4组GPIO

v  组内编号域(MUX_IO_I),范围0 ~ 31,分别对应于组内的32个GPIO

v  MUX域(MUX_I),记录该IO对应SW_MUX_CTL_Register偏移

v  PAD域(PAD_I),记录该IO对应SW_PAD_CTL_Register偏移

MUX域和PAD域是为了方便编程而携带的寄存器偏移值。举个简单例子:

MX25_PIN_A10= _MXC_BUILD_GPIO_PIN(3, 0, 0x8, 0x0),              // A10

宏_MXC_BUILD_GPIO_PIN的定义为:

#define _MXC_BUILD_GPIO_PIN(gp, gi, mi, pi)           \

              _MXC_BUILD_PIN(gp, gi, mi, pi)

=>

#define _MXC_BUILD_PIN(gp, gi, mi, pi)                            \

              (((gp) << MUX_IO_P) | ((gi)<< MUX_IO_I) | \

              ((mi) << MUX_I) | ((pi) <<PAD_I))

2.        GPIO配置值

前面硬件原理上说到,GPIO引脚具有7个功能。配置值是一个枚举变量,分别可选为功能MUX_CONFIG_ALT1 ~ MUX_CONFIG_ALT7。对应的实际具体功能需要根据芯片手册指定来判断。iomux_pin_cfg_t对象如下:

typedefenum iomux_pin_config {

    MUX_CONFIG_FUNC = 0,

    MUX_CONFIG_ALT1,

    MUX_CONFIG_ALT2,

    MUX_CONFIG_ALT3,

    MUX_CONFIG_ALT4,

    MUX_CONFIG_ALT5,

    MUX_CONFIG_ALT6,

    MUX_CONFIG_ALT7,

    MUX_CONFIG_SION = 0x1 << 4,

    MUX_CONFIG_GPIO = MUX_CONFIG_ALT5,

}iomux_pin_cfg_t;

3.        相关API

v  函数:    intmxc_request_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg)

描述:    向软件GPIO管理系统申请GPIO

参数:    @pin      GPIO引脚

@cfg      GPIO配置值

返回值:0代表成功;其他代表失败

v  函数:    voidmxc_free_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg)

描述:    向软件GPIO管理系统释放GPIO

参数:    @pin      GPIO引脚

@cfg      GPIO配置值(实际上不用考虑)

 

返回值:无

v  函数:    voidmxc_iomux_set_pad(iomux_pin_name_t pin, u32 config)

描述:    设置pad寄存器来设定GPIO的电气特性

参数:    @pin      GPIO引脚

@config  需要的设置值。

v  函数:    voidmxc_iomux_set_gpr(iomux_gp_func_t gp, bool en)

描述:    设置SW_PAD_CTL_GRP寄存器来设定GPIO组的电气特性

参数:    @gp        设置值

@en        使能或者禁止

返回值:无

v  函数:    voidmxc_iomux_set_input(iomux_input_select_t input, u32 config)

描述:    设置模块IO的输入路径

参数:    @input    输入选择寄存器索引号

@config  输入路径

返回值:无

NOTE:举例

如下图,第2个I2C模块的ipp_sda_in可以通过的FEC_RX_DV的ALT1功能输入也可以通过GPIO_D的ALT2功能输入。通过FEC_RX_DV输入的路径为0x0,通过GPIO_D输入的路径为0x1。

(四)IOMUX使用流程

              根据模块设置输入路径(如果存在多路输入)

=》         设置复用IO的功能

=》       设置PAD(电气特性)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

设置IO模式

直接分析源代码,请看,

mxc_request_iomux(MX25_PIN_A25, MUX_CONFIG_ALT5);

=>

int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg)

{

       int ret =iomux_config_mux(pin, cfg);                      // 设置为IO模式

       if (IOMUX_TO_GPIO(pin)< MXC_GPIO_IRQS) {

              if (

                            (

                                   (cfg& (~MUX_CONFIG_SION)) == MUX_CONFIG_GPIO) ||

                                 (

                                        ((cfg& (~MUX_CONFIG_SION)) == MUX_CONFIG_FUNC) &&

                                      (

                                      (pin == MX25_PIN_GPIO_A) ||

                                      (pin == MX25_PIN_GPIO_B) ||

                                      (pin == MX25_PIN_GPIO_C) ||

                                      (pin == MX25_PIN_GPIO_D) ||

                                      (pin == MX25_PIN_GPIO_E) ||

                                      (pin == MX25_PIN_GPIO_F)

                               )

                        )

                    )

                     ret |=gpio_request(IOMUX_TO_GPIO(pin), NULL); //清除label

       }

       return ret;

}

=>

static int iomux_config_mux(iomux_pin_name_t pin, iomux_pin_cfg_tcfg)

{

...

       __raw_writel(cfg,mux_reg);                      //写mux寄存器

...

       rp = iomux_pin_res_table+ pin_index;

       if (*rp && *rp!= (cfg | MUX_USED)) {    // 检查使用变化情况

              /*Console: how to do */

              printk(KERN_ERR "iomux_config_mux: Warning: iomuxpin"

                     " configchanged, index=%d register=%p, "

                     " prev=0x%xnew=0x%x\n", pin_index, mux_reg,

                     *rp, cfg);

              ret = -EINVAL;

       }

       *rp = cfg | MUX_USED;                           //记录使用情况

       spin_unlock(&gpio_mux_lock);

 

       return ret;

}

另外ret |= gpio_request(IOMUX_TO_GPIO(pin), NULL);        // 清除label

=>

int gpio_request(unsigned gpio, const char *label)

{

...

// 标志为被使用

       if (test_and_set_bit(FLAG_REQUESTED,&desc->flags) == 0) {

              desc_set_label(desc, label ? : "?");        // 设置label

              status = 0;

       } else {

              status = -EBUSY;

              module_put(chip->owner);

              goto done;

       }

 

       if (chip->request) {                                    //没有定义

...

       }

done:

...

       return status;

}

有上述几段代码,可以设置为GPIO方式主要有两点工作,1.设置SW_MUX_CTL_Register寄存器为GPIO模式,2.清除label。题外,稍微细心点,可以发现这里有一个bug。这里清除label的时候,设置了FLAG_REQUESTED标志。这样,接着下来真正request的时候,判断到FLAG_REQUESTED已经设置,就会直接返回busy。如果校检返回值,这样就永远不会申请到。事实上,能不能申请到只是软件上的一个标志,但是此IO还是会在GPIO模式工作。这里正确的做法应该是用gpio_free来代替gpio_request(IOMUX_TO_GPIO(pin),NULL)。以下,做了个简单的测试。

源程序片段:

mxc_request_iomux(MX25_PIN_A14, MUX_CONFIG_ALT5);

mxc_iomux_set_pad(MX25_PIN_A14, PAD_CTL_PKE_ENABLE );

ret = gpio_request(IOMUX_TO_GPIO(MX25_PIN_A14),"power_latch");

printk(KERN_INFO "MTzhou: ret = %d at%s-%d!\r\n",ret,__FILE__,__LINE__);

输出结果:

结果分析:

#define    EBUSY          16    /* Device or resource busy */

Ret = -16,IO停留在busy状态!

设置IO的PAD属性

设置pad属性其实就是简单的修改SW_PAD_CTL_Register寄存器来设置IO的电气特性。

void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config)

{

       void *pad_reg = IOMUXGPR+ PIN_TO_IOMUX_PAD(pin);

...

       __raw_writel(config,pad_reg);            // 修改pad_reg寄存器

}

申请GPIO

申请gpio在设置IO模式已经讲解,不做重复论述。

控制GPIO

控制gpio就是设置gpio的输出值或者获取输入值,这里简单介绍输出的情况。请看源代码,

gpio_direction_output(IOMUX_TO_GPIO(MX25_PIN_A25),1)

// IOMUX_TO_GPIO是利用引脚获取gpio号

=>

int gpio_direction_output(unsigned gpio, int value)

{

...

       status =chip->direction_output(chip, gpio, value);      //调用direction_output完成输出

...

       return status;

}

曾记否?此处不做重复论述。

port[i].chip.direction_input = mxc_gpio_direction_input;

port[i].chip.direction_output = mxc_gpio_direction_output;

到此,GPIO相关知识论述完毕


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值