2. GPIO代码分析(代码分析)

承接之前的文档:GPIO代码分析(接口和注册过程)

2.3 函数分析

2.3.1 mxc_gpio_init

(arch/arm/plat-mxc/gpio.c)

int mxc_gpio_init(struct mxc_gpio_port *port, int cnt)
{
    /* save for local usage */
    mxc_gpio_ports = port;
    gpio_table_size = cnt;
    printk(KERN_INFO "MXC GPIO hardware\n");
    ...
    /* register gpio chip */
    // mxc_gpio_direction_input 将对应 gpio 设置为输入, mxc_gpio_direction_output 将对应 gpio 设置为输出,并会设置一个初始值
    // 这儿的输入/输出是对 cpu 来说的
    port[i].chip.direction_input = mxc_gpio_direction_input; //设置GPIO的方向,GPIO的方向关系到IO的负载能力和输入阻抗。设置方向之前应该首先设置输出值,然后再设置方向。
    port[i].chip.direction_output = mxc_gpio_direction_output;
    port[i].chip.get = mxc_gpio_get; // 获取/设置 gpio 状态
    port[i].chip.set = mxc_gpio_set;
    port[i].chip.base = i * 32;
    port[i].chip.ngpio = 32;
 
    spin_lock_init(&port[i].lock);
 
    if (!initialed)
    /* its a serious configuration bug when it fails */
    // 添加 gpio chip , 调用的是我们前面用到的一个线索函数, 该函数中有给 gpio_desc 数组赋值
    BUG_ON(gpiochip_add(&port[i].chip) < 0);
    ...
}

2.3.2 gpiochip_add()

(drivers/gpio/gpiolib.c)

在gpio_desc[]中分配空间,并链接chip结构;注册一个gpio_chip对应的gpio_desc到全局数组gpio描述符中。

int gpiochip_add(struct gpio_chip *chip)
    { ...
    if (base < 0) {
    base = gpiochip_find_base(chip->ngpio);// 这个函数在gpiolib.c中,在gpio_desc[]中分配chip->ngpio个空间(从最后往前分配),返回第一个index
    ... }
    ...
    if (status == 0) {// 分配到空间,正常情况下
    for (id = base; id < base + chip->ngpio; id++) {
    gpio_desc[id].chip = chip;
    ...
    } ... } }
EXPORT_SYMBOL_GPL(gpiochip_add);

2.3.3 gpiochip_remove

int gpiochip_remove(struct gpio_chip *chip) 
{ 
    …
    for (id = chip->base; id < chip->base + chip->ngpio; id++) {    //查找对应全局gpio_desc数组项 
        if (test_bit(FLAG_REQUESTED, &gpio_desc[id].flags)) {   //判断是否存在标志含FLAG_REQUESTED的gpio口 
            status = -EBUSY; 
            break; 
        } 
    } 
    if (status == 0) { 
        for (id = chip->base; id < chip->base + chip->ngpio; id++)  //清空对应全局gpio_desc数组项 
            gpio_desc[id].chip = NULL; 
    } 
    spin_unlock_irqrestore(&gpio_lock, flags);  //解自旋锁 
    if (status == 0) 
        gpiochip_unexport(chip);    //gpio_chip移除用户接口 
    return status; 
} 
EXPORT_SYMBOL_GPL(gpiochip_remove);

2.3.4查找获取gpio_chip

struct gpio_chip *gpiochip_find(void *data,int (*match)(struct gpio_chip *chip, void *data)) 
{ 
     struct gpio_chip *chip = NULL; 
     unsigned long flags; 
     int i; 
    spin_lock_irqsave(&gpio_lock, flags);   //上自旋锁 
    for (i = 0; i < ARCH_NR_GPIOS; i++) {    //遍历整个全局gpio_desc数组 
         if (!gpio_desc[i].chip) //若没被某个gpio_chip管理,则跳过继续 
         continue; 
         if (match(gpio_desc[i].chip, data)) {   //调用传递进来的匹配函数 
         chip = gpio_desc[i].chip;   //匹配则获取其gpio_chip 
         break; 
        } 
    } 
     spin_unlock_irqrestore(&gpio_lock, flags);  //解自旋锁 
     return chip;    //返回查找到的gpio_chip指针 
}

