编写按键驱动时,想知道内核是如何管理GPIO的,所以开始追踪代码,中间走了一些弯路,现记录于此。

  追踪代码之前,我猜测:第一,这部分代码应该在系统set up阶段执行;第二,GPIO的代码应该在machine或者platform或者vendor相关的目录下。事实证明,第一点是正确的,第二点基本是错误的,因为内核依靠对GPIO的抽象来管理之,这层抽象层给具体的machine留出了一些它们需要是实现的接口,这与其他的设备驱动框架在使用上是很类似的,当然,GPIO也是一种设备啊... ...所以,管理GPIO的多数代码位于drivers/gpio/目录下。

  好了,开始走读代码,那么对于S5PV210这块SoC,GPIO子系统的入口函数在哪里呢?在drivers/gpio/gpio-s5pv210.c中,入口函数的实现如下图所示:

 static __init int s5pv210_gpiolib_init(void)
{
struct s3c_gpio_chip *chip = s5pv210_gpio_4bit;
int nr_chips = ARRAY_SIZE(s5pv210_gpio_4bit);
int gpioint_group = ;
int i = ; for (i = ; i < nr_chips; i++, chip++) {
if (chip->config == NULL) {
chip->config = &gpio_cfg;
chip->group = gpioint_group++;
}
if (chip->base == NULL)
chip->base = S5PV210_BANK_BASE(i);
} samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);
s5p_register_gpioint_bank(IRQ_GPIOINT, , S5P_GPIOINT_GROUP_MAXNR); return ;
}
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

  

 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;
spinlock_t lock;
#ifdef CONFIG_PM
u32 pm_save[];
#endif
};

  从面向对象的角度来看,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,具体的结构是:

 struct s3c_gpio_cfg {
unsigned int cfg_eint; s3c_gpio_pull_t (*get_pull)(struct s3c_gpio_chip *chip, unsigned offs);
int (*set_pull)(struct s3c_gpio_chip *chip, unsigned offs,
s3c_gpio_pull_t pull); unsigned (*get_config)(struct s3c_gpio_chip *chip, unsigned offs);
int (*set_config)(struct s3c_gpio_chip *chip, unsigned offs,
unsigned config);
};

  可见config成员用于控制和查看当前bank的某个pin的上下拉电阻的状态,以及设置和查看某引脚上的复用功能,这个成员作为子类struct s3c_gpio_cfg的新成员,说明以上这两种功能在不同的machine以及GPIO IP上的差别是不能忽略的。

  struct s3c_gpio_cfg中很重要的一个成员就是它的父类struct gpio_chip,它内容很多,不过看成员名字就基本能明白含义:

 struct gpio_chip {
const char *label;
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;
u16 ngpio;
const char *const *names;
unsigned can_sleep:;
unsigned exported:; #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
};

  这个结构体中的request free等函数指针与gpio的request、free、direction_input、direction_output等函数的实现有关系。

  接着看一下数组s5pv210_gpio_4bit,首先,这个名字中包含4bit,意思是每个bank的gpio控制寄存器中,每4bit控制一个gpio pin。这个数组很长,我们只取出个别元素看一下:

 static struct s3c_gpio_chip s5pv210_gpio_4bit[] = {
{
.chip = {
.base = S5PV210_GPA0(),
.ngpio = S5PV210_GPIO_A0_NR,
.label = "GPA0",
},
}, {
.chip = {
.base = S5PV210_GPA1(),
.ngpio = S5PV210_GPIO_A1_NR,
.label = "GPA1",
},
}, {
.chip = {
.base = S5PV210_GPB(),
.ngpio = S5PV210_GPIO_B_NR,
.label = "GPB",
},
},
... ... ... ... ... ... ... ...
{
.base = (S5P_VA_GPIO + 0xC40),
.config = &gpio_cfg_noint,
.irq_base = IRQ_EINT(),
.chip = {
.base = S5PV210_GPH2(),
.ngpio = S5PV210_GPIO_H2_NR,
.label = "GPH2",
.to_irq = samsung_gpiolib_to_irq,
},
}, {
.base = (S5P_VA_GPIO + 0xC60),
.config = &gpio_cfg_noint,
.irq_base = IRQ_EINT(),
.chip = {
.base = S5PV210_GPH3(),
.ngpio = S5PV210_GPIO_H3_NR,
.label = "GPH3",
.to_irq = samsung_gpiolib_to_irq,
},
},
};

  这个数组描述了一些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,这个变量就在当前文件中,定义如下:

 static struct s3c_gpio_cfg gpio_cfg = {
.set_config = s3c_gpio_setcfg_s3c64xx_4bit,
.set_pull = s3c_gpio_setpull_updown,
.get_pull = s3c_gpio_getpull_updown,
};

  下面就看看这三个函数吧,看名字应该就能知道其功能了。

