一、 概述
这里所说的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相关知识论述完毕