2.3.5 gpio_set_debounce

int gpio_set_debounce(unsigned gpio, unsigned debounce)
{
    unsigned long flags;
    struct gpio_chip *chip;
    struct gpio_desc *desc = &gpio_desc[gpio]; //获取gpio_desc项
    int status = -EINVAL;
    spin_lock_irqsave(&gpio_lock, flags); //上自旋锁
    if (!gpio_is_valid(gpio)) //验证gpio号
          goto fail;
    chip = desc->chip; //获取gpio_chip
    if (!chip || !chip->set || !chip->set_debounce) //判断gpio_chip是否存在且是否存在set和set_debounce方法
          goto fail;
    gpio -= chip->base; //计算gpio在该gpio_chip的偏移量
    if (gpio >= chip->ngpio) //超过该gpio_chip的gpio数范围
          goto fail;
    status = gpio_ensure_requested(desc, gpio); //确保gpio是request请求过了的
    if (status < 0)
          goto fail;
    /* now we know the gpio is valid and chip won't vanish */
    spin_unlock_irqrestore(&gpio_lock, flags); //解自旋锁
    might_sleep_if(chip->can_sleep);
    return chip->set_debounce(chip, gpio, debounce); //调用gpio_chip的set_debounce方法
fail:
    spin_unlock_irqrestore(&gpio_lock, flags); //解自旋锁
    if (status)
           pr_debug("%s: gpio-%d status %d\n",__func__, gpio, status);
    return status;
}
EXPORT_SYMBOL_GPL(gpio_set_debounce);

2.3.6 gpio_is_valid

(include/asm-generic/gpio.h)

测试gpio端口是否合法 int gpio_is_valid(int number):

“有效”GPIO编号是非负的,可以传递给像gpiorequest()这样的设置例程。只有一些有效的数字可以被成功地请求和使用。

无效的GPIO数字对于在平台数据和其他表中表示无such-gpio是很有用的。

验证gpio号是否正确。

static inline int gpio_is_valid(int number) 
{ 
    return ((unsigned)number) < ARCH_NR_GPIOS; 
}
 
static inline bool gpio_is_valid(int number)
{
/* 只有非负数是有效的 */
return number >= 0;
}

2.3.7 gpio_request

(drivers/gpio/gpiolib.c)

申请某个gpio端口当然在申请之前需要显示的配置该gpio端口的pinmux。

调用请求:将参数gpio在数组中查找其对应的gpio_desc,在根据gpio_chip就可以找到底层关于request()的实现。所有的GPIO控制器驱动应包括头文件gpio.h(include/asm-generic),包含 struct gpio_chip 的定义。

struct gpio_chip:标记GPIO所属的控制器,里面包含诸多回调函数,用于控制GPIO的行为,各个板卡都有实现自己的gpio_chip控制模块;

struct gpio_desc:用于标记一个GPIO。

2.3.7.1 gpio_desc

drivers/gpio/gpiolib.c

每个元素对应于当前CPU上各个GPIO接口的信息,记录各个GPIO的描述符,即对应struct gpio_desc结构体。struct gpio_desc内的成员gpio_chip又指向了一系列关于GPIO的操作。

struct gpio_desc {
    struct gpio_chip *chip;
    unsigned long flags;
    /* flag symbols are bit numbers */
    #define FLAG_REQUESTED 0
    #define FLAG_IS_OUT 1
    #define FLAG_RESERVED 2
    #define FLAG_EXPORT 3 /*保护sysfs_lock */
    #define FLAG_SYSFS 4 /* exported via /sys/class/gpio/control */
    #define FLAG_TRIG_FALL 5 /* 下降沿触发 */
    #define FLAG_TRIG_RISE 6 /* 上升沿触发 */
    #define FLAG_ACTIVE_LOW 7 /* sysfs value has active low */
    #define ID_SHIFT 16 /* 在这个之前添加新的标志 */
    #define GPIO_FLAGS_MASK ((1 << ID_SHIFT) - 1)
    #define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE)
    #ifdef CONFIG_DEBUG_FS
    const char *label;
    #endif
};
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS]; //ARCH_NR_GPIOS 256