#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 & ) * ;
u32 con; if (off < && chip->chip.ngpio > )
reg -= ; 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 ;
}

  代码逻辑很简单,获取当前bank的控制寄存器的虚拟地址,根据offset计算要控制的引脚在控制寄存器中的起始位,然后将要设置的cfg值写入到控制寄存器,完成工作。不过这里有个宏CONFIG_S3C_GPIO_CFG_S3C64XX,查看相关的Kconfig就可知道,一旦选中s5p相关的平台,那么S3C_GPIO_CFG_S3C64XX这个宏一定会定义。

  刚刚写了很多,结果360一优化浏览器直接退出没保存,悲剧。

   上面的数组中很多元素的base都没有初始化,就需要使用宏定义S5PV210_BANK_BASE计算:

/drivers/gpio/gpio-s5pv210.c

 #define S5PV210_BANK_BASE(bank_nr)    (S5P_VA_GPIO + ((bank_nr) * 0x20))

/arch/arm/plat-s5p/include/plat/map-s5p.h

 #define S5P_VA_GPIO        S3C_ADDR(0x02200000)

/arch/arm/plat-s5p/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

  经过计算,S5P_VA_GPIO在3GB+898M处。

  由于每个gpio bank的相关寄存器都占用0x20的内存空间,所以可以同等差数列通项公式来计算各个bank的起始地址。

  第三部分,samsung_gpiolib_add_4bit_chips的分析

/drivers/gpio/gpio-plat-samsung.c

 void __init samsung_gpiolib_add_4bit_chips(struct s3c_gpio_chip *chip,
int nr_chips)
{
for (; nr_chips > ; nr_chips--, chip++) {
samsung_gpiolib_add_4bit(chip);
s3c_gpiolib_add(chip);
}
}
 void __init samsung_gpiolib_add_4bit(struct s3c_gpio_chip *chip)
{
chip->chip.direction_input = samsung_gpiolib_4bit_input;
chip->chip.direction_output = samsung_gpiolib_4bit_output;
chip->pm = __gpio_pm(&s3c_gpio_pm_4bit);
}

  samsung_gpiolib_4bit_input、samsung_gpiolib_output是设置s3c_gpio_chip->chip的函数指针成员,功能如函数名字所示,因为三星的SoC的GPIO IP的寄存器组织形式很相似,所以就直接以samsung_gpiolib开头命名函数了。以上两个函数都是在操作寄存器,代码逻辑很简单。

  __gpio_pm(&s3c_gpio_pm_4bit)是跟功耗管理相关的函数指针,__gpio_pm是一个宏:

 #ifdef CONFIG_PM
extern struct s3c_gpio_pm s3c_gpio_pm_1bit;
extern struct s3c_gpio_pm s3c_gpio_pm_2bit;
extern struct s3c_gpio_pm s3c_gpio_pm_4bit;
#define __gpio_pm(x) x
#else
#define s3c_gpio_pm_1bit NULL
#define s3c_gpio_pm_2bit NULL
#define s3c_gpio_pm_4bit NULL
#define __gpio_pm(x) NULL #endif /* CONFIG_PM */
 struct s3c_gpio_pm s3c_gpio_pm_4bit = {
.save = s3c_gpio_pm_4bit_save,
.resume = s3c_gpio_pm_4bit_resume,
};

  上述两个函数就是功耗管理的函数,save函数就是在gpio模块进入休眠之前,将各个寄存器的值存入到pm_save数组中,退出休眠状态时,再将数组中的内容回写到各寄存器,实现的时候也只是读写寄存器,不多说。

  接下来分析s3c_gpiolib_add()函数

