在mini2440_leds_misc.c里有这样的一段代码,而纵观整个c文件里面,其实真正核心的代码,我感觉就一句了: s3c2410_gpio_setpin(led_table[i], !cmd); 而这一句里面,堪称核心的就是s3c2410_gpio_setpin 了。其详细讲解。对于理解arm-linux对于24x0系列开发板的地址映射有着异常强悍的指导意义。 1 case MEMDEV_IOCON: 2 /*灯全亮*/ 3 for(i=0;i<4;i++) 4 s3c2410_gpio_setpin(led_table[i], !cmd); 5 return 0; 作用:设置相应GPIO口的值, 如pin=S3C2410_GPB5 to=0 则:设置S3C2410_GPB5的输出值为0 如pin=S3C2410_GPB5 to=1 则:设置S3C2410_GPB5的输出值为1 void s3c2410_gpio_setpin(unsigned int pin, unsigned int to) { void __iomem *base = S3C2410_GPIO_BASE(pin); unsigned long offs = S3C2410_GPIO_OFFSET(pin); unsigned long flags; unsigned long dat; local_irq_save(flags); dat = __raw_readl(base + 0x04); dat &= ~(1 << offs); dat |= to << offs; __raw_writel(dat, base + 0x04); local_irq_restore(flags); } EXPORT_SYMBOL(s3c2410_gpio_setpin); 说明: 1. S3C2410_GPIO_BASE(pin)------------- 在linux/include/asm/hardware/s3c2410/regs-gpio.h中 #define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) +S3C24XX_VA_GPIO) S3C24XX_VA_GPIO------------ 在linux/include/asm-arm/arch-s3c2410/map.h中 #define S3C24XX_VA_GPIO S3C2410_ADDR(0x00E00000) ----GPIO的虚拟偏移地址:0x00E00000 #define S3C2410_ADDR(x) (0xF0000000 + (x)) ----所有寄存器的虚拟首地址:0xF0000000 则: S3C24XX_VA_GPIO =0xF0E0 0000 ----GPIO的虚拟首地址 如:pin = S3C2410_GPB5 而在linux/include/asm/hardware/s3c2410/regs-gpio.h中 #define S3C2410_GPB5 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5) #define S3C2410_GPIONO(bank,offset) ((bank) + (offset)) #define S3C2410_GPIO_BANKB (32*1) 则: S3C2410_GPB5 =32*1+5=37 由: #define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) +S3C24XX_VA_GPIO) pin = S3C2410_GPB5 = 37 S3C24XX_VA_GPIO =0xF0E0 0000 则: S3C2410_GPIO_BASE(S3C2410_GPB5) ((((S3C2410_GPB5) & ~31) >> 1) +0xF0E0 0000 ) S3C2410_GPIO_BASE(37) ((((37) & ~31) >> 1) +0xF0E0 0000 ) S3C2410_GPIO_BASE(37) =((10 0101 &0 0000)>>1)+0xF0E0 0000 = 1 0000+0xF0E0 0000 = 0xF0E1 0000 ~31主要是为了清除S3C2410_GPB5的后5位, 以上表明,linux内核驱动里面操作的都不是GPIO的物理地址,都是对映射之后的虚拟地址进行操作的。 在map中MMU将虚拟地址映射到物理地址中的范围是, 虚拟地址的范围是:0x0000 0000 -------------0x4F00 0000 物理地址的范围是:0x4800 0000--------------0x5B00 001C (这两个地方不懂) #define S3C24XX_VA_GPIO S3C2410_ADDR(0x00E00000) #define S3C2400_PA_GPIO (0x15600000) #define S3C2410_PA_GPIO (0x56000000) #define S3C24XX_SZ_GPIO SZ_1M S3C24XX_VA_GPIO 表示: S3C24XX GPIO的Virtual Address S3C2400_PA_GPIO 表示: S3C2400 GPIO的Physicla Address S3C2410_PA_GPIO 表示: S3C2410 GPIO的Physicla Address S3C24XX_SZ_GPIO SZ_1M 表示:S3C24XX每个段(虚拟地址中)划分为1M 如:ARM920T是32位的,则虚拟内存空间为4GB,共划分为4096个1M大小的段 |
3 ARM的IO内存映射计算及分析
UART映射后的虚拟地址是0xF4000000+0x01000000,而物理地址GPIO=0x56000000 UART=0x50000000,void __iomem *base = S3C24XX_GPIO_BASE(pin)中的base是通过UART的虚拟地址加上GPIO和UART 的差,进而计算出GPIO的虚拟地址S3C24XX_VA_GPIO=((S3C24XX_PA_GPIO - S3C24XX_PA_UART)+S3C24XX_VA_UART=0x56000000-0x50000000+S3C24XX_VA_UART=0x060000000+(0xf4000000+0x01000000)=0xfb000000,
所以A组GPIO的基地址为 0xfb000000 B组的基地址为:0xfb000000+(0b100000>>1)=0xfb000010 ,即 S3C24XX_GPIO_BASE=0xfb000010,C组基地址为 2*10+2*(2-2)+0xfb000000=0b10100+0xfb000000=0x14+0xfb000000=0xfb000014,DEFGH这些组可以类推,如D组比C组基地址大12,E组比D组基地址大12……
S3C2410_GPIO_OFFSET的定义如下:
#define S3C2410_GPIO_OFFSET(pin) ((pin) & 31)
可知GPB(5)的偏移量就是5对应GPBCON 的第五位,对应控制断口的第五位。
而在硬件手册上找到ARM920T的GPIO物理地址:
GPACON 0x56000000 GPADAT 0x56000004 Ox56000008 0x5600000c 这两个地址保留
PGBCON 0x56000010 GPBDAT 0x56000014 GPBUP 0x56000018 Reserved 0x5600001c
PGCCON 0x56000020 GPCDAT 0x56000024 GPCUP 0x56000028 Reserved 0x5600002c
由此可知道S3C24XX_GPIO_BASE=0xfb000010就是GPBCON的地址,S3C24XX_GPIO_BASE+0x04=(base+0x04)就是GPBDAT的地址,所以下面语句就是向从GPBDAT读数据到dat,经过修改之后(根据穿过来的参数,设置对应位,这里是GPB(5),也就是修改GPBDAT中的第五位)再次写入GPBDAT
dat = __raw_readl(base + 0x04);
dat &= ~(1<< offs);
dat |= to << offs;
__raw_writel(dat, base + 0x04);
以上IO物理地址映射为虚拟地址的过程就是ARM IO 地址映射的过程
local_irq_save(flags);是关中断
local_irq_restore(flags);是开中断
4 一些琐碎的话
在Documents/arm/s3c24xx中有关于s3c24xxcpu的一些文档,很好,可以看看。比如有关gpio的文档。
GPIO 在使用的时候首先是对所有的GPIO 进行了分类A B C D E F G H J 这几大类,每一类都有32个GPIO,
在逻辑上对他们进行了地址的规划,地址是从0(还是1?有待细看)一直到32*9 这么多地址,
这些地址进而又与虚拟地址相对应,linux中端口的地址都是把对应的物理地址加一个偏移量形成了物理地址,
这个偏移量从0xf4000000 开始的,然后再以0xf4000000为基地址进行映射(那些编号就是相对从0xf4000000开始映射的GPIO地址的偏移地址)。这样就从CPU引脚的物理地址变换成了虚拟地址。