2.3.7.2 gpio_chip

include/asm-generic/gpio.h

struct gpio_chip对象是注册gpio软件架构指定的。

struct gpio_chip {
    const char *label;
    struct device *dev;
    struct module *owner;
    int (*request)(struct gpio_chip *chip,
    unsigned offset); //请求gpio
    void (*free)(struct gpio_chip *chip,
    unsigned offset); //释放gpio
    int (*direction_input)(struct gpio_chip *chip,
    unsigned offset); //配置gpio为输入,返回当前gpio状态
    int (*get)(struct gpio_chip *chip,
    unsigned offset); //获取gpio的状态
    int (*direction_output)(struct gpio_chip *chip,
    unsigned offset, int value); //配置gpio为输出,并设置为value
    int (*set_debounce)(struct gpio_chip *chip,
    unsigned offset, unsigned debounce); //设置消抖动时间,尤其是gpio按键时有用
    void (*set)(struct gpio_chip *chip,
    unsigned offset, int value); //设置gpio为value值
    int (*to_irq)(struct gpio_chip *chip,
    unsigned offset); //把gpio号转换为中断号
    void (*dbg_show)(struct seq_file *s,
    struct gpio_chip *chip);
    int base; // 这个gpio控制器的gpio开始编号
    u16 ngpio; //这个gpio控制器说控制的gpio数
    const char *const *names;
    unsigned can_sleep:1;
    unsigned exported:1;
    #if defined(CONFIG_OF_GPIO)
    /*
    * 如果启用了CONFIG_OF_GPIO,那么在设备树中描述的所有GPIO控制器都可以自动进行翻译,(没有找到设备树的相关内容)
    */
    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
};

2.3.7.3代码部分

int gpio_request(unsigned gpio, const char *label)
{
    struct gpio_desc *desc;
    struct gpio_chip *chip;
    int status = -EINVAL; //#define EINVAL 22 /* Invalid argument */
    unsigned long flags;
    spin_lock_irqsave(&gpio_lock, flags); //gpio_lock是自旋锁,上锁,保存FLAG在flags变量,屏蔽中断
    if (!gpio_is_valid(gpio)) //判断是否有效,也就是参数的取值范围判断
        goto done;
    desc = &gpio_desc[gpio]; //这个是关键gpio_desc为定义的一个全局的数组变量,这个函数的实值也就是, 用gpio_desc里面的一个变量来表示数组中的这个元素已经被申请了,而这个变量就是下面会看到的desc->flags。
    chip = desc->chip; //按理说这个这个全局的gpio_desc如果没有初始化的话,这个chip就为空了,随后就直接返回-EINVAL了。
    if (chip == NULL) //如果不为空继续往下走
        goto done;
    if (!try_module_get(chip->owner))
        goto done;
    /* NOTE: gpio_request() can be called in early boot,
    * before IRQs are enabled, for non-sleeping (SOC) GPIOs.
    */
    if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) { //这里测试并设置flags的第FLAG_REQUESTED位,如果没有被申请就返回该位的原值0
      …
}

2.3.8 gpio方向标定

标记gpio的使用方向包括输入还是输出

       /*成功返回零失败返回负的错误值*/

       int gpio_direction_input(unsigned gpio);

       int gpio_direction_output(unsigned gpio, int value);

返回值为零代表成功,否则返回一个负的错误代码。这个返回值需要检查,因为get/set(获取/设置)函数调用没法返回错误,且有可能是配置错误。通常,你应该在进程上下文中调用这些函数。然而,对于自旋锁安全的GPIO,在板子启动的早期、进程启动前使用他们也是可以的。

对于作为输出的GPIO,为其提供初始输出值,对于避免在系统启动期间出现信号毛刺是很有帮助的。

