编写按键驱动时,想知道内核是如何管理GPIO的,所以开始追踪代码,中间走了一些弯路,现记录于此。
追踪代码之前,我猜测:第一,这部分代码应该在系统set up阶段执行;第二,GPIO的代码应该在machine或者platform或者vendor相关的目录下。事实证明,第一点是正确的,第二点基本是错误的,因为内核依靠对GPIO的抽象来管理之,这层抽象层给具体的machine留出了一些它们需要是实现的接口,这与其他的设备驱动框架在使用上是很类似的,当然,GPIO也是一种设备啊... ...所以,管理GPIO的多数代码位于drivers/gpio/目录下。
好了,开始走读代码,那么对于S5PV210这块SoC,GPIO子系统的入口函数在哪里呢?在drivers/gpio/gpio-s5pv210.c中,入口函数的实现如下图所示:
1 static __init int s5pv210_gpiolib_init(void) 2 { 3 struct s3c_gpio_chip *chip = s5pv210_gpio_4bit; 4 int nr_chips = ARRAY_SIZE(s5pv210_gpio_4bit); 5 int gpioint_group = 0; 6 int i = 0; 7 8 for (i = 0; i < nr_chips; i++, chip++) { 9 if (chip->config == NULL) { 10 chip->config = &gpio_cfg; 11 chip->group = gpioint_group++; 12 } 13 if (chip->base == NULL) 14 chip->base = S5PV210_BANK_BASE(i); 15 } 16 17 samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips); 18 s5p_register_gpioint_bank(IRQ_GPIOINT, 0, S5P_GPIOINT_GROUP_MAXNR); 19 20 return 0; 21 } 22 core_initcall(s5pv210_gpiolib_init);
入口函数没几行代码,但是下面所有的内容都要从它开始,所以一点一点来吧,这里先罗列一下下面要叙述的内容:
1. struct s3c_gpio_chip的内容,以及重要的数组s5pv210_gpio_4bit;
2. 上述结构体中的成员config和base;
3. samsung_gpiolib_add_4bit_chips()的分析,这部分比较长。
第一部分,struct s3c_gpio_chip的介绍以及重要的数组s5pv210_gpio_4bit
1 struct s3c_gpio_chip { 2 struct gpio_chip chip; 3 struct s3c_gpio_cfg *config; 4 struct s3c_gpio_pm *pm; 5 void __iomem *base; 6 int irq_base; 7 int group; 8 spinlock_t lock; 9 #ifdef CONFIG_PM 10 u32 pm_save[4]; 11 #endif 12 };
从面向对象的角度来看,s3c_gpio_chip是gpio_chip的子类,但是又新增了一些新的属性和操作。虽说它的名字是chip,但是一个s3c_gpio_chip描述的是一个GPIO bank,如GPA0、GPA1、GPB等等, 这一点在接着要介绍的数组s5pv210_gpio_4bit中得以清晰体现。
base成员表示的是当前bank的控制寄存器的起始虚拟地址,注意是虚拟地址。
pm_save[]用于功耗管理,进入低功耗模式之前将相关寄存器的值存到该数组中,退出低功耗模式时将该数组的内容回写到各寄存器中。
config成员的类型是struct s3c_gpio_cfg,具体的结构是:
1 struct s3c_gpio_cfg { 2 unsigned int cfg_eint; 3 4 s3c_gpio_pull_t (*get_pull)(struct s3c_gpio_chip *chip, unsigned offs); 5 int (*set_pull)(struct s3c_gpio_chip *chip, unsigned offs, 6 s3c_gpio_pull_t pull); 7 8 unsigned (*get_config)(struct s3c_gpio_chip *chip, unsigned offs); 9 int (*set_config)(struct s3c_gpio_chip *chip, unsigned offs, 10 unsigned config); 11 };
可见config成员用于控制和查看当前bank的某个pin的上下拉电阻的状态,以及设置和查看某引脚上的复用功能,这个成员作为子类struct s3c_gpio_cfg的新成员,说明以上这两种功能在不同的machine以及GPIO IP上的差别是不能忽略的。
struct s3c_gpio_cfg中很重要的一个成员就是它的父类struct gpio_chip,它内容很多,不过看成员名字就基本能明白含义:
1 struct gpio_chip { 2 const char *label; 3 struct device *dev; 4 struct module *owner; 5 6 int (*request)(struct gpio_chip *chip, 7 unsigned offset); 8 void (*free)(struct gpio_chip *chip, 9 unsigned offset); 10 11 int (*direction_input)(struct gpio_chip *chip, 12 unsigned offset); 13 int (*get)(struct gpio_chip *chip, 14 unsigned offset); 15 int (*direction_output)(struct gpio_chip *chip, 16 unsigned offset, int value); 17 int (*set_debounce)(struct gpio_chip *chip, 18 unsigned offset, unsigned debounce); 19 20 void (*set)(struct gpio_chip *chip, 21 unsigned offset, int value); 22 23 int (*to_irq)(struct gpio_chip *chip, 24 unsigned offset); 25 26 void (*dbg_show)(struct seq_file *s, 27 struct gpio_chip *chip); 28 int base; 29 u16 ngpio; 30 const char *const *names; 31 unsigned can_sleep:1; 32 unsigned exported:1; 33 34 #if defined(CONFIG_OF_GPIO) 35 /* 36 * If CONFIG_OF is enabled, then all GPIO controllers described in the 37 * device tree automatically may have an OF translation 38 */ 39 struct device_node *of_node; 40 int of_gpio_n_cells; 41 int (*of_xlate)(struct gpio_chip *gc, struct device_node *np, 42 const void *gpio_spec, u32 *flags); 43 #endif 44 };
这个结构体中的request free等函数指针与gpio的request、free、direction_input、direction_output等函数的实现有关系。
接着看一下数组s5pv210_gpio_4bit,首先,这个名字中包含4bit,意思是每个bank的gpio控制寄存器中,每4bit控制一个gpio pin。这个数组很长,我们只取出个别元素看一下:
1 static struct s3c_gpio_chip s5pv210_gpio_4bit[] = { 2 { 3 .chip = { 4 .base = S5PV210_GPA0(0), 5 .ngpio = S5PV210_GPIO_A0_NR, 6 .label = "GPA0", 7 }, 8 }, { 9 .chip = { 10 .base = S5PV210_GPA1(0), 11 .ngpio = S5PV210_GPIO_A1_NR, 12 .label = "GPA1", 13 }, 14 }, { 15 .chip = { 16 .base = S5PV210_GPB(0), 17 .ngpio = S5PV210_GPIO_B_NR, 18 .label = "GPB", 19 }, 20 }, 21 ... ... ... ... ... ... ... ... 22 { 23 .base = (S5P_VA_GPIO + 0xC40), 24 .config = &gpio_cfg_noint, 25 .irq_base = IRQ_EINT(16), 26 .chip = { 27 .base = S5PV210_GPH2(0), 28 .ngpio = S5PV210_GPIO_H2_NR, 29 .label = "GPH2", 30 .to_irq = samsung_gpiolib_to_irq, 31 }, 32 }, { 33 .base = (S5P_VA_GPIO + 0xC60), 34 .config = &gpio_cfg_noint, 35 .irq_base = IRQ_EINT(24), 36 .chip = { 37 .base = S5PV210_GPH3(0), 38 .ngpio = S5PV210_GPIO_H3_NR, 39 .label = "GPH3", 40 .to_irq = samsung_gpiolib_to_irq, 41 }, 42 }, 43 };
这个数组描述了一些S5PV210上的gpio,但是对比数据手册,有一些gpio并没有出现在这里,但是这已经足够多了,可以说这个数组描述了系统中多数能够使用的gpio,并初始化了一些信息,比如当前bank的第一个pin的编号、这个bank中所有pin的数量等等,这些都是宏定义,具体的需要看mach-s5pv210相关的头文件,由于涉及到的宏太多,而且没什么难度,这里就不记录了。
第二部分,struct s3c_gpio_chip中的config成员和base成员初始化。
这一部分的处理在s5pv210_gpiolib_init函数中,可以看到s5pv210_gpio_4bit中很多元素的config成员没有初始化,也就是NULL,那么这里就需要给其赋值为gpio_cfg,这个变量就在当前文件中,定义如下:
1 static struct s3c_gpio_cfg gpio_cfg = { 2 .set_config = s3c_gpio_setcfg_s3c64xx_4bit, 3 .set_pull = s3c_gpio_setpull_updown, 4 .get_pull = s3c_gpio_getpull_updown, 5 };
下面就看看这三个函数吧,看名字应该就能知道其功能了。
#ifdef CONFIG_S3C_GPIO_CFG_S3C64XX int s3c_gpio_setcfg_s3c64xx_4bit(struct s3c_gpio_chip *chip, unsigned int off, unsigned int cfg) { void __iomem *reg = chip->base; unsigned int shift = (off & 7) * 4; u32 con; if (off < 8 && chip->chip.ngpio > 8) reg -= 4; if (s3c_gpio_is_cfg_special(cfg)) { cfg &= 0xf; cfg <<= shift; } con = __raw_readl(reg); con &= ~(0xf << shift); con |= cfg; __raw_writel(con, reg); return 0; }
代码逻辑很简单,获取当前bank的控制寄存器的虚拟地址,根据offset计算要控制的引脚在控制寄存器中的起始位,然后将要设置的cfg值写入到控制寄存器,完成工作。不过这里有个宏CONFIG_S3C_GPIO_CFG_S3C64XX,查看相关的Kconfig就可知道,一旦选中s5p相关的平台,那么S3C_GPIO_CFG_S3C64XX这个宏一定会定义。
刚刚写了很多,结果360一优化浏览器直接退出没保存,悲剧。
上面的数组中很多元素的base都没有初始化,就需要使用宏定义S5PV210_BANK_BASE计算:
/drivers/gpio/gpio-s5pv210.c
1 #define S5PV210_BANK_BASE(bank_nr) (S5P_VA_GPIO + ((bank_nr) * 0x20))
/arch/arm/plat-s5p/include/plat/map-s5p.h
1 #define S5P_VA_GPIO S3C_ADDR(0x02200000)
/arch/arm/plat-s5p/include/plat/map-base.h
1 #define S3C_ADDR_BASE 0xF6000000 2 3 #ifndef __ASSEMBLY__ 4 #define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x)) 5 #else 6 #define S3C_ADDR(x) (S3C_ADDR_BASE + (x)) 7 #endif
经过计算,S5P_VA_GPIO在3GB+898M处。
由于每个gpio bank的相关寄存器都占用0x20的内存空间,所以可以同等差数列通项公式来计算各个bank的起始地址。
第三部分,samsung_gpiolib_add_4bit_chips的分析
/drivers/gpio/gpio-plat-samsung.c
1 void __init samsung_gpiolib_add_4bit_chips(struct s3c_gpio_chip *chip, 2 int nr_chips) 3 { 4 for (; nr_chips > 0; nr_chips--, chip++) { 5 samsung_gpiolib_add_4bit(chip); 6 s3c_gpiolib_add(chip); 7 } 8 }
1 void __init samsung_gpiolib_add_4bit(struct s3c_gpio_chip *chip) 2 { 3 chip->chip.direction_input = samsung_gpiolib_4bit_input; 4 chip->chip.direction_output = samsung_gpiolib_4bit_output; 5 chip->pm = __gpio_pm(&s3c_gpio_pm_4bit); 6 }
samsung_gpiolib_4bit_input、samsung_gpiolib_output是设置s3c_gpio_chip->chip的函数指针成员,功能如函数名字所示,因为三星的SoC的GPIO IP的寄存器组织形式很相似,所以就直接以samsung_gpiolib开头命名函数了。以上两个函数都是在操作寄存器,代码逻辑很简单。
__gpio_pm(&s3c_gpio_pm_4bit)是跟功耗管理相关的函数指针,__gpio_pm是一个宏:
1 #ifdef CONFIG_PM 2 extern struct s3c_gpio_pm s3c_gpio_pm_1bit; 3 extern struct s3c_gpio_pm s3c_gpio_pm_2bit; 4 extern struct s3c_gpio_pm s3c_gpio_pm_4bit; 5 #define __gpio_pm(x) x 6 #else 7 #define s3c_gpio_pm_1bit NULL 8 #define s3c_gpio_pm_2bit NULL 9 #define s3c_gpio_pm_4bit NULL 10 #define __gpio_pm(x) NULL 11 12 #endif /* CONFIG_PM */
1 struct s3c_gpio_pm s3c_gpio_pm_4bit = { 2 .save = s3c_gpio_pm_4bit_save, 3 .resume = s3c_gpio_pm_4bit_resume, 4 };
上述两个函数就是功耗管理的函数,save函数就是在gpio模块进入休眠之前,将各个寄存器的值存入到pm_save数组中,退出休眠状态时,再将数组中的内容回写到各寄存器,实现的时候也只是读写寄存器,不多说。
接下来分析s3c_gpiolib_add()函数
/arch/arm/plat-samsung/gpio.c
1 __init void s3c_gpiolib_add(struct s3c_gpio_chip *chip) 2 { 3 struct gpio_chip *gc = &chip->chip; 4 int ret; 5 6 BUG_ON(!chip->base); 7 BUG_ON(!gc->label); 8 BUG_ON(!gc->ngpio); 9 10 spin_lock_init(&chip->lock); 11 12 if (!gc->direction_input) 13 gc->direction_input = s3c_gpiolib_input; 14 if (!gc->direction_output) 15 gc->direction_output = s3c_gpiolib_output; 16 if (!gc->set) 17 gc->set = s3c_gpiolib_set; 18 if (!gc->get) 19 gc->get = s3c_gpiolib_get; 20 21 #ifdef CONFIG_PM 22 if (chip->pm != NULL) { 23 if (!chip->pm->save || !chip->pm->resume) 24 printk(KERN_ERR "gpio: %s has missing PM functions ", 25 gc->label); 26 } else 27 printk(KERN_ERR "gpio: %s has no PM function ", gc->label); 28 #endif 29 30 /* gpiochip_add() prints own failure message on error. */ 31 ret = gpiochip_add(gc); 32 if (ret >= 0) 33 s3c_gpiolib_track(chip); 34 }
s3c_gpiolib_add初始化s3c_gpio_chip->gpio_chip->direction_input、s3c_gpio_chip->gpio_chip->direction_output、s3c_gpio_chip->gpio_chip->set、s3c_gpio_chip->gpio_chip->get,s3c_gpiolib_xxx实现时依然是操作各寄存器,这个函数的核心是gpiochip_add(struct gpio_chip *chip):
/drivers/gpio/gpiolib.c
1 int gpiochip_add(struct gpio_chip *chip) 2 { 3 unsigned long flags; 4 int status = 0; 5 unsigned id; 6 int base = chip->base; 7 8 if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1)) 9 && base >= 0) { 10 status = -EINVAL; 11 goto fail; 12 } 13 14 spin_lock_irqsave(&gpio_lock, flags); 15 16 if (base < 0) { 17 base = gpiochip_find_base(chip->ngpio); 18 if (base < 0) { 19 status = base; 20 goto unlock; 21 } 22 chip->base = base; 23 } 24 25 /* these GPIO numbers must not be managed by another gpio_chip */ 26 for (id = base; id < base + chip->ngpio; id++) { 27 if (gpio_desc[id].chip != NULL) { 28 status = -EBUSY; 29 break; 30 } 31 } 32 if (status == 0) { 33 for (id = base; id < base + chip->ngpio; id++) { 34 gpio_desc[id].chip = chip; 35 36 /* REVISIT: most hardware initializes GPIOs as 37 * inputs (often with pullups enabled) so power 38 * usage is minimized. Linux code should set the 39 * gpio direction first thing; but until it does, 40 * we may expose the wrong direction in sysfs. 41 */ 42 gpio_desc[id].flags = !chip->direction_input 43 ? (1 << FLAG_IS_OUT) 44 : 0; 45 } 46 } 47 48 of_gpiochip_add(chip); 49 50 unlock: 51 spin_unlock_irqrestore(&gpio_lock, flags); 52 53 if (status) 54 goto fail; 55 56 status = gpiochip_export(chip); 57 if (status) 58 goto fail; 59 60 return 0; 61 fail: 62 /* failures here can mean systems won't boot... */ 63 pr_err("gpiochip_add: gpios %d..%d (%s) failed to register ", 64 chip->base, chip->base + chip->ngpio - 1, 65 chip->label ? : "generic"); 66 return status; 67 }
上面代码的核心是:
1 for (id = base; id < base + chip->ngpio; id++) { 2 gpio_desc[id].chip = chip; 3 4 /* REVISIT: most hardware initializes GPIOs as 5 * inputs (often with pullups enabled) so power 6 * usage is minimized. Linux code should set the 7 * gpio direction first thing; but until it does, 8 * we may expose the wrong direction in sysfs. 9 */ 10 gpio_desc[id].flags = !chip->direction_input 11 ? (1 << FLAG_IS_OUT) 12 : 0; 13 }
可以看到,每个gpio pin都对应一个gpio_desc结构,而且同一个bank下的所有pin对应的gpio_desc的chip成员都是一样的,看一下gpio_desc结构:
1 struct gpio_desc { 2 struct gpio_chip *chip; 3 unsigned long flags; 4 /* flag symbols are bit numbers */ 5 #define FLAG_REQUESTED 0 6 #define FLAG_IS_OUT 1 7 #define FLAG_RESERVED 2 8 #define FLAG_EXPORT 3 /* protected by sysfs_lock */ 9 #define FLAG_SYSFS 4 /* exported via /sys/class/gpio/control */ 10 #define FLAG_TRIG_FALL 5 /* trigger on falling edge */ 11 #define FLAG_TRIG_RISE 6 /* trigger on rising edge */ 12 #define FLAG_ACTIVE_LOW 7 /* sysfs value has active low */ 13 14 #define ID_SHIFT 16 /* add new flags before this one */ 15 16 #define GPIO_FLAGS_MASK ((1 << ID_SHIFT) - 1) 17 #define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE)) 18 19 #ifdef CONFIG_DEBUG_FS 20 const char *label; 21 #endif 22 }; 23 static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
gpio_desc[ARCH_NR_GPIOS]是当前文件的全局变量,该文件中的代码是内核管理gpio的最高的抽象层,常用的gpio_request、gpio_free等函数都是在操作gpio_desc这个数组,ARCH_NR_GPIOS在s5pv210相关的头文件中可以找到定义。至此,S5PV210中定义的GPIO资源就注册到内核中了。