转自 http://blog.chinaunix.net/uid-20543672-id-3067021.html
在移植内核的时候经常会遇到管脚复用(mux)的配置问题。在现在的linux内核中,TI的ARM芯片已经有比较通用的架构,这对于很多TI芯片都是通用的,这次以AM335X为例分析,以备后用。
1.硬件
对于许多TI的芯片来说,引脚复用的配置是在,Control Module(配置模块)的寄存器里配置的,(这个和三星的CPU不同,三星一般在GPIO的寄存器中配置)。所以当你需要配置这些寄存器的时候,青岛数据手册的Control Module的Pad Control Registers查找。
TI的CPU芯片手册有两种
一种是DataSheet(DS:数据手册),较小,只是大概介绍下芯片的结构;
另一种是Technical Reference Manual (TRM:技术参考手册)。较大,详细介绍芯片的各部分功能原理和寄存器定义。
在开发过程中,这两个手册都需要参考的,是互补的。
对于AM335X,关于引脚复用的列表及模式号用功能的对应可以再数据手册中找到:
2 Terminal Description:
2.2 Ball Characteristics
关于引脚复用寄存器定义及各引脚相应寄存器的偏移可以再TRM中找到:
9 Control Module
9.2 Functional Description
9.2.2 Pad Control Registers(包含引脚复用寄存器定义)
9.3 Control_Module Registers(包含引脚相应寄存器的偏移)
2.软件
由于TI的芯片架构类似,对于Linux内核来说,早就已经为这个做好了一个软件上的框架,无论在启动的初始化阶段还是在系统运行时,都可以通过这个框架提供的接口函数配置芯片的mux。下面就来简要分析一下。
以AM335X为例,相关的代码位置:arch/arm/mach-omap2
mux.h
mux.c
mux33xx.h
mux33xx.c
board-am335xevm.c
(还有一些用到了:arch/arm/plat-omap/include/plat/omap_hwmod.h)
其中他们的层次关系式:
(1)重要的数据结构
/**
* struct mux_partition- 包含分区的相关信息
*@name :当前的分区名
*@flags: 本分区的特定标志
*@phys : 物理地址
*@size :分区大小
*@base :ioremap 映射过的虚拟地址
*@muxmodes:本分区的mux节点链表头
*@nade :分区链表头
*/
struct omap_mux_partition{
const char * name;
u32 flags;
u32 phys;
u32 size;
void __iomem *base;
struct list_head muxmodes;
struct list_head node;
};
这个数据结构中包含了芯片中几乎所有定义好的mux数据,它在mux数据初始化函数omap_mux_init中初始化,并添加到全局mux_partitions链表中(通过const成员)。而其中的muxmodes是所有mux信息节点的链表头,用来链接一下数据结构:
/**
*struct omap_mux_entry -mux信息节点
*@mux:omap_mux结构体
*@node:链表节点
*/
struct omap_mux_entry{
struct omap_mux mux;
struct list_head node;
};
而在以上数据结构中,struct omap_mux是记录单个节点数据的结构体;
/**
*struct omap_mux -omap mux 寄存器偏移和值的数据
*@reg_offset : 从Control Module寄存器基地址算起的mux寄存器偏移
*@gpio : GPIO编号
*@muxnames :引脚可用的信号模式字符串指针数组
*@balls : 封装中可用的引脚
*/
struct omap_mux {
u16 reg_offset;
u16 gpio;
#ifdef CONFIG_OMAP_MUX
char *muxnames[OMAP_MUX_NR_MODES];
#ifdef CONFIG_DEBUG_FS
char *balls[OMAP_MUX_NR_SIDES];
#endif
#endif
};
而struct mux_partition中muxmodes链表及节点数据的初始化都是在omap_mux_init初始化函数中(omap_mux_init_list(partition,superset);),而struct omap_mux节点数据中信息是由mux33xx.h和mux33xx.c中提供的。你可以再mux33xx.c中看到一个巨大的struct omap_mux结构体数组的初始化代码,这个代码一看就明了。不同的芯片只需要根据芯片资料修改这个结构体就好了,但是am33xx这个结构体(当前)还不完善,gpio的数据还都是0.值得一提的是其中用到了一个宏:
#define _AM33XX_MUXENTRY(M0,g,m0,m1,m2,m3,m4,m5,m6,m7) \
{
.reg_offset =(AM33XX_CONTROL_PADCONF_##M0##_OFFSET),
.gpio=(g),
.muxnames={m0,m1,m2,m3,m4,m5,m6,m7},
}
这个宏使得这个数据体数组的初始化变得清晰明了。
以上的数据结构是在系统初始化的时候使用的,在struct omap_mux_partition完成初始化后,omap_mux_init初始化函数最后会根据不同的板子初始化部分mux寄存器(omap_mux_init_signals(partition,board_mux);),其中牵涉到了以下结构体:
/**
*struct omap_board_mux -初始化mux寄存器的数据
*@reg_offset : 从Control Module寄存器基地址算起的Mux寄存器偏移
*@mux_value: 希望设置的mux寄存器值
*/
struct omap_board_mux{
u16 reg_offset;
u16 value;
};
以在最上层的板级初始化文件(board-am335xevm.c)中定义一个这样的结构体数组,确定所要初始化的引脚复用寄存器,交由omap_mux_init_signals(partition,board_mux);使用。例如:
#ifdef CONFIG_OMAP_MUX
static struct omap_board_mux board_mux[] __initdata ={
AM33XX_MUX(I2C0_SDA,OMAP_MUX_MODE0|AM33XX_SLEWCTRL_SLOW|AM33XX_INPUT_EN|AM33XX_PIN_OUTPUT),
AM33XX_MUX(I2C0_SCL,OMAP_MUX_MODE0|AM33XX_SLEWCRTL_SLOW|AM33XX_INPUT_EN|AM33XX_PIN_OUTPUT),
{ .reg_offset=OMAP_MUX_TERMINATOR},
};
#else
#define board_mux null
#endif
其中用到了一个宏
/*如果引脚没有定义为输入,拉动电阻将会被禁用
*如果定义为输入,所提供的标志位将确定拉动电阻的配置
*/
#define AM33XX_MUX(mode0,mux_value)
{
.reg_offset =(AM33XX_CONTROL_PADCONF_##mode0##_OFFSET), \
.value =(((mux_value)&AM33XX_INPUT_EN)?(mux_value):((max_value)|(AM33XX_PULL_DISA))),
}
注意 __AM33XX_MUXENTRY和AM33xx_MUX这两个宏,前者是用于struct omap_mux的;后者是用于struct omap_board_mux的。
(2)重要的接口函数
/**
*omap_mux_init - mux初始化的私有函数,请勿使用
*由各板级特定的MUX初始化函数调用
*/
int omap_mux_init(const char *name,u32 flags,
u32 mux_pbase, u32 mux_size,
struct omap_mux *superset,
struct omap_mux *package_subset,
struct omap_board_mux *board_mux,
struct omap_ball *package_balls
);
这个函数式内部用于初始化struct mux_partition的最重要函数,但是这个函数并不作为接口函数使用,而是供各芯片初始化函数“*_mux_init”所使用的。比如AM33XX芯片;
/**
*am33xx_mux_init() -用板级特定的配置来初始化MUX系统
*@board_mux; -板级特定的MUX配置表
*/
int __init am33xx_mux_init(struct omap_board_mux *board_subset)
{
return omap_mux_init("core",0,AM33XX_CONTROL_PADCONF_MUX_PBASE,
AM33XX_CONTROL_PADCONF_MUX_SIZE,am33xx_mux_modes,
NULL,board_subset,NULL
);
}
有了已经初始化好的struct mux_partition结构体,我们可以利用mux.h提供的许多函数方便的初始化各mux寄存器:
/**
void omap_mux_set_gpio(u16 val, int gpio);
但是am33xx的gpio成员(当前)还都是0,所有这些函数没法使用。
此外,在mux.h中还导出了其他的软件接口和数据结构,这些在am33xx中没有使用,有需要的时候再看。
在板级初始化代码(比如board-am335xevm.c)运行完芯片特定的MUX初始化函数(am33xx_mux_init(board_mux);)之后,也可以在各子系统初始化时通过上面的接口函数修改配置MUX,比如在am33xx中使用了自己封装的一个函数和结构体:
/* 模块引脚复用结构体 */
struct pinmux_config {
const char *string_name; /* 信号名格式化字符串,“模式0字符串.目标模式字符串“ */
int val; /* 其他mux寄存器可选配置值 */
};
/*
* @pin_mux - 单个模块引脚复用结构体
* 其中定义了本模块所有引脚复用细节.
*/
static void setup_pin_mux(struct pinmux_config *pin_mux)
{
int i;
for (i = 0; pin_mux->string_name != NULL; pin_mux++)
omap_mux_init_signal(pin_mux->string_name, pin_mux->val);
}
你可以在board-am335xevm.c中看到如下的代码:
static struct pinmux_config d_can_ia_pin_mux[] = {
{"uart0_rxd.d_can0_tx", OMAP_MUX_MODE2 | AM33XX_PULL_ENBL},
{"uart0_txd.d_can0_rx", OMAP_MUX_MODE2 | AM33XX_PIN_INPUT_PULLUP},
{NULL, 0},
};
......
static void d_can_init(int evm_id, int profile)
{
switch (evm_id) {
case IND_AUT_MTR_EVM:
if ((profile == PROFILE_0) || (profile == PROFILE_1)) {
setup_pin_mux(d_can_ia_pin_mux);
/* Instance Zero */
am33xx_d_can_init(0);
}
break;
case GEN_PURP_EVM:
if (profile == PROFILE_1) {
setup_pin_mux(d_can_gp_pin_mux);
/* Instance One */
am33xx_d_can_init(1);
}
break;
default:
break;
}
}
三、使用注意
上面初始化过的结构体和接口函数的定义都是带有"__init"和“__initdata”的,所以这些都只能在内核初始化代码中使用,一旦系统初始化结束并进入了文件系统,这些定义都会被free。所有它们不能在内核模块(.ok)中被调用,否则你就等着Oops吧。因为一个芯片的引脚复用一般是硬件设计的时候定死的,一般不可能在启动后更改。如果你是在要在模块中改变引脚复用配置,你只能通过自己ioremap相关寄存器再修改它们来实现。