在很多的SOC芯片里,GPIO硬件接口都是和其它硬件接口共用Pin脚,然后通过特定的寄存器去设置Pin使用时的类型。比如目前手上的项目使用的是Marvell的SOC芯片,该芯片上有50个多功能Pin脚(MPP:Multi-Purpose Pins),每个Pin都可以配置成不同的功能。SOC提供了7个32位的寄存器用来配置MPP Pin的类型,寄存器的每4位对应一个Pin,一个寄存器控制可以8个Pin.当MPP Pin被设置成GPIO Pin后,就可以按照GPIO Pin的方式来使用该Pin脚,比如input/output和high/low的设置。
硬件抽象层负责把GPIO控制器注册到GPIO通用层,通过调用gpiochip_add()把表示GPIO控制器的gpio_chip注册到gpiolib中。所以在注册gpio_chip之前,要对该结构体进行初始化。在mv_gpio.c中可以看到,label用来标识GPIO控制器,direction_input/direction_output用来设置GPIO Pin的方向,get/set用来读写GPIO Pin的值,而request用来获取一个空闲的GPIO Pin,base和ngpio分别标识该控制器的起始Pin和Pin的总数量。
static struct gpio_chip mv_gpiochip = {
.label = "mv_gpio",
.direction_input= mv_gpio_direction_input,
.get = mv_gpio_get_value,
.direction_output= mv_gpio_direction_output,
.set = mv_gpio_set_value,
.request = mv_gpio_request,
.base = 0,
.ngpio = MV_GPP_MAX_PINS,
.can_sleep = 0,
};
mv_gpio_direction_input用来设置GPIO Pin为input模式,函数的调用路径为mv_gpio_direction_input()->_set_direction()->mvGppTypeSet()->gppRegSet()。在_set_directon里面,会先对要设置的Pin Num做一些处理,因为总共有50个GPIO Pin可用,所以需要2组32位的寄存器来控制,所以pin>>5就是算出该Pin是属于第一组,还是属于第二组。mask的值就是算出该Pin属于寄存器的第几个bit。然后通过mvGppTypeSet函数里面的gppRegSet把对应的值写到特定的寄存器中。
static inline void __set_direction(unsigned pin, int input)
{
u32 grp = pin >> 5;
u32 mask = (1 << (pin & 0x1F));
if (input)
mvGppTypeSet(grp, mask, MV_GPP_IN & mask);
else
mvGppTypeSet(grp, mask, MV_GPP_OUT & mask);
}
mv_gpio_direction_output用来设置GPIO Pin为output模式,因为GPIO Pin在output模式下可以设置该Pin脚的值,所以该函数在设置Pin脚方向的同时可以设置输出的值。函数调用路径为mv_gpio_direction_output->_set_level()->mvGppValueSet()->_set_direction()->mvGppTypeSet()。从函数执行的路径可以看出,是先设置GPIO的值,然后再设置GPIO的方向。这个顺序比较重要,因为在不知道默认值得情况下,先设置GPIO为输出模式,默认的值可能会引起异常,在之前的项目上就碰到过这种问题。
static void __set_level(unsigned pin, int high)
{
u32 grp = pin >> 5;
u32 mask = (1 << (pin & 0x1F));
if (high)
mvGppValueSet (grp, mask, mask);
else
mvGppValueSet (grp, mask, 0);
}
mv_gpio_set_value用来设置GPIO输出的值,所以该函数直接调用_set_level()把值写到GPIO的输出寄存器里面。而对于mv_gpio_get_value,读取的值分两种情况,即输出模式的值和输入模式的值。对于输出模式,直接读取对应寄存器的值就可以了。而对于输入模式,由输入寄存器和一个极性寄存器共同表示该Pin脚的值,当极性寄存器对应的Pin脚位为1时,该Pin实际的值跟输入寄存器里面的值相反,当极性寄存器对应的Pin脚位为0时,该Pin实际的值与输入寄存器里面的值一致。
static int mv_gpio_get_value(struct gpio_chip *chip, unsigned pin)
{
u32 val;
u32 grp = pin >> 5;
u32 mask = (1 << (pin & 0x1F));
if (MV_REG_READ(GPP_DATA_OUT_EN_REG(grp)) & mask)
val = mvGppValueGet(grp, mask) ^ mvGppPolarityGet(grp, mask);
else
val = MV_REG_READ(GPP_DATA_OUT_REG(grp));
return (val >> (pin & 31)) & 1;
}
mv_gpio_request用来申请一个空闲的GPIO Pin,在该项目的平台上直接返回一个0值,具体的申请功能由gpio通用层实现。