• Linux-3.0.8中基于S5PV210的GPIO模块代码追踪和分析


      编写按键驱动时,想知道内核是如何管理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资源就注册到内核中了。

  • 相关阅读:
    C语言I博客作业06
    C语言I博客作业07
    C语言I博客作业03
    oracle 创建用户并指定表空间
    Oracle 给用户赋予dblink权限,创建dblink
    IDEA 2020.2 破解、激活
    nginx 里的常用变量
    nginx 跨域问题解决
    elasticsearch (一)
    kubenetes 安装部署
  • 原文地址:https://www.cnblogs.com/tech-lqh/p/9201718.html
Copyright © 2020-2023  润新知