为了与传统的GPIO接口兼容, 在设置一个GPIO方向时,如果它还未被申请,则隐含了申请那个GPIO的操作。这种兼容性正在从可选的gpiolib框架中移除。

如果这个GPIO编码不存在或特定的GPIO不能用于那种模式,则方向设置可能失败。依赖启动固件来正确地设置方向通常是一个坏主意,因为它可能除了启动Linux,并没有做更多的验证工作。(类似地, 板子的启动代码可能需要将这个复用的引脚设置为GPIO,并正确地配置上拉/下拉电阻。)     

2.3.8.1 gpio_direction_input

int gpio_direction_input(unsigned gpio)
{
    …
    chip = desc->chip; //获取gpio_chip
    if (!chip || !chip->get || !chip->direction_input) //判断gpio_chip是否存在且存在get和directon_input方法
        goto fail;
    gpio -= chip->base; //计算gpio在该gpio_chip的偏移量
    if (gpio >= chip->ngpio) //超过该gpio_chip的gpio数范围
        goto fail;
    status = gpio_ensure_requested(desc, gpio); //确保gpio是request请求过了的
    if (status < 0)
        goto fail;
    /* now we know the gpio is valid and chip won't vanish */
    spin_unlock_irqrestore(&gpio_lock, flags); //解自旋锁
    might_sleep_if(chip->can_sleep);
    if (status) {
        status = chip->request(chip, gpio); //调用chip_gpio的request方法
        if (status < 0) {
        pr_debug("GPIO-%d: chip request fail, %d\n",chip->base + gpio, status);
        goto lose;
        }
    }
    status = chip->direction_input(chip, gpio); //调用chip_gpio的direction_input方法
   …
}

2.3.8.2 gpio_direction_output

int gpio_direction_output(unsigned gpio, int value)
{
    unsigned long flags;
    struct gpio_chip *chip;
    struct gpio_desc *desc = &gpio_desc[gpio]; //获取gpio_desc项
    int status = -EINVAL;
    spin_lock_irqsave(&gpio_lock, flags); //上自旋锁
    if (!gpio_is_valid(gpio))
        goto fail;
    chip = desc->chip; //获取gpio_chip
    if (!chip || !chip->set || !chip->direction_output) //判断gpio_chip是否存在且存在set和direction_output方法
        goto fail;
    gpio -= chip->base; //计算gpio在该gpio_chip的偏移量
    if (gpio >= chip->ngpio) //超过该gpio_chip的gpio数范围
        goto fail;
    status = gpio_ensure_requested(desc, gpio); //确保gpio是request请求过了的
    if (status < 0)
        goto fail;
    spin_unlock_irqrestore(&gpio_lock, flags); //解自旋锁
    might_sleep_if(chip->can_sleep);
    if (status) {
        status = chip->request(chip, gpio); //调用gpio_chip的request方法
       …   }
    status = chip->direction_output(chip, gpio, value); //调用gpio_chip的direction_output方法
…   }
EXPORT_SYMBOL_GPL(gpio_direction_output);

2.3.9引脚值

获得gpio引脚的值和设置gpio引脚的值(对于输出)

static inline int gpio_get_value(unsigned 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 can never have been requested or set as output */
WARN_ON(1);
}

以上的get/set函数不会对早期已经通过gpio_direction_*()报告“无效的GPIO”返回错误。此外,还需要注意的是并不是所有平台都可以从输出引脚中读取数据的,那些引脚也不总是返回零。且对那些无法安全访问(可能会休眠)的GPIO(见下文)使用这些函数是错误的。

在GPIO编号(还有输出、值)为常数的情况下,鼓励通过平台特定的实现来优化这两个函数来访问GPIO值。这种情况(读写一个硬件寄存器)下只需要几条指令是很正常的,且无须自旋锁。这种优化函数比起那些在子程序上花费许多指令的函数可以使得模拟接口(译者注:例如GPIO模拟I2C、1-wire或SPI)的应用(在空间和时间上都)更具效率。    

2.3.10 gpio_to_irq