/arch/arm/plat-samsung/gpio.c

 __init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)
{
struct gpio_chip *gc = &chip->chip;
int ret; BUG_ON(!chip->base);
BUG_ON(!gc->label);
BUG_ON(!gc->ngpio); spin_lock_init(&chip->lock); if (!gc->direction_input)
gc->direction_input = s3c_gpiolib_input;
if (!gc->direction_output)
gc->direction_output = s3c_gpiolib_output;
if (!gc->set)
gc->set = s3c_gpiolib_set;
if (!gc->get)
gc->get = s3c_gpiolib_get; #ifdef CONFIG_PM
if (chip->pm != NULL) {
if (!chip->pm->save || !chip->pm->resume)
printk(KERN_ERR "gpio: %s has missing PM functions\n",
gc->label);
} else
printk(KERN_ERR "gpio: %s has no PM function\n", gc->label);
#endif /* gpiochip_add() prints own failure message on error. */
ret = gpiochip_add(gc);
if (ret >= )
s3c_gpiolib_track(chip);
}

  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

 int gpiochip_add(struct gpio_chip *chip)
{
unsigned long flags;
int status = ;
unsigned id;
int base = chip->base; if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - ))
&& base >= ) {
status = -EINVAL;
goto fail;
} spin_lock_irqsave(&gpio_lock, flags); if (base < ) {
base = gpiochip_find_base(chip->ngpio);
if (base < ) {
status = base;
goto unlock;
}
chip->base = base;
} /* these GPIO numbers must not be managed by another gpio_chip */
for (id = base; id < base + chip->ngpio; id++) {
if (gpio_desc[id].chip != NULL) {
status = -EBUSY;
break;
}
}
if (status == ) {
for (id = base; id < base + chip->ngpio; id++) {
gpio_desc[id].chip = chip; /* REVISIT: most hardware initializes GPIOs as
* inputs (often with pullups enabled) so power
* usage is minimized. Linux code should set the
* gpio direction first thing; but until it does,
* we may expose the wrong direction in sysfs.
*/
gpio_desc[id].flags = !chip->direction_input
? ( << FLAG_IS_OUT)
: ;
}
} of_gpiochip_add(chip); unlock:
spin_unlock_irqrestore(&gpio_lock, flags); if (status)
goto fail; status = gpiochip_export(chip);
if (status)
goto fail; return ;
fail:
/* failures here can mean systems won't boot... */
pr_err("gpiochip_add: gpios %d..%d (%s) failed to register\n",
chip->base, chip->base + chip->ngpio - ,
chip->label ? : "generic");
return status;
}

  上面代码的核心是:

         for (id = base; id < base + chip->ngpio; id++) {
gpio_desc[id].chip = chip; /* REVISIT: most hardware initializes GPIOs as
* inputs (often with pullups enabled) so power
* usage is minimized. Linux code should set the
* gpio direction first thing; but until it does,
* we may expose the wrong direction in sysfs.
*/
gpio_desc[id].flags = !chip->direction_input
? ( << FLAG_IS_OUT)
: ;
}

  可以看到,每个gpio pin都对应一个gpio_desc结构,而且同一个bank下的所有pin对应的gpio_desc的chip成员都是一样的,看一下gpio_desc结构:

 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 /* protected by sysfs_lock */
#define FLAG_SYSFS 4 /* exported via /sys/class/gpio/control */
#define FLAG_TRIG_FALL 5 /* trigger on falling edge */
#define FLAG_TRIG_RISE 6 /* trigger on rising edge */
#define FLAG_ACTIVE_LOW 7 /* sysfs value has active low */ #define ID_SHIFT 16 /* add new flags before this one */ #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];

  gpio_desc[ARCH_NR_GPIOS]是当前文件的全局变量,该文件中的代码是内核管理gpio的最高的抽象层,常用的gpio_request、gpio_free等函数都是在操作gpio_desc这个数组,ARCH_NR_GPIOS在s5pv210相关的头文件中可以找到定义。至此,S5PV210中定义的GPIO资源就注册到内核中了。

