开发平台iTOP4412,主机windows + 虚拟机Ubuntu,内核源码iTOP4412_Kernel_3.0.15,
Source Insight查看内核代码,EXYNOS4412的DATASHEET,可以直接下载(完整版)
首先在内核源码目录下使用命令 ls drivers/gpio/*.o 查看哪些.c文件被编译进内核
查看./drivers/gpio/gpio-exynos4.c ,跳转到文件的最后一行:
core_initcall(exynos4_gpiolib_init);
跳转到exynos4_gpiolib_init函数定义的地方,就在“core_initcall(exynos4_gpiolib_init);”的上方
static __init int exynos4_gpiolib_init(void)
{
struct s3c_gpio_chip *chip;
int i;
int nr_chips;
/* GPIO common part */
chip = exynos4_gpio_common_4bit; //注意这个结构体
nr_chips = ARRAY_SIZE(exynos4_gpio_common_4bit);
for (i = 0; i < nr_chips; i++, chip++) {
if (chip->config == NULL)
chip->config = &gpio_cfg;
if (chip->base == NULL)
pr_err("No allocation of base address for [common gpio]");
}
samsung_gpiolib_add_4bit_chips(exynos4_gpio_common_4bit, nr_chips);
/* Only 4210 GPIO part */
if (soc_is_exynos4210()) {
chip = exynos4210_gpio_4bit;
nr_chips = ARRAY_SIZE(exynos4210_gpio_4bit);
for (i = 0; i < nr_chips; i++, chip++) {
if (chip->config == NULL)
chip->config = &gpio_cfg;
if (chip->base == NULL)
pr_err("No allocation of base address [4210 gpio]");
}
samsung_gpiolib_add_4bit_chips(exynos4210_gpio_4bit, nr_chips);
} else {
/* Only 4212/4412 GPIO part */
chip = exynos4212_gpio_4bit;
nr_chips = ARRAY_SIZE(exynos4212_gpio_4bit);
for (i = 0; i < nr_chips; i++, chip++) {
if (chip->config == NULL)
chip->config = &gpio_cfg;
if (chip->base == NULL)
pr_err("No allocation of base address [4212 gpio]");
}
samsung_gpiolib_add_4bit_chips(exynos4212_gpio_4bit, nr_chips);
}
s5p_register_gpioint_bank(IRQ_GPIO_XA, 0, IRQ_GPIO1_NR_GROUPS);
s5p_register_gpioint_bank(IRQ_GPIO_XB, IRQ_GPIO1_NR_GROUPS, IRQ_GPIO2_NR_GROUPS);
return 0;
}
这个函数定义了三星EXYNOS系列不同产品的GPIO,我的开发平台是EXYNOS4412,因此只看通用的这部分就行了,
注意chip = exynos4_gpio_common_4bit;这个结构体,跳转到exynos4_gpio_common_4bit定义的地方;在内核源码目录下./drivers/gpio/gpio-exynos4.c中:
#if defined(CONFIG_MTK_COMBO_COMM) || defined(CONFIG_MTK_COMBO_COMM_MODULE) //add by cym 20130301
struct s3c_gpio_chip exynos4_gpio_common_4bit[] = {
#else
static struct s3c_gpio_chip exynos4_gpio_common_4bit[] = { //结构体数组,结构体类型为s3c_gpio_chip,等下再看这个结构体类型
#endif
{ 。
。
。
。
。
。
}, { //我们取中间这个结构体,前后都是类似的结构体
.base = (S5P_VA_GPIO2 + 0x100),
.eint_offset = 0x20,
.group = 22,
.chip = {
.base = EXYNOS4_GPL2(0),
.ngpio = EXYNOS4_GPIO_L2_NR,
.label = "GPL2",
},
}, {
。
。
。
。
。
。
}
接着我们来分析这个结构体数组中的元素,把它单独弄出来:我们先看一下成员chip,
跳转至s3c_gpio_chip这个结构体,定义在./arch/arm/plat-samsung/include/plat/gpio-core.h中
struct s3c_gpio_chip {
struct gpio_chip chip;
struct s3c_gpio_cfg *config;
struct s3c_gpio_pm *pm;
void __iomem *base;
int irq_base;
int group;
unsigned int eint_offset;
spinlock_t lock;
#ifdef CONFIG_PM
u32 pm_save[4];
#endif
};
我们发现成员chip依然是一个类型为gpio_chip的结构体,结构体gpio_chip定义在./include/asm-generic/gpio.h中,
struct gpio_chip {
const char *label; //成员1
struct device *dev;
struct module *owner;
int (*request)(struct gpio_chip *chip,
unsigned offset);
void (*free)(struct gpio_chip *chip,
unsigned offset);
int (*direction_input)(struct gpio_chip *chip,
unsigned offset);
int (*get)(struct gpio_chip *chip,
unsigned offset);
int (*direction_output)(struct gpio_chip *chip,
unsigned offset, int value);
int (*set_debounce)(struct gpio_chip *chip,
unsigned offset, unsigned debounce);
void (*set)(struct gpio_chip *chip,
unsigned offset, int value);
int (*to_irq)(struct gpio_chip *chip,
unsigned offset);
void (*dbg_show)(struct seq_file *s,
struct gpio_chip *chip);
int base; //成员2
u16 ngpio; //成员3
const char *const *names;
unsigned can_sleep:1;
unsigned exported:1;
#if defined(CONFIG_OF_GPIO)
/*
* If CONFIG_OF is enabled, then all GPIO controllers described in the
* device tree automatically may have an OF translation
*/
struct device_node *of_node;
int of_gpio_n_cells;
int (*of_xlate)(struct gpio_chip *gc, struct device_node *np,
const void *gpio_spec, u32 *flags);
#endif
};
在这里我们再回到前面结构体数组中的元素chip中的成员
.chip = {
.base = EXYNOS4_GPL2(0),
.ngpio = EXYNOS4_GPIO_L2_NR,
.label = "GPL2",
},
首先是.base = EXYNOS4_GPL2(0),顾名思义,表示GPL2(0)基地址的意思,但这个地址是多少呢?接着往下看
#define EXYNOS4_GPL2(_nr) (EXYNOS4_GPIO_L2_START + (_nr)) //在内核源码目录下./arch/arm/mach-exynos/include/mach/gpio-exynos4.h中宏定义
同样的,查看到EXYNOS4_GPIO_L2_START是枚举类型exynos4_gpio_number的元素
enum exynos4_gpio_number { //在内核源码目录下./arch/arm/mach-exynos/include/mach/gpio-exynos4.h中宏定义
EXYNOS4_GPIO_A0_START = 0,
EXYNOS4_GPIO_A1_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_A0),
EXYNOS4_GPIO_B_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_A1),
EXYNOS4_GPIO_C0_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_B),
EXYNOS4_GPIO_C1_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_C0),
EXYNOS4_GPIO_D0_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_C1),
EXYNOS4_GPIO_D1_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_D0),
EXYNOS4_GPIO_F0_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_D1),
EXYNOS4_GPIO_F1_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_F0),
EXYNOS4_GPIO_F2_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_F1),
EXYNOS4_GPIO_F3_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_F2),
EXYNOS4_GPIO_K0_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_F3),
EXYNOS4_GPIO_K1_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_K0),
EXYNOS4_GPIO_K2_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_K1),
EXYNOS4_GPIO_K3_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_K2),
EXYNOS4_GPIO_L0_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_K3),
EXYNOS4_GPIO_L1_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_L0),
EXYNOS4_GPIO_L2_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_L1), //☆☆☆☆☆☆☆☆☆☆☆☆☆
EXYNOS4_GPIO_X0_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_L2),
EXYNOS4_GPIO_X1_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_X0),
EXYNOS4_GPIO_X2_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_X1),
EXYNOS4_GPIO_X3_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_X2),
EXYNOS4_GPIO_Y0_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_X3),
EXYNOS4_GPIO_Y1_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_Y0),
EXYNOS4_GPIO_Y2_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_Y1),
EXYNOS4_GPIO_Y3_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_Y2),
EXYNOS4_GPIO_Y4_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_Y3),
EXYNOS4_GPIO_Y5_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_Y4),
EXYNOS4_GPIO_Y6_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_Y5),
EXYNOS4_GPIO_Z_START = EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_Y6),
};
再跳到EXYNOS4_GPIO_NEXT宏定义处:
#define EXYNOS4_GPIO_NEXT(__gpio) \ //在内核源码目录下./arch/arm/mach-exynos/include/mach/gpio-exynos4.h中宏定义
((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)
注意这个特殊的宏定义,##连接符号是把传递过来的参数当成字符串进行代替,例如这里的参数是EXYNOS4_GPIO_L1,其中CONFIG_S3C_GPIO_SPACE 也是一个宏定义,其值就是为0,这个宏定义表示的意思就是EXYNOS4_GPIO_NEXT(EXYNOS4_GPIO_L1) = EXYNOS4_GPIO_L1_START + EXYNOS4_GPIO_L1_NR + 1;
这个意思就很明显了,GPIO_L1的下一个GPIO的起始地址就是GPIO_L1的起始地址加上可以配置为GPIO的EXYNOS4_GPIO_L1_NR个数再加1(有点绕口,可能是我没表述清楚,继续往下看),比如说EXYNOS4_GPIO_L1_NR=3,那么GPIO_L1的下一个GPIO的起始地址 = EXYNOS4_GPIO_L1_START + 4,一层层的调用最终可以追溯到内存中表示GPIO的最小地址。
再看元素.ngpio = EXYNOS4_GPIO_L2_NR
宏定义#define EXYNOS4_GPIO_L2_NR (8) //在内核源码目录下./arch/arm/mach-exynos/include/mach/gpio-exynos4.h中宏定义
刚好如前面有类似之处,为了验证我所说的,我们看看EXYNOS4412的DATASHEET
EXYNOS4_GPIO_L2_NR = 8 正好对应GPL2CON控制寄存器,其中可以用作为GPIO分别是KP_COL[7:0]共8个GPIO。(图片麻烦,可以下载链接的数据手册看看)
第三个元素.label = "GPL2",就一个名称,没什么好说的。
着重来看虚拟地址与物理地址的映射关系
.base = (S5P_VA_GPIO2 + 0x100), //基地址 = 虚拟地址 + 偏移地址
数据手册中关于GPL2CON控制寄存器是这样描述的:
Base Address: 0x1100_0000 //基地址
Address = Base Address + 0x0100, Reset Value = 0x0000_0000 //物理地址 = 基地址 + 偏移地址, 复位值 = 0x0000_0000
当然datasheet里的地址都是实际的物理地址,基地址为0x1100_0000,
偏移地址为0x0100与.base = (S5P_VA_GPIO2 + 0x100)中的0x100一致,这没什么好说的。
主要是这个S5P_VA_GPIO2,到底是如何映射的?
用Source Insight来查找有关的S5P_VA_GPIO2定义:
在内核源码目录下./arch/arm/mach-exynos/cpu-exynos4.c中有定义
static struct map_desc exynos4_iodesc[] __initdata = {
。
。
。
。
。
。
}, {
.virtual = (unsigned long)S5P_VA_GPIO2, //此处定义
.pfn = __phys_to_pfn(EXYNOS4_PA_GPIO2),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
。
。
。
。
。
。
}
S5P_VA_GPIO2为结构体数组元素的一个成员赋的值:
类似前面的方法,先看看成员.virtual = (unsigned long)S5P_VA_GPIO2,
#define S5P_VA_GPIO2 S3C_ADDR(0x02240000) ////在内核源码目录下./arch/arm/plat-s5p/include/plat/map-s5p.h中宏定义
跳转到S3C_ADDR定义的地方,在内核源码目录下./arch/arm/plat-samsung/include/plat/map-base.h中宏定义
#define S3C_ADDR_BASE 0xF6000000
#ifndef __ASSEMBLY__
#define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))
#else
#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
#endif
到这里我们可以看到这个虚拟地址的值是0xF6000000 + 0x02240000(同样是 基地址 + 偏移地址);显然这个地址值表示的是在3G~4G之间的范围,超过了内存1G的最大值,当然就是虚拟地址了。#define __phys_to_pfn(paddr) ((paddr) >> PAGE_SHIFT)
这里就是表示物理地址怎么转换为虚拟地址,简单的说就是MMU一个换页的思想,当然其中还有有很多的道道,详细的可以参考一下其他资料。
#define EXYNOS4_PA_GPIO2 0x11000000 //在内核源码目录下./arch/arm/mach-exynos/include/mach/map-exynos4.h中宏定义
此刻,终于找到了实际的物理地址0x11000000这个地址值了,与前面datasheet中的物理地址也对应上了,皆大欢喜。
成员.length = SZ_4K 则表示映射块的大小,我们也来看一下它的值,顾名思义表示4K块大小
#define SZ_4K 0x00001000 //在内核源码目录下./include/asm-generic/sizes.h中宏定义
成员.type = MT_DEVICE,一般也没怎么管,其值就是为0, 没多大意义,也不是我们关注的点。
到此,我们理清了内核是如何实现GPIO物理地址到虚拟地址映射的,但是其中涉及到MMU的工作机制,还值得深入研究,再多捋一捋,无非就是查找调用的函数和一些宏定义,过程也不是很复杂,其实这也是平时阅读内核代码的一种常用手段。
接下来再看一下GPIO底层的调用过程
在内核源码目录下./arch/arm/plat-samsung/include/plat/gpio-cfg.h中定义了这几个函数
/*GPIO引脚功能配置*/
extern int s3c_gpio_cfgpin(unsigned int pin, unsigned int to); //第一个参数表示GPIO的引脚号,例如EXYNOS4_GPL2(0),第二个参数选择以下三个宏定义中的一个
/* Defines for generic pin configurations */
#define S3C_GPIO_INPUT (S3C_GPIO_SPECIAL(0)) //作为输入引脚使用
#define S3C_GPIO_OUTPUT (S3C_GPIO_SPECIAL(1)) //作为输出引脚使用
#define S3C_GPIO_SFN(x) (S3C_GPIO_SPECIAL(x)) //作为其它特殊功能使用
我们看一下函数原型:
int s3c_gpio_cfgpin(unsigned int pin, unsigned int config) //在内核源码目录下./arch/arm/plat-samsung/gpio-config.c
{
struct s3c_gpio_chip *chip = s3c_gpiolib_getchip(pin); //在前面初始化时讨论过这个结构体类型,与前面的一致
unsigned long flags;
int offset;
int ret;
if (!chip)
return -EINVAL;
offset = pin - chip->chip.base;
s3c_gpio_lock(chip, flags); //锁住GPIO,避免共享冲突
ret = s3c_gpio_do_setcfg(chip, offset, config); //配置GPIO
s3c_gpio_unlock(chip, flags); //解锁
return ret;
}
在内核源码目录下./include/linux/gpio.h中定义了这几个函数:
static inline int gpio_request(unsigned gpio, const char *label) //GPIO申请函数
{
return -ENOSYS;
}
static inline int gpio_request_array(const struct gpio *array, size_t num) //多个GPIO以数组形式申请
{
return -ENOSYS;
}
static inline void gpio_free(unsigned gpio) //GPIO释放函数
{
might_sleep();
/* GPIO can never have been requested */
WARN_ON(1);
}
static inline void gpio_free_array(const struct gpio *array, size_t num)
{
might_sleep();
/* GPIO can never have been requested */
WARN_ON(1);
}
static inline int gpio_get_value(unsigned gpio) //获得GPIO引脚的输入输出值
{
/* GPIO can never have been requested or set as {in,out}put */
WARN_ON(1);
return 0;
}
static inline void gpio_set_value(unsigned gpio, int value) //设置GPIO引脚的输入输出值
{
/* GPIO can never have been requested or set as output */
WARN_ON(1);
}
static inline int gpio_to_irq(unsigned gpio) //GPIO作为中断源使用
{
/* GPIO can never have been requested or set as input */
WARN_ON(1);
return -EINVAL;
}
static inline int irq_to_gpio(unsigned irq)
{
/* irq can never have been returned from gpio_to_irq() */
WARN_ON(1);
return -EINVAL;
}
在驱动初始化时,直接调用这几个相关的函数就可以了,首先申请GPIO调用gpio_request函数,配置GPIO是输入输出还是作其它用,调用s3c_gpio_cfgpin函数,这个函数的第二参数选择S3C_GPIO_INPUT、S3C_GPIO_OUTPUT、S3C_GPIO_SFN(x)三者之一就可以了,卸载驱动时,记得释放申请的GPIO,有始有终,这时调用gpio_free函数,一个简单的IO控制就可以这样完成。