gpio当作中断口使用:

    返回的值即中断编号可以传给request_irq()和free_irq();

    内核通过调用该函数将gpio端口转换为中断,在用户空间也有类似方法;

GPIO编号是无符号整数,IRQ编号也是。这些构成了两个逻辑上不同的命名空间(GPIO 0不一定使用IRQ 0)。你可以通过以下函数在它们之间实现映射:

/* 映射GPIO编号到IRQ编号 */

 int gpio_to_irq(unsigned gpio); 

/* 映射IRQ编号到GPIO编号 (尽量避免使用) */

int irq_to_gpio(unsigned irq);

它们的返回值为对应命名空间的相关编号,或是负的错误代码(如果无法映射)。(例如,某些GPIO无法做为IRQ使用。)以下的编号错误是未经检测的:使用一个未通过gpio_direction_input()配置为输入的GPIO编号,或者使用一个并非来源于gpio_to_irq()的IRQ编号。

这两个映射函数可能会在信号编号的加减计算过程上花些时间。它们不可休眠。

gpio_to_irq()返回的非错误值可以传递给request_irq()或者free_irq()。它们通常通过板级特定的初始化代码存放到平台设备的IRQ资源中。注意:IRQ触发选项是IRQ接口的一部分,比如IRQF_TRIGGER_FALLING,系统唤醒能力也是如此。

irq_to_gpio()返回的非错误值大多数通常可以被gpio_get_value()所使用,比如在IRQ是沿触发时初始化或更新驱动状态。注意某些平台不支持反映射,所以你应该尽量避免使用它。

2.3.11 导出gpio端口到用户空间

内核代码可以明确地管理那些已通过gpio_request()申请的GPIO的导出:

 /* 导出GPIO到用户空间 */

int gpio_export(unsigned gpio, bool direction_may_change); 

/* gpio_export()的逆操作 */ void gpio_unexport(); 

/* 创建一个sysfs连接到已导出的GPIO节点 */

int gpio_export_link(struct device *dev, const char *name, unsigned gpio) 

/* 改变sysfs中的一个GPIO节点的极性 */

int gpio_sysfs_set_active_low(unsigned gpio, int value);

在一个内核驱动申请一个GPIO之后, 它可以通过gpio_export()使其在sysfs接口中可见。该驱动可以控制信号方向是否可修改。这有助于防止用户空间代码无意间破坏重要的系统状态。

这个明确的导出有助于(通过使某些实验更容易来)调试,也可以提供一个始终存在的接口,与文档配合作为一个板级支持包的一部分。

在GPIO被导出之后,gpio_export_link()允许在sysfs文件系统的任何地方创建一个到这个GPIO sysfs节点的符号链接。这样驱动就可以通过一个描述性的名字,在sysfs中他们所拥有的设备下提供一个(到这个GPIO sysfs节点的)接口。

驱动可以使用 gpio_sysfs_set_active_low() 来在用户空间隐藏电路板之间 GPIO 线的极性差异。这个仅对sysfs接口起作用。极性的改变可以在gpio_export()前后进行,且之前使能的轮询操作(poll(2))支持(上升或下降沿)将会被重新配置来遵循这个设置。

2.3.12 sysfs的初始化

static int __init gpiolib_sysfs_init(void)
{     …
    status = class_register(&gpio_class); //注册gpio_class
    if (status < 0)
        return status;
    spin_lock_irqsave(&gpio_lock, flags);
    for (gpio = 0; gpio < ARCH_NR_GPIOS; gpio++) { //遍历全局gpio_desc数组
        struct gpio_chip *chip;
        chip = gpio_desc[gpio].chip; //获取数组项的gpio_chip
        if (!chip || chip->exported) //gpio_chip为空或已经exported了
            continue;
        spin_unlock_irqrestore(&gpio_lock, flags); //上自旋锁
        status = gpiochip_export(chip); //exported该项
        spin_lock_irqsave(&gpio_lock, flags); //解自旋锁
}
postcore_initcall(gpiolib_sysfs_init);

next:寄存器配置举例

猜你喜欢

转载自blog.csdn.net/qq_38022972/article/details/82733620