Linux-3.0.8中基于S5PV210的GPIO模块代码追踪和分析的更多相关文章

  1. Linux-3.0.8中基于S5PV210的IRQ模块代码追踪和分析

    init/main.c: asmlinkage void start_kernel(void) { ...... early_irq_init(); init_IRQ(); ...... } earl ...

  2. Linux移植随笔:对tslib库的ts_test测试程序代码的一点分析【转】

    转自:http://www.latelee.org/embedded-linux/porting-linux-tstest-code.html 本文是作者对tslib库的ts_test.c文件进行分析 ...

  3. 开源低代码平台开发实践二:从 0 构建一个基于 ER 图的低代码后端

    前后端分离了! 第一次知道这个事情的时候,内心是困惑的. 前端都出去搞 SPA,SEO 们同意吗? 后来,SSR 来了. 他说:"SEO 们同意了!" 任何人的反对,都没用了,时代 ...

  4. (转)S5pv210 HDMI 接口在 Linux 3.0.8 驱动框架解析 (By liukun321 咕唧咕唧)

    作者:liukun321 咕唧咕唧 日期:2014.1.18 转载请标明作者.出处:http://blog.csdn.net/liukun321/article/details/18452663 本文 ...

  5. S5pv210 HDMI 接口在 Linux 3.0.8 驱动框架解析

    作者:liukun321 咕唧咕唧 日期:2014.1.18 转载请标明作者.出处:http://blog.csdn.net/liukun321/article/details/18452663 本文 ...

  6. 基于s5pv210嵌入式linux系统sqlite3数据库移植

    基于s5pv210嵌入式linux系统sqlite3数据库移植 1.下载源码 http://www.sqlite.org/download.html 最新源码为3080100 2.解压 tar xvf ...

  7. 《Linux设备驱动开发具体解释(第3版)》(即《Linux设备驱动开发具体解释:基于最新的Linux 4.0内核》)网购链接

    <Linux设备驱动开发具体解释:基于最新的Linux 4.0内核> china-pub   spm=a1z10.3-b.w4011-10017777404.30.kvceXB&i ...

  8. 如何在 Docker 容器中运行 Kali Linux 2.0

    https://linux.cn/article-6103-1.html Kali Linux 是一个对于安全测试人员和白帽的一个知名操作系统.它带有大量安全相关的程序,这让它很容易用于渗透测试.最近 ...

  9. MySQL 并行复制演进及 MySQL 8.0 中基于 WriteSet 的优化

    MySQL 8.0 可以说是MySQL发展历史上里程碑式的一个版本,包括了多个重大更新,目前 Generally Available 版本已经已经发布,正式版本即将发布,在此将介绍8.0版本中引入的一 ...

随机推荐

  1. vue_element_vue 引入路径@

    build/webpack.base.conf.js resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('s ...

  2. com.android.support:design

    Error:Could not find com.android.support:design:27.3.1.Required by: project :app Please install the ...

  3. HTTP协议中request和response常用方法

    一.request的常用方法:1.获取请求的方式 getMethod()2.目录的路径 getContextPath()3.获取servlet路径 getServletString()4.获得get请 ...

  4. jmeter入门案例(二)

    jmeter入门简介(一)下载及元件介绍https://www.cnblogs.com/wish5714/p/9714930.html jmeter典型的http请求示例 业务场景 银行卡收单交易,模 ...

  5. day50 盒子显隐2D形变

    复习 1.浮动布局 解决block盒子同行显示 => 不完全脱离文档流 => 不再撑开父级高度 脱离文档流: 不在页面中占位(显示层次高于文档流) 不完全: 可以通过清浮动操作, 让子级重 ...

  6. Python学习—数据库篇之SQL补充

    一.SQL注入问题 在使用pymysql进行信息查询时,推荐使用传参的方式,禁止使用字符串拼接方式,因为字符串拼接往往会带来sql注入的问题 # -*- coding:utf-8 -*- # auth ...

  7. LeetCode Smallest Range

    数据范围是3500,3500也就是说n的平方是可以接受的.这里告诉你就是有序的,也就是在提醒你可能会是一个类似于二分的算法,所以的话其实基于这两个认识的话我们就可以利用一个枚举叫二分的算法来解决这道题 ...

  8. 异步FIFO的verilog实现与简单验证(调试成功)

    最近在写一个异步FIFO的时候,从网上找了许多资料,文章都写的相当不错,只是附在后面的代码都多多少少有些小错误. 于是自己写了一个调试成功的代码,放上来供大家参考. 非原创 原理参考下面: 原文 ht ...

  9. 杭电oj 4004---The Frog Games java解法

    import java.util.Arrays; import java.util.Scanner; //杭电oj 4004 //解题思路:利用二分法查找,即先选取跳跃距离的区间,从最大到最小, // ...

  10. easyUI添加修改tab页(toolbar)

    代码: <div id="editdialos" class="easyui-dialog" title="虚机配置修改" data- ...