穷根究底linux内核函数之s3c2410_gpio_cfgpin()
flyriz 2012-07-06
在ARM驱动程序的学习过程中,经常碰到对IO操作的函数:s3c2410_gpio_cfgpin(),结合linux内核源代码(版本linux-2.6.29.4),做一个详细的分析,以如下代码为例:
s3c2410_gpio_cfgpin(S3C2410_GPB5,S3C2410_GPB5_OUTP);
从函数名上来看,其作用是把S3C2410的GPB5引脚设置为输出,接下来就像做数学题一样进行化简吧,为方便描述,过程中用等号表示。
先把参数展开,参数S3C2410_GPB5 宏展开:
S3C2410_GPB5 = S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5)
S3C2410_GPIO_BANKB = (32*1)
S3C2410_GPIONO(bank,offset) = ((bank) + (offset))
S3C2410_GPB5 = (32*1)+5
参数S3C2410_GPB5_OUTP 宏展开:
S3C2410_GPB5_OUTP = (0x01 << 10)
参数简化后的函数:
s3c2410_gpio_cfgpin(S3C2410_GPB5,S3C2410_GPB5_OUTP)=
s3c2410_gpio_cfgpin( (32*1)+5,(0x01 << 10) )
接下为再对函数本身化简,进入函数:s3c2410_gpio_cfgpin(unsigned int pin, unsigned int )
void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int )
{
void __iomem *base = S3C24XX_GPIO_BASE(pin);
unsigned long mask;
unsigned long con;
unsigned long flags;
if (pin < S3C2410_GPIO_BANKB) {
mask = 1 << S3C2410_GPIO_OFFSET(pin);
} else {
mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;
}
switch () {
case S3C2410_GPIO_LEAVE:
mask = 0;
= 0;
break;
case S3C2410_GPIO_INPUT:
case S3C2410_GPIO_OUTPUT:
case S3C2410_GPIO_SFN2:
case S3C2410_GPIO_SFN3:
if (pin < S3C2410_GPIO_BANKB) {
-= 1;
&= 1;
<<= S3C2410_GPIO_OFFSET(pin);
} else {
&= 3;
<<= S3C2410_GPIO_OFFSET(pin)*2;
}
}
/* modify the specified register wwith IRQs off */
local_irq_save(flags);
con = __raw_readl(base + 0x00);
con &= ~mask;
con |= ;
__raw_writel(con, base + 0x00);
local_irq_restore(flags);
}
现在看第一个子函数:
S3C24XX_GPIO_BASE(pin),这也是最复杂的一个子函数,宏展开后:
S3C24XX_GPIO_BASE(pin) = S3C2410_GPIO_BASE(pin)
= ( ( ( (pin) & ~31 ) >> 1 ) + S3C24XX_VA_GPIO )
S3C24XX_VA_GPIO = ( (S3C24XX_PA_GPIO-S3C24XX_PA_UART) + S3C24XX_VA_UART )
S3C24XX_PA_GPIO = S3C2410_PA_GPIO = (0x56000000)
S3C24XX_PA_UART = S3C2410_PA_UART = (0x50000000)
S3C24XX_VA_UART = S3C_VA_UART = S3C_ADDR(0x01000000)
S3C_ADDR(x)=(S3C_ADDR_BASE + (x))
S3C_ADDR_BASE=(0xF4000000)
S3C24XX_VA_UART=(0xF4000000)+(0x01000000)=(0xF5000000)
S3C24XX_VA_GPIO =(0x56000000)-(0x50000000)+(0xF5000000)
=0xFB000000
最后:S3C24XX_GPIO_BASE(pin)=( ( ( (pin) & ~31 ) >> 1 ) + 0xFB000000
说明:
( ( ( (pin) & ~31 ) >> 1 ),这个东东是为了得到指定的pin引脚的寄存器相对于GPIO基地址的偏移量,至于为什么要这样写我接下来会说明。
S3C24XX_VA_GPIO,GPIO基地址的虚拟地址。顺便说明一下VA,PA,VA:虚拟地址,PA:物理地址,都是英语单词的首字母。现在很好理解了,先根据GPIO,UART的物理地址算出两者的偏移量:(S3C24XX_PA_GPIO-
S3C24XX_PA_UART),然后UART虚拟地址加上这个偏移量就得到GPIO的虚拟地址了:( (S3C24XX_PA_GPIO-
S3C24XX_PA_UART) + S3C24XX_VA_UART )。至于S3C24XX_VA_UART为什么最后就变成了(0xF5000000),我也暂时不知道,内核代码就是这么写的,应该是这样一个转换规则吧。
分析完这个子函数,接下来的代码就很好理解了。有一个地方要注意一下:if (pin < S3C2410_GPIO_BANKB),两次出现这个表达式,因为这个芯片的PORT A与其他的端口的不一样,它能实现的功能比较少,GPACON中只用一个数据位来控制这个引脚的功能,其他的比如PORT B的GPBCON寄存器,是用两个数据位来控制的。所以对PORT A的引脚要单独处理了。
有一个问题:
S3C_VA_UART = S3C_ADDR(0x01000000),这里的UART虚拟地址转制为什么是这样子的?
再看一下源代码里面的其他虚拟地址的转换:
#define S3C_VA_IRQ S3C_ADDR(0x00000000) /* irq controller(s) */
#define S3C_VA_SYS S3C_ADDR(0x00100000) /* system control */
#define S3C_VA_MEM S3C_ADDR(0x00200000) /* system control */
#define S3C_VA_TIMER S3C_ADDR(0x00300000) /* timer block */
#define S3C_VA_WATCHDOG S3C_ADDR(0x00400000) /* watchdog */
#define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */
这个S3C_ADDR里面的内容是如何确定的?
S3C_ADDR_BASE=(0xF4000000),这个基地址又是怎么确定的?
期待高手的解答,谢谢!