软件框架
一、前言
作为一个工作多年的系统工程师,免不了做两件事情:培训新员工和给新员工分配任务。对于那些刚刚从学校出来的学生,一般在开始的时候总是分配一些非常简单的任务,例如GPIO driver、LED driver。往往CPU datasheet的关于GPIO或者IO ports的章节都是比较简单的,非常适合刚入行的工程师。虽然GPIO子系统相关的硬件比较简单,没有复杂的协议,不过,对于软件抽象而言,其分层次的软件思想是每个嵌入式软件工程师需要掌握的内容。
我更倾向使用GPIO系统这个名字来代替GPIO driver这个名字,GPIO driver仅仅包含了pin signal状态控制和读取的内容,而GPIO系统包括了pin multiplexing、pin configuration、GPIO control、GPIO interrupt control等内容。本文主要是以3.14内核作为例子,讲述linux kernel中GPIO系统的软件框架。
二、GPIO相关硬件有哪些差异
嵌入式工程师总是要处理各种各样的target board,每个target board上的GPIO总是存在不同,例如:
1、和CPU的连接方式不同
对于ARM的嵌入式硬件平台,SOC本身可以提供大量的IO port,SOC上的GPIO controller是通过SOC的总线(AMBA)连接到CPU的。对于嵌入式系统而言,除了SOC的IO port,一些外设芯片也可能会提供IO port,例如:
(1)有些key controller芯片、codec或者PMU的芯片会提供I/O port
(2)有些专用的IO expander芯片可以扩展16个或者32个GPIO
从硬件角度看,这些IO和SOC提供的那些IO完全不同,CPU和IO expander是通过I2C(也有可能是SPI等其他类型的bus)连接的,在这种情况下,访问这些SOC之外的GPIO需要I2C的操作,而控制SOC上的GPIO只需要写寄存器的操作。不要小看这个不同,写一个SOC memory map的寄存器非常快,但是通过I2C来操作IO就不是那么快了,甚至,如果总线繁忙有可能阻塞当前进程,这种情况下,内核同步机制必须有所区别(如果操作GPIO可能导致sleep,那么同步机制不能采用spinlock)。
2、访问方式不同
SOC片内的GPIO controller和SOC片外的IO expander的访问当然不一样,不过,即便都是SOC片内的GPIO controller,不同的ARM芯片,其访问方式也不完全相同,例如:有些SOC的GPIO controller会提供一个寄存器来控制输出电平。向寄存器写1就是set high,向寄存器写0就是set low。但是有些SOC的GPIO controller会提供两个寄存器来控制输出电平。向其中一个寄存器写一就是set high,向另外一个寄存器写一就是set low。
3、配置方式不同
即便是使用了同样的硬件(例如都使用同样的某款SOC),不同硬件系统上GPIO的配置不同。在一个系统上配置为输入,在另外的系统上可能配置为输出。
4、GPIO特性不同。这些特性包括:
(1)是否能触发中断。对一个SOC而言,并非所有的IO port都支持中断功能,可能某些处理器只有一两组GPIO有中断功能。
(2)如果能够触发中断,那么该GPIO是否能够将CPU从sleep状态唤醒
(3)有些有软件可控的上拉或者下拉电阻的特性,有的GPIO不支持这种特性。在设定为输入的时候,有的GPIO可以设定debouce的算法,有的则不可以。
5、多功能复用
有的GPIO就是单纯的作为一个GPIO出现,有些GPIO有其他的复用的功能。例如IO expander上的GPIO只能是GPIO,但是SOC上的某个GPIO除了做普通的IO pin脚,还可以是SPI上clock信号线。
三、硬件功能分类
ARM based SOC的datasheet中总有一个章节叫做GPIO controller(或者I/O ports)的章节来描述如何配置、使用SOC的引脚。虽然GPIO controller的硬件描述中充满了大量的寄存器的描述,但是这些寄存器的功能大概分成下面三个类别:
1、有些硬件逻辑是和IO port本身的功能设定相关的,我们称这个HW block为pin controller。软件通过设定pin controller这个硬件单元的寄存器可以实现:
(1)引脚功能配置。例如该I/O pin是一个普通的GPIO还是一些特殊功能引脚(例如memeory bank上CS信号)。
(2)引脚特性配置。例如pull-up/down电阻的设定,drive-strength的设定等。
2、如果一组GPIO被配置成SPI,那么这些pin脚被连接到了SPI controller,如果配置成GPIO,那么控制这些引脚的就是GPIO controller。通过访问GPIO controller的寄存器,软件可以:
(1)配置GPIO的方向
(2)如果是输出,可以配置high level或者low level
(3)如果是输入,可以获取GPIO引脚上的电平状态
3、如果一组gpio有中断控制器的功能,虽然控制寄存器在datasheet中的I/O ports章节描述,但是实际上这些GPIO已经被组织成了一个interrupt controller的硬件block,它更像是一个GPIO type的中断控制器,通过访问GPIO type的中断控制器的寄存器,软件可以:
(1)中断的enable和disable(mask和unmask)
(2)触发方式
(3)中断状态清除
四、如何通过软件抽象来掩盖硬件差异
传统的GPIO driver是负责上面三大类的控制,而新的linux kernel中的GPIO subsystem则用三个软件模块来对应上面三类硬件功能:
(1)pin control subsystem。驱动pin controller硬件的软件子系统。
(2)GPIO subsystem。驱动GPIO controller硬件的软件子系统。
(3)GPIO interrupt chip driver。这个模块是作为一个interrupt subsystem中的一个底层硬件驱动模块存在的。本文主要描述前两个软件模块,具体GPIO interrupt chip driver以及interrupt subsystem请参考本站其他相关文档。
1、pin control subsystem block diagram
下图描述了pin control subsystem的模块图:
底层的pin controller driver是硬件相关的模组,初始化的时候会向pin control core模块注册pin control设备(通过pinctrl_register这个bootom level interface)。pin control core模块是一个硬件无关模块,它抽象了所有pin controller的硬件特性,仅仅从用户(各个driver就是pin control subsystem的用户)角度给出了top level的接口函数,这样,各个driver不需要关注pin controller的底层硬件相关的内容。
2、GPIO subsystem block diagram
下图描述了GPIO subsystem的模块图:
基本上这个软件框架图和pin control subsystem是一样的,其软件抽象的思想也是一样的,当然其内部具体的实现不一样,我们会在后续的文章中描述。
pin control subsystem
一、前言
在linux2.6内核上工作的嵌入式软件工程师在pin control上都会遇到这样的状况:
(1)启动一个新的项目后,需要根据硬件平台的设定进行pin control相关的编码。例如:在bootloader中建立一个大的table,描述各个引脚的配置和缺省状态。此外,由于SOC的引脚是可以复用的,因此在各个具体的driver中,也可能会对引脚进行的配置。这些工作都是比较繁琐的工作,需要极大的耐心和细致度。
(2)发现某个driver不能正常工作,辛辛苦苦debug后发现仅仅是因为其他的driver在初始化的过程中修改了引脚的配置,导致自己的driver无法正常工作
(3)即便是主CPU是一样的项目,但是由于外设的不同,我们也不能使用一个kernel image,而是必须要修改代码(这些代码主要是board-specific startup code)
(4)代码不是非常的整洁,cut-and-pasted代码满天飞,linux中的冗余代码太多
作为一个嵌入式软件工程师,项目做多了,接触的CPU就多了,摔的跤就多了,之后自然会去思考,我们是否可以解决上面的问题呢?此外,对于基于ARM core那些SOC,虽然表面上看起来各个SOC各不相同,但是在pin control上还有很多相同的内容的,是否可以把它抽取出来,进行进一步的抽象呢?新版本中的内核(本文以3.14版本内核为例)提出了pin control subsystem来解决这些问题。
二、pin control subsystem的文件列表
1、源文件列表
我们整理linux/drivers/pinctrl目录下pin control subsystem的源文件列表如下:
文件名 | 描述 |
core.c core.h | pin control subsystem的core driver |
pinctrl-utils.c pinctrl-utils.h | pin control subsystem的一些utility接口函数 |
pinmux.c pinmux.h | pin control subsystem的core driver(pin muxing部分的代码,也称为pinmux driver) |
pinconf.c pinconf.h | pin control subsystem的core driver(pin config部分的代码,也称为pin config driver) |
devicetree.c devicetree.h | pin control subsystem的device tree代码 |
pinctrl-xxxx.c | 各种pin controller的low level driver。 |
在pin controller driver文档中 ,我们以2416的pin controller为例,描述了一个具体的low level的driver,这个driver涉及的文件包括pinctrl-samsung.c,pinctrl-samsung.h和pinctrl-s3c24xx.c。
2、和其他内核模块接口头文件
很多内核的其他模块需要用到pin control subsystem的服务,这些头文件就定义了pin control subsystem的外部接口以及相关的数据结构。我们整理linux/include/linux/pinctrl目录下pin control subsystem的外部接口头文件列表如下:
文件名 | 描述 |
consumer.h | 其他的driver要使用pin control subsystem的下列接口: a、设置引脚复用功能 b、配置引脚的电气特性 这时候需要include这个头文件 |
devinfo.h | 这是for linux内核的驱动模型模块(driver model)使用的接口。struct device中包括了一个struct dev_pin_info *pins的成员,这个成员描述了该设备的引脚的初始状态信息,在probe之前,driver model中的core driver在调用driver的probe函数之前会先设定pin state |
machine.h | 和machine模块的接口。 |
3、Low level pin controller driver接口
我们整理linux/include/linux/pinctrl目录下pin control subsystem提供给底层specific pin controller driver的头文件列表如下:
文件名 | 描述 |
pinconf-generic.h | 这个接口主要是提供给各种pin controller driver使用的,不是外部接口。 |
pinconf.h | pin configuration 接口 |
pinctrl-state.h | pin control state状态定义 |
pinmux.h | pin mux function接口 |
三、pin control subsystem的软件框架图
1、功能和接口概述
一般而言,学习复杂的软件组件或者软件模块是一个痛苦的过程。我们可以把我们要学习的那个软件block看成一个黑盒子,不论里面有多么复杂,第一步总是先了解其功能和外部接口特性。如果你愿意,你可以不去看其内部实现,先自己思考其内部逻辑,并形成若干问题,然后带着这些问题去看代码,往往事半功倍。
(1)、功能规格。pin control subsystem的主要功能包括:
(A)管理系统中所有可以控制的pin。在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin。
(B)管理这些pin的复用(Multiplexing)。对于SOC而言,其引脚除了配置成普通GPIO之外,若干个引脚还可以组成一个pin group,形成特定的功能。例如pin number是{ 0, 8, 16, 24 }这四个引脚组合形成一个pin group,提供SPI的功能。pin control subsystem要管理所有的pin group。
(C)配置这些pin的特性。例如配置该引脚上的pull-up/down电阻,配置drive strength等
(2)接口规格。linux内核的某个软件组件必须放回到linux系统中才容易探讨它的接口以及在系统中的位置,因此,在本章的第二节会基于系统block上描述各个pin control subsystem和其他内核模块的接口。
(3)内部逻辑。要研究一个subsystem的内部逻辑,首先要打开黑盒子,细分模块,然后针对每一个模块进行功能分析、外部接口分析、内部逻辑分析。如果模块还是比较大,难于掌握,那么就继续细分,拆成子模块,重复上面的分析过程。在本章的第三节中,我们打开pin control subsystem的黑盒子进行进一步的分析。
2、pin control subsystem在和其他linux内核模块的接口关系图如下图所示:
pin control subsystem会向系统中的其他driver提供接口以便进行该driver的pin config和pin mux的设定,这部分的接口在第四章描述。理想的状态是GPIO controll driver也只是象UART,SPI这样driver一样和pin control subsystem进行交互,但是,实际上由于各种源由(后文详述),pin control subsystem和GPIO subsystem必须有交互,这部分的接口在第五章描述。第六章描述了Driver model和pin control subsystem的接口,第七章描述了为Pin control subsystem提供database支持的Device Tree和Machine driver的接口。
3、pin control subsystem内部block diagram
起始理解了接口部分内容,阅读和解析pin control subsystem的内部逻辑已经很简单,本文就不再分析了。
四、pin control subsystem向其他driver提供的接口
当你准备撰写一个普通的linux driver(例如串口驱动)的时候,你期望pin control subsystem提供的接口是什么样子的?简单,当然最好是简单的,最最好是没有接口,当然这是可能的,具体请参考第六章的接口。
1、概述
普通driver调用pin control subsystem的主要目标是:
(1)设定该设备的功能复用。设定设备的功能复用需要了解两个概念,一个是function,另外一个pin group。function是功能抽象,对应一个HW逻辑block,例如SPI0。虽然给定了具体的gunction name,我们并不能确定其使用的pins的情况。例如:为了设计灵活,芯片内部的SPI0的功能可能引出到pin group { A8, A7, A6, A5 },也可能引出到另外一个pin group{ G4, G3, G2, G1 },但毫无疑问,这两个pin group不能同时active,毕竟芯片内部的SPI0的逻辑功能电路只有一个。 因此,只有给出function selector(所谓selector就是一个ID或者index)以及function的pin group selector才能进行function mux的设定。
(2)设定该device对应的那些pin的电气特性。
此外,由于电源管理的要求,某个device可能处于某个电源管理状态,例如idle或者sleep,这时候,属于该device的所有的pin就会需要处于另外的状态。综合上述的需求,我们把定义了pin control state的概念,也就是说设备可能处于非常多的状态中的一个,device driver可以切换设备处于的状态。为了方便管理pin control state,我们又提出了一个pin control state holder的概念,用来管理一个设备的所有的pin control状态。因此普通driver调用pin control subsystem的接口从逻辑上将主要是:
(1)获取pin control state holder的句柄
(2)设定pin control状态
(3)释放pin control state holder的句柄
pin control state holder的定义如下:
struct pinctrl {
struct list_head node;--系统中的所有device的pin control state holder被挂入到了一个全局链表中
struct device *dev;---该pin control state holder对应的device
struct list_head states;----该设备的所有的状态被挂入到这个链表中
struct pinctrl_state *state;---当前的pin control state
struct list_head dt_maps;----mapping table
struct kref users;------reference count
};
系统中的每一个需要和pin control subsystem进行交互的设备在进行设定之前都需要首先获取这个句柄。而属于该设备的所有的状态都是挂入到一个链表中,链表头就是pin control state holder的states成员,一个state的定义如下:
struct pinctrl_state {
struct list_head node;---挂入链表头的节点
const char *name;-----该state的名字
struct list_head settings;---属于该状态的所有的settings
};
一个pin state包含若干个setting,所有的settings被挂入一个链表中,链表头就是pin state中的settings成员,定义如下:
struct pinctrl_setting {
struct list_head node;
enum pinctrl_map_type type;
struct pinctrl_dev *pctldev;
const char *dev_name;
union {
struct pinctrl_setting_mux mux;
struct pinctrl_setting_configs configs;
} data;
};
当driver设定一个pin state的时候,pin control subsystem内部会遍历该state的settings链表,将一个一个的setting进行设定。这些settings有各种类型,定义如下:
enum pinctrl_map_type {
PIN_MAP_TYPE_INVALID,
PIN_MAP_TYPE_DUMMY_STATE,
PIN_MAP_TYPE_MUX_GROUP,---功能复用的setting
PIN_MAP_TYPE_CONFIGS_PIN,----设定单一一个pin的电气特性
PIN_MAP_TYPE_CONFIGS_GROUP,----设定单pin group的电气特性
};
有pin mux相关的设定(PIN_MAP_TYPE_MUX_GROUP),定义如下:
struct pinctrl_setting_mux {
unsigned group;--------该setting所对应的group selector
unsigned func;---------该setting所对应的function selector
};
有了function selector以及属于该functiong的roup selector就可以进行该device和pin mux相关的设定了。设定电气特性的settings定义如下:
struct pinctrl_map_configs {
const char *group_or_pin;----该pin或者pin group的名字
unsigned long *configs;----要设定的值的列表。这个值被用来写入HW
unsigned num_configs;----列表中值的个数
};
2、具体的接口
(1)devm_pinctrl_get和pinctrl_get。devm_pinctrl_get是Resource managed版本的pinctrl_get,核心还是pinctrl_get函数。这两个接口都是获取设备(设备模型中的struct device)的pin control state holder(struct pinctrl)。pin control state holder不是静态定义的,一般在第一次调用该函数的时候会动态创建。创建一个pin control state holder是一个大工程,我们分析一下这段代码:
static struct pinctrl *create_pinctrl(struct device *dev)
{分配pin control state holder占用的内存并初始化
p = kzalloc(sizeof(*p), GFP_KERNEL);
p->dev = dev;
INIT_LIST_HEAD(&p->states);
INIT_LIST_HEAD(&p->dt_maps);mapping table这个database的建立也是动态的,当第一次调用pin control state holder的get函数的时候,就会通过调用pinctrl_dt_to_map来建立该device需要的mapping entry。具体请参考第七章。
ret = pinctrl_dt_to_map(p);
devname = dev_name(dev);
mutex_lock(&pinctrl_maps_mutex);
for_each_maps(maps_node, i, map) {
/* Map must be for this device */
if (strcmp(map->dev_name, devname))
continue;ret = add_setting(p, map);----分析一个mapping entry,把这个setting的代码加入到holder中
}
mutex_unlock(&pinctrl_maps_mutex);kref_init(&p->users);
/* 把这个新增加的pin control state holder加入到全局链表中 */
mutex_lock(&pinctrl_list_mutex);
list_add_tail(&p->node, &pinctrl_list);
mutex_unlock(&pinctrl_list_mutex);return p;
}
(2)devm_pinctrl_put和pinctrl_put。是(1)接口中的逆函数。devm_pinctrl_get和pinctrl_get获取句柄的时候申请了很多资源,在devm_pinctrl_put和pinctrl_put可以释放。需要注意的是多次调用get函数不会重复分配资源,只会reference count加一,在put中referrenct count减一,当count==0的时候才释放该device的pin control state holder持有的所有资源。
(3)pinctrl_lookup_state。根据state name在pin control state holder找到对应的pin control state。具体的state是各个device自己定义的,不过pin control subsystem自己定义了一些标准的pin control state,定义在pinctrl-state.h文件中:
#define PINCTRL_STATE_DEFAULT "default"
#define PINCTRL_STATE_IDLE "idle"
#define PINCTRL_STATE_SLEEP "sleep"
(4)pinctrl_select_state。设定一个具体的pin control state接口。
五、和GPIO subsystem交互
1、为何pin control subsystem要和GPIO subsystem交互?
GPIO的HW block应该和其他功能复用的block是对等关系的,它们共同输入到一个复用器block,这个block的寄存器控制哪一个功能电路目前是active的。pin configuration是全局的,不论哪种功能是active的,都可以针对pin进行电气特性的设定。这样的架构下,上图中红色边框的三个block是完全独立的HW block,其控制寄存器在SOC datasheet中应该是分成三个章节描述,同时,这些block的寄存器应该分别处于不同的地址区间。
对于软件工程师,我们可以让pin control subsystem和GPIO subsystem完全独立,各自进行初始化,各自映射自己的寄存器地址空间,对于pin control subsystem而言,GPIO和其他的HW block没有什么不同,都是使用自己提供服务的一个软件模块而已。然而实际上SOC的设计并非总是向软件工程师期望的那样,有的SOC的设计框架图如下:
这时候,GPIO block是alway active的,而红色边框的三个block是紧密的捆绑在一起,它们的寄存器占据了一个memory range(datasheet中用一个章节描述这三个block)。这时候,对于软件工程师来说就有些纠结了,本来不属于我的GPIO控制也*要参与进来。这时候,硬件寄存器的控制都是pin controller来处理,GPIO相关的操作都要经过pin controller driver,这时候,pin controller driver要作为GPIO driver的back-end出现。
2、具体的接口形态
(1)pinctrl_request_gpio。该接口主要用来申请GPIO。GPIO也是一种资源,使用前应该request,使用完毕后释放。具体的代码如下:
int pinctrl_request_gpio(unsigned gpio)----这里传入的是GPIO 的ID
{
struct pinctrl_dev *pctldev;
struct pinctrl_gpio_range *range;
int ret;
int pin;ret = pinctrl_get_device_gpio_range(gpio, &pctldev, &range);---A
if (ret) {
if (pinctrl_ready_for_gpio_range(gpio))
ret = 0;
return ret;
}mutex_lock(&pctldev->mutex);
pin = gpio_to_pin(range, gpio); ---将GPIO ID转换成pin IDret = pinmux_request_gpio(pctldev, range, pin, gpio); ------B
mutex_unlock(&pctldev->mutex);
return ret;
}
毫无疑问,申请GPIO资源本应该是GPIO subsystem的责任,但是由于上一节描述的源由,pin control subsystem提供了这样一个接口函数供GPIO driver使用(其他的内核driver不应该调用,它们应该使用GPIO subsystem提供的接口)。多么丑陋的代码,作为pin control subsystem,除了维护pin space中的ID,还要维护GPIO 的ID以及pin ID和GPIO ID的关系。
A:根据GPIO ID找到该ID对应的pin control device(struct pinctrl_dev)和GPIO rang(pinctrl_gpio_range)。在core driver中,每个low level的pin controller device都被映射成一个struct pinctrl_dev,并形成链表,链表头就是pinctrldev_list。由于实际的硬件设计(例如GPIO block被分成若干个GPIO 的bank,每个bank就对应一个HW GPIO Controller Block),一个pin control device要管理的GPIO ID是分成区域的,每个区域用struct pinctrl_gpio_range来抽象,在low level 的pin controller初始化的时候(具体参考samsung_pinctrl_register的代码),会调用pinctrl_add_gpio_range将每个GPIO bank表示的gpio range挂入到pin control device的range list中(gpio_ranges成员)。pinctrl_gpio_range 的定义如下:
struct pinctrl_gpio_range {
struct list_head node;
const char *name;
unsigned int id;-----------GPIO chip ID
unsigned int base;------该range中的起始GPIO IDD
unsigned int pin_base;---在线性映射的情况下,这是起始的pin base
unsigned const *pins;---在非线性映射的时候,这是table是pin到GPIO的lookup table
unsigned int npins;----这个range有多少个GPIO引脚
struct gpio_chip *gc;------每个GPIO bank都是一个gpio chip,对应一个GPIO range
};
pin ID和GPIO ID有两种映射关系,一种是线性映射(这时候pin_base有效),也就是说,对于这个GPIO range,GPIO base ID是a,pin ID base是b,那么a<--->b,a+1<--->b+1,a+2<--->b+2,以此类推。对于非线性映射(pin_base无效,pins是有效的),我们需要建立一个lookup table,以GPIO ID为索引,可以找到对于的pin ID。
B:这里主要是进行复用功能的设定,毕竟GPIO也是引脚的一个特定的功能。pinmux_request_gpio函数的作用主要有两个,一个是在core driver中标记该pin已经用作GPIO了,这样,如果有模块后续request该资源,那么core driver可以拒绝不合理的要求。第二步就是调用底层pin controller driver的callback函数,进行底层寄存器相关的操作。
(2)pinctrl_free_gpio。有申请就有释放,这是pinctrl_request_gpio的逆函数
(3)pinctrl_gpio_direction_input和pinctrl_gpio_direction_output。为已经指定为GPIO功能的引脚设定方向,输入或者输出。代码很简单,不再赘述。
六、和驱动模型的接口
前文已经表述过,最好是让统一设备驱动模型(Driver model)来处理pin 的各种设定。与其自己写代码调用devm_pinctrl_get、pinctrl_lookup_state、pinctrl_select_state等pin control subsystem的接口函数,为了不让linux内核自己的框架处理呢。本章将分析具体的代码,这些代码实例对自己driver调用pin control subsystem的接口函数来设定本device的pin control的相关设定也是有指导意义的。 linux kernel中的驱动模型提供了driver和device的绑定机制,一旦匹配会调用probe函数如下:
static int really_probe(struct device *dev, struct device_driver *drv)
{
……
ret = pinctrl_bind_pins(dev); ---对该device涉及的pin进行pin control相关设定
……if (dev->bus->probe) {------下面是真正的probe过程
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}……
}
pinctrl_bind_pins的代码如下:
int pinctrl_bind_pins(struct device *dev)
{
int ret;dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);---(1)
dev->pins->p = devm_pinctrl_get(dev);-----------------(2)
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p, -------(3)
PINCTRL_STATE_DEFAULT);ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state); -----(4)
dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p, ------(3)
PINCTRL_STATE_SLEEP);dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p, -------(3)
PINCTRL_STATE_IDLE);return 0;
}
(1)struct device数据结构有一个pins的成员,它描述了和该设备相关的pin control的信息,定义如下:
struct dev_pin_info {
struct pinctrl *p;------------该device对应的pin control state holder
struct pinctrl_state *default_state;----缺省状态
struct pinctrl_state *sleep_state;-----电源管理相关的状态
struct pinctrl_state *idle_state;-----电源管理相关的状态
};
(2)调用devm_pinctrl_get获取该device对应的 pin control state holder句柄。
(3)搜索default state,sleep state,idle state并记录在本device中
(3)将该设备设定为pin default state
七、和device tree或者machine driver相关的接口
1、概述
device tree或者machine driver这两个模块主要是为 pin control subsystem提供pin mapping database的支持。这个database的每个entry用下面的数据结构表示:
struct pinctrl_map {
const char *dev_name;---使用这个mapping entry的设备名
const char *name;------该名字表示了该mapping entry
enum pinctrl_map_type type;---这个entry的mapping type
const char *ctrl_dev_name; -----pin controller这个设备的名字
union {
struct pinctrl_map_mux mux;
struct pinctrl_map_configs configs;
} data;
};
2、通过machine driver静态定义的数据来建立pin mapping database
machine driver定义一个巨大的mapping table,描述,然后在machine初始化的时候,调用pinctrl_register_mappings将该table注册到pin control subsystem中。
3、通过device tree来建立pin mapping database
pin mapping信息定义在dts中,主要包括两个部分,一个是定义在各个具体的device node中,另外一处是定义在pin controller的device node中。
一个典型的device tree中的外设node定义如下(建议先看看pin controller driver的第二章关于dts的描述):
device-node-name {
定义该device自己的属性pinctrl-names = "sleep", "default";
pinctrl-0 = ;
pinctrl-1 = ;
};
对普通device的dts分析在函数pinctrl_dt_to_map中,代码如下:
int pinctrl_dt_to_map(struct pinctrl *p)
{
of_node_get(np);
for (state = 0; ; state++) {-------------------(1)
/* Retrieve the pinctrl-* property */
propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state);
prop = of_find_property(np, propname, &size);
kfree(propname);
if (!prop)
break;
list = prop->value;
size /= sizeof(*list); --------------(2)/* Determine whether pinctrl-names property names the state */
ret = of_property_read_string_index(np, "pinctrl-names", ------(3)
state, &statename);
if (ret < 0) {
/* strlen("pinctrl-") == 8 */
statename = prop->name + 8; -------------(4)
}/* For every referenced pin configuration node in it */
for (config = 0; config < size; config++) { -----------(5)
phandle = be32_to_cpup(list++);/* Look up the pin configuration node */
np_config = of_find_node_by_phandle(phandle); ------(6)/* Parse the node */
ret = dt_to_map_one_config(p, statename, np_config); ----(7)
of_node_put(np_config);
if (ret < 0)
goto err;
}/* No entries in DT? Generate a dummy state table entry */
if (!size) {
ret = dt_remember_dummy_state(p, statename); -------(8)
if (ret < 0)
goto err;
}
}return 0;
err:
pinctrl_dt_free_maps(p);
return ret;
}
(1)pinctrl-0 pinctrl-1 pinctrl-2……表示了该设备的一个个的状态,这里我们定义了两个pinctrl-0和pinctrl-1分别对应sleep和default状态。这里每次循环分析一个pin state。
(2)代码执行到这里,size和list分别保存了该pin state中所涉及pin configuration phandle的数目以及phandle的列表
(3)读取从pinctrl-names属性中获取state name
(4)如果没有定义pinctrl-names属性,那么我们将pinctrl-0 pinctrl-1 pinctrl-2……中的那个ID取出来作为state name
(5)遍历一个pin state中的pin configuration list,这里的pin configuration实际应该是pin controler device node中的sub node,用phandle标识。
(6)用phandle作为索引,在device tree中找他该phandle表示的那个pin configuration
(7)分析一个pin configuration,具体下面会仔细分析
(8)如果该设备没有定义pin configuration,那么也要创建一个dummy的pin state。
这里我们已经进入对pin controller node下面的子节点的分析过程了。分析一个pin configuration的代码如下:
static int dt_to_map_one_config(struct pinctrl *p, const char *statename,
struct device_node *np_config)
{
struct device_node *np_pctldev;
struct pinctrl_dev *pctldev;
const struct pinctrl_ops *ops;
int ret;
struct pinctrl_map *map;
unsigned num_maps;/* Find the pin controller containing np_config */
np_pctldev = of_node_get(np_config);
for (;;) {
np_pctldev = of_get_next_parent(np_pctldev);-------(1)
if (!np_pctldev || of_node_is_root(np_pctldev)) {
of_node_put(np_pctldev);
return -EPROBE_DEFER;
}
pctldev = get_pinctrl_dev_from_of_node(np_pctldev);-----(2)
if (pctldev)
break;------------------------(3)
/* Do not defer probing of hogs (circular loop) */
if (np_pctldev == p->dev->of_node) {
of_node_put(np_pctldev);
return -ENODEV;
}
}
of_node_put(np_pctldev);/*
* Call pinctrl driver to parse device tree node, and
* generate mapping table entries
*/
ops = pctldev->desc->pctlops;
ret = ops->dt_node_to_map(pctldev, np_config, &map, &num_maps);----(4)
if (ret < 0)
return ret;/* Stash the mapping table chunk away for later use */
return dt_remember_or_free_map(p, statename, pctldev, map, num_maps);----(5)
}
(1)首先找到该pin configuration node对应的parent node(也就是pin controler对应的node),如果找不到或者是root node,则进入出错处理。
(2)获取pin control class device
(3)一旦找到pin control class device则跳出for循环
(4)调用底层的callback函数处理pin configuration node。这也是合理的,毕竟很多的pin controller bindings是需要自己解析的。
(5)将该pin configuration node的mapping entry信息注册到系统中
八、core driver和low level pin controller driver的接口规格
pin controller描述符。每一个特定的pin controller都用一个struct pinctrl_desc来抽象,具体如下:
struct pinctrl_desc {
const char *name;
struct pinctrl_pin_desc const *pins;
unsigned int npins;
const struct pinctrl_ops *pctlops;
const struct pinmux_ops *pmxops;
const struct pinconf_ops *confops;
struct module *owner;
};
pin controller描述符需要描述它可以控制多少个pin(成员npins),每一个pin的信息为何?(成员pins)。这两个成员就确定了一个pin controller所能控制的引脚的信息。
pin controller描述符中包括了三类操作函数:pctlops是一些全局的控制函数,pmxops是复用引脚相关的操作函数,confops操作函数是用来配置引脚的特性(例如:pull-up/down)。struct pinctrl_ops中各个callback函数的具体的解释如下:
callback函数 | 描述 |
get_groups_count | 该pin controller支持多少个pin group。pin group的定义可以参考本文关于pin controller的功能规格中的描述。注意不要把pin group和IO port的硬件分组搞混了。例如:S3C2416有138个I/O 端口,分成11组,分别是gpa~gpl,这个组并不叫pin group,而是叫做pin bank。pin group是和特定功能(例如SPI、I2C)相关的一组pin。 |
get_group_name | 给定一个selector(index),获取指定pin group的name |
get_group_pins | 给定一个selector(index),获取该pin group中pin的信息(该pin group包括多少个pin,每个pin的ID是什么) |
pin_dbg_show | debug fs的callback接口 |
dt_node_to_map | 分析一个pin configuration node并把分析的结果保存成mapping table entry,每一个entry表示一个setting(一个功能复用设定,或者电气特性设定) |
dt_free_map | 上面函数的逆函数 |
复用引脚相关的操作函数的具体解释如下:
call back函数 | 描述 |
request | pin control core进行具体的复用设定之前需要调用该函数,主要是用来请底层的driver判断某个引脚的复用设定是否是OK的。 |
free | 是request的逆函数。调用request函数请求占用了某些pin的资源,调用free可以释放这些资源 |
get_functions_count | 就是返回pin controller支持的function的数目 |
get_function_name | 给定一个selector(index),获取指定function的name |
get_function_groups | 给定一个selector(index),获取指定function的pin groups信息 |
enable | enable一个function。当然要给出function selector和pin group的selector |
disable | enable的逆函数 |
gpio_request_enable | request并且enable一个单独的gpio pin |
gpio_disable_free | gpio_request_enable的逆函数 |
gpio_set_direction | 设定GPIO方向的回调函数 |
配置引脚的特性的struct pinconf_ops数据结构的各个成员定义如下:
call back函数 | 描述 |
pin_config_get | 给定一个pin ID以及config type ID,获取该引脚上指定type的配置。 |
pin_config_set | 设定一个指定pin的配置 |
pin_config_group_get | 以pin group为单位,获取pin上的配置信息 |
pin_config_group_set | 以pin group为单位,设定pin group的特性配置 |
pin_config_dbg_parse_modify | debug接口 |
pin_config_dbg_show | debug接口 |
pin_config_group_dbg_show | debug接口 |
pin_config_config_dbg_show | debug接口 |
pin controller driver代码分析
一、前言
对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datasheet会把pin controller的内容放入GPIO controller的章节中),主要功能包括:
(1)pin multiplexing。基于ARM core的嵌入式处理器一般会提供丰富的功能,例如camera interface、LCD interface、USB、I2C、SPI等等。虽然处理器有几百个pin,但是这些pin还是不够分配,因此有些pin需要复用。例如:127号GPIO可以做一个普通的GPIO控制LED,也可以配置成I2C的clock信号,也可以配置成SPI的data out信号。当然,这些功能不可能同时存在,因为硬件信号只有一个。
(2)pin configuration。这些配置参数包括:pull-up/down电阻的设定, tri-state设定,drive-strength的设定。
本文主要描述pin control subsystem中的low level driver,也就是驱动pin controller的driver。具体的硬件选用的是S3C2416的硬件平台。既然是代码分析,本文不是非常多的描述框架性的内容,关于整个pin control subsystem软件结构的描述请参考Linux内核中的GPIO系统之(2)。
阅读本文需要device tree的知识,建议首先阅读device tree代码分析。
二、pin controller相关的DTS描述
类似其他的硬件,pin controller这个HW block需要是device tree中的一个节点。此外,各个其他的HW block在驱动之前也需要先配置其引脚复用功能,因此,这些device(我们称pin controller是host,那么这些使用pin controller进行引脚配置的device叫做client device)也需要在它自己的device tree node中描述pin control的相关内容
1、S3C2416 pin controller DTS结构
下面的伪代码描述了S3C2416 pin controller 的DTS结构:
pinctrl@56000000 {
定义S3C2416 pin controller自己的属性定义属于S3C2416 pin controller的pin configurations
}
每个pin configuration都是pin controller的child node,描述了client device要使用到的一组pin的配置信息。具体如何定义pin configuration是和具体的pin controller相关的。
在pin controller node中定义pin configuration其目的是为了让client device引用。所谓client device其实就是使用pin control subsystem提供服务的那些设备,例如串口设备。在使用之前,我们一般会在初始化代码中配置相关的引脚功能是串口功能。有了device tree,我们可以通过device tree来传递这样的信息。也就是说,各个device可以通过自己节点的属性来指向pin controller的某个child node,也就是pin configuration了。samsung 24xx系列SOC的pin controller的pin configurations包括两类,一类是定义pin bank,另外一类是定义功能复用配置。
2、pin configuration定义
我们举两个简单的例子(当然一个是pin bank,另外一个是定义功能复用配置)来理解pin configuration第一个例子是描述pin bank:
pinctrl@56000000 {
定义S3C2416 pin controller自己的属性……
gpf {
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>;
linux,phandle = <0xc>;
phandle = <0xc>;
};……
}
其实S3C2416 pin controller定义了gpa到gpm共计11个sub node,每个sub node是描述S3C2416 GPIO controller的各个bank信息。S3C2416有138个I/O 端口(或者叫做pin、finger、pad)这些端口分成了11个bank(这里没有用group这个术语,为了和pin group这个概念区分开,pin group的概念下面会具体描述):
Port A(GPA) : 25-output port
Port B(GPB) : 9-input/output port
Port C(GPC) : 16-input/output port
Port D(GPD) : 16-input/output port
Port E(GPE) : 16-input/output port
Port F(GPF) : 8-input/output port
Port G(GPG) : 8-input/output port
Port H(GPH) : 15-input/output port
Port K(GPK) : 16-input/output port
Port L(GPL) : 7-input/output port
Port M(GPM) : 2-input port
之所以分成bank,主要是把特性相同的GPIO进行分组,方便控制。例如:这些bank中,只有GPF和GPG这两个bank上的引脚有中断功能,其他的都没有。interrupt-controller这个属性相信大家已经熟悉,就是说明该node是一个interrupt controller。gpio-controller类似,说明该device node是一个GPIO controller。#gpio-cells属性是一个GPIO controller的必须定义的属性,它描述了需要多少个cell来具体描述一个GPIO(这是和具体的GPIO controller相关的)。#interrupt-cells的概念类似,不再赘述。phandle(linux,phandle这个属性和phandle是一样的,只不过linux,phandle是old-style,多定义一个属性是为了兼容)定义了一个句柄,当其他的device node想要引用这个node的时候就可以使用该句柄。具体的例子参考下节client device的DTS的描述。
另外一个例子是uart的pin configuration,代码如下:
pinctrl@56000000 {
定义S3C2416 pin controller自己的属性……
uart0-data {
samsung,pins = "gph-0", "gph-1";
samsung,pin-function = <0x2>;
linux,phandle = <0x2>;
phandle = <0x2>;
};uart0-fctl {
samsung,pins = "gph-8", "gph-9";
samsung,pin-function = <0x2>;
linux,phandle = <0x3>;
phandle = <0x3>;
};……
}
samsung,pins这个属性定义了一个pin configuration所涉及到的引脚定义。对于uart0-data这个node,该配置涉及了gph bank中的第一个和第二个GPIO pin。一旦选择了一个功能,那么samsung,pins定义的所有的引脚都需要做相应的功能设定,那么具体设定什么值呢?这就是samsung,pin-function定义的内容了。而具体设定哪个值则需要去查阅datasheet了。对于uart0-data,向gph bank中的第一个和第二个GPIO pin对应的配置寄存器中写入2就可以把这两个pin定义为uart功能。
3.client device的DTS
一个典型的device tree中的外设node定义如下:
device-node-name {
定义该device自己的属性pinctrl-names = "sleep", "active";------(1)
pinctrl-0 = <pin-config-0-a>;--------------(2)
pinctrl-1 = <pin-config-1-a pin-config-1-b>;
};
(1)pinctrl-names定义了一个state列表。那么什么是state呢?具体说应该是pin state,对于一个client device,它使用了一组pin,这一组pin应该同时处于某种状态,毕竟这些pin是属于一个具体的设备功能。state的定义和电源管理关系比较紧密,例如当设备active的时候,我们需要pin controller将相关的一组pin设定为具体的设备功能,而当设备进入sleep状态的时候,需要pin controller将相关的一组pin设定为普通GPIO,并精确的控制GPIO状态以便节省系统的功耗。state有两种,标识,一种就是pinctrl-names定义的字符串列表,另外一种就是ID。ID从0开始,依次加一。根据例子中的定义,state ID等于0(名字是active)的state对应pinctrl-0属性,state ID等于1(名字是idle)的state对应pinctrl-1属性。具体设备state的定义和各个设备相关,具体参考在自己的device bind。
(2)pinctrl-x的定义。pinctrl-x是一个句柄(phandle)列表,每个句柄指向一个pin configuration。有时候,一个state对应多个pin configure。例如在active的时候,I2C功能有两种配置,一种是从pin ID{7,8}引出,另外一个是从pin ID{69,103}引出。
我们选取samsung串口的dts定义如下:
serial@50000000 {
……
pinctrl-names = "default";
pinctrl-0 = <0x2 0x3>;
};
该serial device只定义了一个state就是default,对应pinctrl-0属性定义。pinctrl-0是一个句柄(phandle)列表,每个句柄指向一个pin configuration。0x2对应上节中的uart0-data节点,0x03对应uart0-fctl 节点,也就是说,这个串口有两种配置,一种是从gph bank中的第一个和第二个GPIO pin引出,另外一个是从gph bank中的第8个和第9个GPIO pin引出。
三、 pin controller driver初始化
1、注册pin control device
旧的内核一般是在machine相关的代码中建立静态的platform device的数据结构,然后在machine初始化的时候,将静态定义的platform device注册到系统。不过在引入device tree之后,事情发生了变化。
根据device tree代码分析,我们知道,在系统初始化的时候,dts描述的device node会形成一个树状结构,在machine初始化的过程中,会scan device node的树状结构,将真正的硬件device node变成一个个的设备模型中的device结构(比如struct platform_device)并加入到系统中。我们看看具体2416描述pin controller的dts code,如下:
pinctrl@56000000 {
reg = <0x56000000 0x1000="">;
compatible = "samsung,s3c2416-pinctrl";……省略wakeup的pin configuration
……省略gpb~gpm这些pink bank的pin configuration
……省略Pin groups的相关描述
}
reg属性描述pin controller硬件的地址信息,开始地址是0x56000000 ,地址长度是0x1000。compatible属性用来描述pin controller的programming model。该属性的值是string list,定义了一系列的modle(每个string是一个model)。这些字符串列表被操作系统用来选择用哪一个pin controller driver来驱动该设备,后面的代码会更详细的描述。 pin control subsystem要想进行控制,必须首先了解自己控制的对象,也就是说软件需要提供一个方法将各种硬件信息(total有多少可控的pin,有多少bank,pin的复用情况以及pin的配置情况)注册到pin control subsystem中,这也是pin controller driver的初始化的主要内容。这些信息当然可以通过定义静态的表格(参考linux/drivers/pinctrl目录下的pinctrl-u300.c文件,该文件定义了一个大数组u300_pads来描述每一个pin),也可以通过dts加上静态表格的方式(2416采用的方式)。
2、注册pin controller driver
当然,pinctrl@56000000这个device node也会变成一个platform device加入系统。有了device,那么对应的platform driver是如何注册到系统中的呢?代码如下:
static int __init samsung_pinctrl_drv_register(void)
{
……return platform_driver_register(&samsung_pinctrl_driver);
}
系统初始化的时候,该函数会执行,向系统注册了samsung_pinctrl_driver:
static struct platform_driver samsung_pinctrl_driver = {
.probe = samsung_pinctrl_probe, ----该driver的初始化函数
.driver = {
.name = "samsung-pinctrl",
.owner = THIS_MODULE,
.of_match_table = samsung_pinctrl_dt_match, ----匹配列表
},
};
3、probe过程(driver初始化过程)
在linux kernel引入统一设备模型之后,bus、driver和device形成了设备模型中的铁三角。对于platform这种类型的bus,其铁三角数据是platform_bus_type(表示platform这种类型的bus)、struct platform_device(platform bus上的device)、struct platform_driver(platform bus上的driver)。统一设备模型大大降低了驱动工程师的工作量,驱动工程师只要将driver注册到系统即可,剩余的事情交给统一设备模型来完成。每次系统增加一个platform_driver,platform_bus_type都会启动scan过程,让新加入的driver扫描整个platform bus上的device的链表,看看是否有device让该driver驱动。同样的,每次系统增加一个platform_device,platform_bus_type也会启动scan过程,遍历整个platform bus上的driver的链表,看看是否有适合驱动该device的driver。具体匹配的代码是platform bus上的match函数,如下:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
旧的的platform的匹配函数就是简单的比较device和driver的名字,多么简单,多么清晰,真是有点怀念过去单纯而美好的生活。当然,事情没有那么糟糕,我们这里只要关注OF style的匹配过程即可,思路很简单,解铃还需系铃人,把匹配过程推给device tree模块,代码如下:
const struct of_device_id *of_match_device(const struct of_device_id *matches,
const struct device *dev)
{
if ((!matches) || (!dev->of_node))
return NULL;
return of_match_node(matches, dev->of_node);
}
platform driver提供了match table(struct device_driver 中的of_match_table的成员)。platform device提供了device tree node(struct device中的of_node成员)。对于我们这个场景,match table是samsung_pinctrl_dt_match,代码如下:
static const struct of_device_id samsung_pinctrl_dt_match[] = {
……
{ .compatible = "samsung,s3c2416-pinctrl",
.data = s3c2416_pin_ctrl },
……
{},
};
再去看看dts中pin controller的节点compatible属性的定义,你会禁不住感慨:啊,终于遇到对的人。这里还要特别说明的是data成员被设定为s3c2416_pin_ctrl ,它描述了2416的HW pin controller,我们称之samsung pin controller的描述符,初始化的过程中需要这个数据结构,后面还会详细介绍它。一旦pin controller这个device遇到了适当的driver,就会调用probe函数进行具体的driver初始化的动作了,代码如下:
static int samsung_pinctrl_probe(struct platform_device *pdev)
{
struct samsung_pinctrl_drv_data *drvdata;
struct device *dev = &pdev->dev;
struct samsung_pin_ctrl *ctrl;
struct resource *res;
int ret;drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); ------(1)
ctrl = samsung_pinctrl_get_soc_data(drvdata, pdev); ----------(2)
drvdata->ctrl = ctrl;
drvdata->dev = dev;res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -----分配memory资源
drvdata->virt_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(drvdata->virt_base))
return PTR_ERR(drvdata->virt_base);res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); ------分配IRQ资源
if (res)
drvdata->irq = res->start;ret = samsung_gpiolib_register(pdev, drvdata); -------------(3)
ret = samsung_pinctrl_register(pdev, drvdata); -------------(4)
if (ctrl->eint_gpio_init) ------------------(5)
ctrl->eint_gpio_init(drvdata);
if (ctrl->eint_wkup_init)
ctrl->eint_wkup_init(drvdata);platform_set_drvdata(pdev, drvdata); -设定platform device的私有数据为samsung_pinctrl_drv_data
/* Add to the global list */
list_add_tail(&drvdata->node, &drvdata_list); --挂入全局链表return 0;
}
(1)devm_kzalloc函数是为struct samsung_pinctrl_drv_data数据结构分配内存。每当driver probe一个具体的device实例的时候,都需要建立一些私有的数据结构来保存该device的一些具体的硬件信息(本场景中,这个数据结构就是struct samsung_pinctrl_drv_data)。在过去,驱动工程师多半使用kmalloc或者kzalloc来分配内存,但这会带来一些潜在的问题。例如:在初始化过程中,有各种各样可能的失败情况,这时候就依靠driver工程师小心的撰写代码,释放之前分配的内存。当然,初始化过程中,除了memory,driver会为probe的device分配各种资源,例如IRQ 号,io memory map、DMA等等。当初始化需要管理这么多的资源分配和释放的时候,很多驱动程序都出现了资源管理的issue。而且,由于这些issue是异常路径上的issue,不是那么容易测试出来,更加重了解决这个issue的必要性。内核解决这个问题的模式(所谓解决一类问题的设计方法就叫做设计模式)是Devres,即device resource management软件模块。更细节的内容就不介绍了,其核心思想就是资源是设备的资源,那么资源的管理归于device,也就是说不需要driver过多的参与。当device和driver detach的时候,device会自动的释放其所有的资源。
(2)分配了struct samsung_pinctrl_drv_data数据结构的内存,当然下一步就是初始化这个数据结构了。我们先看看2416的pin controller driver是如何定义该数据结构的:
struct samsung_pinctrl_drv_data {
struct list_head node;---------多个pin controller的描述符可以形成链表
void __iomem *virt_base;---------访问硬件寄存器的基地址
struct device *dev;-----------和platform device建立联系
int irq; --------irq number,对于2416 pin control硬件而言,不需要irq资源struct samsung_pin_ctrl *ctrl;----samsung pin controller描述符
struct pinctrl_desc pctl; ------指向pin control subsystem中core driver中抽象的pin controller描述符。
struct pinctrl_dev *pctl_dev; ------指向core driver的pin controller class deviceconst struct samsung_pin_group *pin_groups; -描述samsung pin controller中pin groups的信息
unsigned int nr_groups; --------描述samsung pin controller中pin groups的数目
const struct samsung_pmx_func *pmx_functions; --描述samsung pin controller中function信息
unsigned int nr_functions; --------描述samsung pin controller中function的数目
};
struct pinctrl_desc和struct pinctrl_dev 都是pin control subsystem中core driver的概念。各个具体硬件的pin controller可能会各不相同,但是可以抽取其共同的部分来形成一个HW independent的数据结构,这个数据就是pin controller描述符,在core driver中用struct pinctrl_desc表示,具体该数据结构的定义如下:
struct pinctrl_desc {
const char *name;
struct pinctrl_pin_desc const *pins;---指向npins个pin描述符,每个描述符描述一个pin
unsigned int npins;------------该pin controller中有多少个可控的pin
const struct pinctrl_ops *pctlops;------callback函数,是core driver和底层driver的接口
const struct pinmux_ops *pmxops;-----callback函数,是core driver和底层driver的接口
const struct pinconf_ops *confops;-----callback函数,是core driver和底层driver的接口
struct module *owner;
};
其实整个初始化过程的核心思想就是low level的driver定义一个pinctrl_desc ,设定pin相关的定义和callback函数,注册到pin control subsystem中。我们用引脚描述符(pin descriptor)来描述一个pin。在pin control subsystem中,struct pinctrl_pin_desc用来描述一个可以控制的引脚,我们称之引脚描述符,代码如下:
struct pinctrl_pin_desc {
unsigned number;-------ID,在本pin controller中唯一标识该引脚
const char *name;-------user friedly name
void *drv_data;
};
冰冷的pin ID是给机器用的,而name是给用户使用的,是ascii字符。
struct pinctrl_dev在pin control subsystem的core driver中抽象一个pin control device。其实我们可以通过多个层面来定义一个device。在这个场景下,pin control subsystem的core driver关注的是一类pin controller的硬件设备,具体其底层是什么硬件连接方式,使用什么硬件协议它不关心,它关心的是pin controller这类设备所有的通用特性和功能。当然2416的pin controller是通过platform bus连接的,因此,在low level的层面,需要一个platform device来标识2416的pin controller(需要注意的是:pin controller class device和platform device都是基于一个驱动模型中的device派生而来的,这里struct device是基类,struct pinctrl_dev和struct platform_device都是派生类,当然c本身不支持class,但面向对象的概念是同样的)。为了充分理解class这个概念,我们再举一个例子。对于audio的硬件抽象层,它应该管理所有的audio设备,因此这个软件模块应该有一个audio class的链表,连接了所有的系统中的audio设备。但这些具体的audio设备可能是PCI接口的audio设备,也可能是usb接口的audio设备,从具体的总线层面来看,也会有PCI或者USB设备来抽象对应的声卡设备。
OK,我们再看看samsung_pinctrl_drv_data底部四个成员,要理解该数据结构底部的四个成员,还要理解什么是pin mux function,什么是pin group。对于SOC而言,其引脚除了配置成普通GPIO之外,若干个引脚还可以组成一个pin group,形成特定的功能。例如pin number是{ 0, 8, 16, 24 }这四个引脚组合形成一个pin group,提供SPI的功能。既然有了pin group的概念,为何又有function这个概念呢?什么是function呢?SPI是function,I2C也是一个function,当然GPIO也是一个function。一个function有可能对应一组或者多组pin。例如:为了设计灵活,芯片内部的SPI0的功能可能引出到pin group { A8, A7, A6, A5 },也可能引出到另外一个pin group{ G4, G3, G2, G1 },但毫无疑问,这两个pin group不能同时active,毕竟芯片内部的SPI0的逻辑功能电路只有一个。 从这个角度看,pin control subsystem要进行功能设定的时候必须要给出function以及function的pin group才能确定所有的物理pin的位置。
我们前面已经说过了,struct samsung_pinctrl_drv_data数据结构就是2416的pin controller driver要驱动2416的HW pin controller的私有数据结构。这个数据结构中最重要的就是samsung pin controller描述符了。关于pin controller有两个描述符,一个是struct pinctrl_desc,是具体硬件无关的pin controller的描述符。struct samsung_pin_ctrl描述的具体samsung pin controller硬件相关的信息,比如说:pin bank的信息,不是所有的pin controller都是分bank的,因此pin bank的信息只能封装在low level的samsung pin controller driver中。这个数据结构定义如下:
struct samsung_pin_ctrl {
struct samsung_pin_bank *pin_banks;----定义具体的pin bank信息
u32 nr_banks; ---------number of pin banku32 base;----该pin controller的pin ID base。
u32 nr_pins; -----总的可以控制的pin的数目其他成员和本场景无关,和GPIO type的中断控制器驱动代码有关
};
关于上面的base可以多说两句。实际上,系统支持多个pin controller设备,这时候,系统要管理多个pin controller控制下的多个pin。每个pin有自己的pin ID,是唯一的,假设系统中有两个pin controller,一个是A,控制32个,另外一个是B,控制64个pin,我们可以统一编号,对A,pin ID从0~31,对于B,pin ID是从32~95。对于B,其pin base就是32。
samsung_pinctrl_probe->samsung_pinctrl_get_soc_data函数中会根据device tree的信息和静态定义的table来初始化该描述符,具体的代码如下:
static struct samsung_pin_ctrl *samsung_pinctrl_get_soc_data(
struct samsung_pinctrl_drv_data *d,
struct platform_device *pdev)
{
int id;
const struct of_device_id *match;
struct device_node *node = pdev->dev.of_node; ---获取device tree中的device node指针
struct device_node *np;
struct samsung_pin_ctrl *ctrl;
struct samsung_pin_bank *bank;
int i;id = of_alias_get_id(node, "pinctrl");
match = of_match_node(samsung_pinctrl_dt_match, node);
ctrl = (struct samsung_pin_ctrl *)match->data + id; --------Abank = ctrl->pin_banks;
for (i = 0; i < ctrl->nr_banks; ++i, ++bank) {------------B
spin_lock_init(&bank->slock);
bank->drvdata = d;
bank->pin_base = ctrl->nr_pins; ---ctrl->nr_pins初始的时候等于0,最后完成bank初始化后,该值等于total的pin number。
ctrl->nr_pins += bank->nr_pins;
}for_each_child_of_node(node, np) { ----------------C
if (!of_find_property(np, "gpio-controller", NULL))
continue;
bank = ctrl->pin_banks;
for (i = 0; i < ctrl->nr_banks; ++i, ++bank) {
if (!strcmp(bank->name, np->name)) {
bank->of_node = np;
break;
}
}
}ctrl->base = pin_base; ----------------------D
pin_base += ctrl->nr_pins;return ctrl;
}
samsung_pinctrl_get_soc_data这个函数名字基本反应了其功能,2416是samsung的一个具体的SOC型号,调用该函数可以返回一个表示2416 SOC的samsung pin controller的描述符。
A:这段代码主要是获取具体的2416的HW pin controller的信息,该数据结构在上文中出现过(具体参考pin controller的device tree match table:samsung_pinctrl_dt_match),就是s3c2416_pin_ctrl这个变量。这个变量定义了2416的pin controller的信息(S3C2416的pin controller的pin bank信息是定义在pin controller driver的静态数据,其实最好在dts中定义)如下:
struct samsung_pin_ctrl s3c2416_pin_ctrl[] = {
{
.pin_banks = s3c2416_pin_banks,------静态定义的2416的pin bank的信息
.nr_banks = ARRAY_SIZE(s3c2416_pin_banks),
.eint_wkup_init = s3c24xx_eint_init,
.label = "S3C2416-GPIO",
},
};
这个变量中包含了2416的pin bank的信息,包括:有多少个pin bank,每个bank中有多少个pin,pin bank的名字是什么,寄存器的offset是多少。这些信息用来初始化pin controller描述符数据结构。
B:初始化2416 samsung pin controller中各个bank的描述符。
C:device tree中表示pin controller的device node有若干的child node,分别表示gpa~gpl这11个bank,每个bank都是一个gpio controller。下面的代码遍历各个child node,并初始化各个bank描述符中的device tree node成员。 这里需要注意的是静态定义的pin bank的名字要和dts文件中定义的pin bank node的名字一样。
D:系统中有可能有多个pin controller,多个pin controller上的pin ID 应该是系统唯一的,ctrl->base表示本pin controller中的pin ID的起始值。
(3)本来pin control subsystem和GPIO subsystem应该是无关的两个子系统,应该各自进行自己的初始化过程。但实际中,由于硬件的复杂性,这两个子系统耦合性非常高。这里samsung_gpiolib_register函数就是把各个bank代表的gpio chip注册到GPIO subsystem中。更具体的信息请参考GPIO subsystem软件框架文档。
(4)samsung_pinctrl_register函数的主要功能是将本pin controller注册到pin control subsystem。代码如下:
static int samsung_pinctrl_register(struct platform_device *pdev,
struct samsung_pinctrl_drv_data *drvdata)
{
struct pinctrl_desc *ctrldesc = &drvdata->pctl;
struct pinctrl_pin_desc *pindesc, *pdesc;
struct samsung_pin_bank *pin_bank;
char *pin_names;
int pin, bank, ret;ctrldesc->name = "samsung-pinctrl";--------A
ctrldesc->owner = THIS_MODULE;
ctrldesc->pctlops = &samsung_pctrl_ops; ---call 函数,具体参考第四章的内容
ctrldesc->pmxops = &samsung_pinmux_ops;
ctrldesc->confops = &samsung_pinconf_ops;pindesc = devm_kzalloc(&pdev->dev, sizeof(*pindesc) *-------B
drvdata->ctrl->nr_pins, GFP_KERNEL);
ctrldesc->pins = pindesc;
ctrldesc->npins = drvdata->ctrl->nr_pins;
for (pin = 0, pdesc = pindesc; pin < ctrldesc->npins; pin++, pdesc++)---C
pdesc->number = pin + drvdata->ctrl->base;
pin_names = devm_kzalloc(&pdev->dev, sizeof(char) * PIN_NAME_LENGTH *---B
drvdata->ctrl->nr_pins, GFP_KERNEL);
for (bank = 0; bank < drvdata->ctrl->nr_banks; bank++) { ---------C
pin_bank = &drvdata->ctrl->pin_banks[bank];
for (pin = 0; pin < pin_bank->nr_pins; pin++) {
sprintf(pin_names, "%s-%d", pin_bank->name, pin);
pdesc = pindesc + pin_bank->pin_base + pin;
pdesc->name = pin_names;
pin_names += PIN_NAME_LENGTH;
}
}ret = samsung_pinctrl_parse_dt(pdev, drvdata);------D
drvdata->pctl_dev = pinctrl_register(ctrldesc, &pdev->dev, drvdata);---E
for (bank = 0; bank < drvdata->ctrl->nr_banks; ++bank) {-----F
pin_bank = &drvdata->ctrl->pin_banks[bank];
pin_bank->grange.name = pin_bank->name;
pin_bank->grange.id = bank;
pin_bank->grange.pin_base = pin_bank->pin_base;
pin_bank->grange.base = pin_bank->gpio_chip.base;
pin_bank->grange.npins = pin_bank->gpio_chip.ngpio;
pin_bank->grange.gc = &pin_bank->gpio_chip;
pinctrl_add_gpio_range(drvdata->pctl_dev, &pin_bank->grange);
}return 0;
}
A:初始化硬件无关的pin controller描述符(struct samsung_pinctrl_drv_data中的pctl成员)。该数据结构中还包含了所有pin的描述符的信息,这些pin descriptor所需要的内存在步骤B中分配
B:初始化过程中涉及不少内存分配,这些内存主要用于描述每一个pin(术语叫做pin descriptor)以及pin name。
C:初始化每一个pin 描述符的名字和ID。对于samsung的pin描述符,其名字使用pin-bank name + pin ID的形式。 ID的分配是从该pin controller的pin base开始分配ID的,逐个加一。
D:初始化pin group和function(具体内容在下节描述)
E:调用pinctrl_register注册到pin control subsystem 。这是pin control subsystem的核心函数,可以参考GPIO系统之2的描述。
F:在这里又不得不进行pin control subsystem和GPIO系统的耦合了。每个bank都是一个GPIO controller,但是pin bank使用的ID是pin control space中的ID,GPIO 子系统中使用的是GPIO space的ID,对于pin control subsystem而言,它需要建立这两个ID的映射关系。pinctrl_add_gpio_range就是起这个作用的。更具体的内容请参考pin control subsystem软件结构文档。 需要注意的是直接在pin controller driver中调用pinctrl_add_gpio_range是不推荐的,建议使用dts的方式在GPIO controller设备节点中描述。
(5)这里的代码是向kernel中的中断子系统注册interrupt controller。对于2416,有两个bank有中断功能,gpf和gpg,本质上gpf和gpg就是两个interrupt controller,挂接在2416真正的那个interrupt contrller之下,形成树状结构。具体的代码就不分析了,请参考GPIO类型的中断控制器代码分析。
4、pin control subsystem如何获取pin group的信息
具体的代码如下:
static int samsung_pinctrl_parse_dt(struct platform_device *pdev,
struct samsung_pinctrl_drv_data *drvdata)
{
struct device *dev = &pdev->dev;
struct device_node *dev_np = dev->of_node;
struct device_node *cfg_np;
struct samsung_pin_group *groups, *grp;
struct samsung_pmx_func *functions, *func;
unsigned *pin_list;
unsigned int npins, grp_cnt, func_idx = 0;
char *gname, *fname;
int ret;grp_cnt = of_get_child_count(dev_np); ------(1)
groups = devm_kzalloc(dev, grp_cnt * sizeof(*groups), GFP_KERNEL); ----(2)
grp = groups;functions = devm_kzalloc(dev, grp_cnt * sizeof(*functions), GFP_KERNEL); ---(2)
func = functions;
for_each_child_of_node(dev_np, cfg_np) { ----遍历pin control的所有的child node
u32 function;
if (!of_find_property(cfg_np, "samsung,pins", NULL)) -忽略掉那些没有samsung,pins属性的node
continue;ret = samsung_pinctrl_parse_dt_pins(pdev, cfg_np, --------(3)
&drvdata->pctl, &pin_list, &npins);
if (ret)
return ret;/* derive pin group name from the node name */
gname = devm_kzalloc(dev, strlen(cfg_np->name) + GSUFFIX_LEN, -分配pin group名字需要的内存
GFP_KERNEL);sprintf(gname, "%s%s", cfg_np->name, GROUP_SUFFIX);--添加“-grp”的后缀
grp->name = gname; ----------------(4)
grp->pins = pin_list;
grp->num_pins = npins;
of_property_read_u32(cfg_np, "samsung,pin-function", &function);
grp->func = function;
grp++;if (!of_find_property(cfg_np, "samsung,pin-function", NULL))
continue; ----忽略掉那些没有samsung,pin-function属性的node/* derive function name from the node name */
fname = devm_kzalloc(dev, strlen(cfg_np->name) + FSUFFIX_LEN,
GFP_KERNEL);
sprintf(fname, "%s%s", cfg_np->name, FUNCTION_SUFFIX); -----(5)func->name = fname;
func->groups = devm_kzalloc(dev, sizeof(char *), GFP_KERNEL); ----(6)
if (!func->groups) {
dev_err(dev, "failed to alloc memory for group list "
"in pin function");
return -ENOMEM;
}
func->groups[0] = gname;
func->num_groups = 1;
func++;
func_idx++;
}drvdata->pin_groups = groups; ----最终,pin group和function的信息被copy到pin controller
driver的私有数据结构struct samsung_pinctrl_drv_data 中
drvdata->nr_groups = grp_cnt;
drvdata->pmx_functions = functions;
drvdata->nr_functions = func_idx;return 0;
}
(1)pin controller的device node有若干个child node,每个child node都描述了一个pin configuration。of_get_child_count函数可以获取pin configuration的数目。
(2)根据pin configuration的数目分配内存。在这里共计分配了两片内存,一片保存了所有pin group的信息(struct samsung_pin_group ),一片保存了pin mux function的信息(struct samsung_pmx_func)。实际上,分配pin configuration的数目的内存有些浪费,因为不是每一个pin control的child node都是和pin group相关(例如pin bank node就是和pin group无关)。对于function,就更浪费了,因为有可能多个pin group对应一个function。
(3)samsung_pinctrl_parse_dt_pins函数主要分析samsung,pins这个属性,并根据属性值返回一个pin list,该list中每个entry是一个pin ID。
(4)初始化samsung pin group的描述符。具体的数据结构解释如下:
struct samsung_pin_group {
const char *name;---------pin group的名字,名字是device tree node name+-grp
const unsigned int *pins;-------pin list的信息
u8 num_pins;----------pin list中的数目
u8 func;------------对应samsung,pin-function属性的值,用来配置pin list中各个pin的功能设定寄存器
};
(5)一个pin configuration的device tree node被解析成两个描述符,一个是samsung pin group的描述符,另外一个是samsung pin mux function描述符。这两个描述符的名字都是根据dts file中的pin configuration的device node name生成,只不过pin group的名字附加-grp的后缀,而function描述符的名字后面附加-mux的后缀。
(6)对于samsung pin mux function描述符解释如下:
struct samsung_pmx_func {
const char *name;------pin function的名字,名字是device tree node name+-muxconst char **groups;-----指向pin groups的指针数组
u8 num_groups;------属于该function的pin group的个数
};
在具体的代码实现中num_groups总是等于1。
四、S3C2416 pin controller driver的操作函数
1、操作函数概述
pin controller描述符中包括了三类操作函数:pctlops是一些全局的控制函数,pmxops是复用引脚相关的操作函数,confops操作函数是用来配置引脚的特性(例如:pull-up/down)。这些callback函数都是和具体的底层pin controller的操作相关。
本章节主要描述这些call back函数的逻辑,这些callback的调用时机不会在这里描述,那些内容请参考pin control subsystem的描述。
2、struct pinctrl_ops中各个callback函数的具体的解释如下:
(1)samsung_get_group_count
该函数的代码如下:
static int samsung_get_group_count(struct pinctrl_dev *pctldev)
{
struct samsung_pinctrl_drv_data *drvdata;drvdata = pinctrl_dev_get_drvdata(pctldev);
return drvdata->nr_groups;
}
该函数主要是用来获取指定pin control device的pin group的数目。逻辑很简单,通过pin control的class device的driver_data成员可以获得samsung pin control driver的私有数据(struct samsung_pinctrl_drv_data),可以nr_groups成员返回group的数目。
(2)samsung_get_group_name
该函数的代码如下:
static const char *samsung_get_group_name(struct pinctrl_dev *pctldev,
unsigned selector)
{
struct samsung_pinctrl_drv_data *drvdata;drvdata = pinctrl_dev_get_drvdata(pctldev);
return drvdata->pin_groups[selector].name;
}
该函数主要用来获取指定group selector的pin group信息。
(3)samsung_get_group_pins
该函数的代码如下:
static int samsung_get_group_pins(struct pinctrl_dev *pctldev,
unsigned selector, const unsigned **pins, unsigned *num_pins)
{
struct samsung_pinctrl_drv_data *drvdata;drvdata = pinctrl_dev_get_drvdata(pctldev);
*pins = drvdata->pin_groups[selector].pins;
*num_pins = drvdata->pin_groups[selector].num_pins;
return 0;
}
该函数的主要功能是给定一个group selector(index),获取该pin group中pin的信息(该pin group包括多少个pin,每个pin的ID是什么) 。
(4)samsung_dt_node_to_map
该函数的代码如下:
static int samsung_dt_node_to_map(struct pinctrl_dev *pctldev,
struct device_node *np, struct pinctrl_map **maps,
unsigned *nmaps)
{
struct device *dev = pctldev->dev;
struct pinctrl_map *map;
unsigned long *cfg = NULL;
char *gname, *fname;
int cfg_cnt = 0, map_cnt = 0, idx = 0;/* count the number of config options specfied in the node */
for (idx = 0; idx < ARRAY_SIZE(pcfgs); idx++) {
if (of_find_property(np, pcfgs[idx].prop_cfg, NULL))
cfg_cnt++;
}/*
* Find out the number of map entries to create. All the config options
* can be accomadated into a single config map entry.
*/
if (cfg_cnt)
map_cnt = 1;
if (of_find_property(np, "samsung,pin-function", NULL))
map_cnt++;
if (!map_cnt) {
dev_err(dev, "node %s does not have either config or function "
"configurations\n", np->name);
return -EINVAL;
}/* Allocate memory for pin-map entries */
map = kzalloc(sizeof(*map) * map_cnt, GFP_KERNEL);
if (!map) {
dev_err(dev, "could not alloc memory for pin-maps\n");
return -ENOMEM;
}
*nmaps = 0;/*
* Allocate memory for pin group name. The pin group name is derived
* from the node name from which these map entries are be created.
*/
gname = kzalloc(strlen(np->name) + GSUFFIX_LEN, GFP_KERNEL);
if (!gname) {
dev_err(dev, "failed to alloc memory for group name\n");
goto free_map;
}
sprintf(gname, "%s%s", np->name, GROUP_SUFFIX);/*
* don't have config options? then skip over to creating function
* map entries.
*/
if (!cfg_cnt)
goto skip_cfgs;/* Allocate memory for config entries */
cfg = kzalloc(sizeof(*cfg) * cfg_cnt, GFP_KERNEL);
if (!cfg) {
dev_err(dev, "failed to alloc memory for configs\n");
goto free_gname;
}/* Prepare a list of config settings */
for (idx = 0, cfg_cnt = 0; idx < ARRAY_SIZE(pcfgs); idx++) {
u32 value;
if (!of_property_read_u32(np, pcfgs[idx].prop_cfg, &value))
cfg[cfg_cnt++] =
PINCFG_PACK(pcfgs[idx].cfg_type, value);
}/* create the config map entry */
map[*nmaps].data.configs.group_or_pin = gname;
map[*nmaps].data.configs.configs = cfg;
map[*nmaps].data.configs.num_configs = cfg_cnt;
map[*nmaps].type = PIN_MAP_TYPE_CONFIGS_GROUP;
*nmaps += 1;skip_cfgs:
/* create the function map entry */
if (of_find_property(np, "samsung,pin-function", NULL)) {
fname = kzalloc(strlen(np->name) + FSUFFIX_LEN, GFP_KERNEL);
if (!fname) {
dev_err(dev, "failed to alloc memory for func name\n");
goto free_cfg;
}
sprintf(fname, "%s%s", np->name, FUNCTION_SUFFIX);map[*nmaps].data.mux.group = gname;
map[*nmaps].data.mux.function = fname;
map[*nmaps].type = PIN_MAP_TYPE_MUX_GROUP;
*nmaps += 1;
}*maps = map;
return 0;free_cfg:
kfree(cfg);
free_gname:
kfree(gname);
free_map:
kfree(map);
return -ENOMEM;
}
具体分析TODO
(5)samsung_dt_free_map
该函数的代码如下:
static void samsung_dt_free_map(struct pinctrl_dev *pctldev,
struct pinctrl_map *map, unsigned num_maps)
{
int idx;for (idx = 0; idx < num_maps; idx++) {
if (map[idx].type == PIN_MAP_TYPE_MUX_GROUP) {
kfree(map[idx].data.mux.function);
if (!idx)
kfree(map[idx].data.mux.group);
} else if (map->type == PIN_MAP_TYPE_CONFIGS_GROUP) {
kfree(map[idx].data.configs.configs);
if (!idx)
kfree(map[idx].data.configs.group_or_pin);
}
};kfree(map);
}
具体分析TODO
3、复用引脚相关的操作函数struct pinmux_ops的具体解释如下:
(1)samsung_get_functions_count
该函数的代码如下:
static int samsung_get_functions_count(struct pinctrl_dev *pctldev)
{
struct samsung_pinctrl_drv_data *drvdata;drvdata = pinctrl_dev_get_drvdata(pctldev);
return drvdata->nr_functions;
}
该函数的主要功能是就是返回pin controller device支持的function的数目
(2)samsung_pinmux_get_fname
该函数的代码如下:
static const char *samsung_pinmux_get_fname(struct pinctrl_dev *pctldev,
unsigned selector)
{
struct samsung_pinctrl_drv_data *drvdata;drvdata = pinctrl_dev_get_drvdata(pctldev);
return drvdata->pmx_functions[selector].name;
}
该函数的主要功能是就是:给定一个function selector(index),获取指定function的name
(3)samsung_pinmux_get_groups
该函数的代码如下:
static int samsung_pinmux_get_groups(struct pinctrl_dev *pctldev,
unsigned selector, const char * const **groups,
unsigned * const num_groups)
{
struct samsung_pinctrl_drv_data *drvdata;drvdata = pinctrl_dev_get_drvdata(pctldev);
*groups = drvdata->pmx_functions[selector].groups;
*num_groups = drvdata->pmx_functions[selector].num_groups;
return 0;
}
该函数的主要功能是就是:给定一个function selector(index),获取指定function的pin groups信息
(4)samsung_pinmux_enable和samsung_pinmux_disable
这个两个callback函数都是通过samsung_pinmux_setup实现,该函数的代码如下:
static void samsung_pinmux_setup(struct pinctrl_dev *pctldev, unsigned selector,
unsigned group, bool enable)
{
struct samsung_pinctrl_drv_data *drvdata;
const unsigned int *pins;
struct samsung_pin_bank *bank;
void __iomem *reg;
u32 mask, shift, data, pin_offset, cnt;
unsigned long flags;drvdata = pinctrl_dev_get_drvdata(pctldev);
pins = drvdata->pin_groups[group].pins;/*
* for each pin in the pin group selected, program the correspoding pin
* pin function number in the config register.
*/
for (cnt = 0; cnt < drvdata->pin_groups[group].num_pins; cnt++) {
struct samsung_pin_bank_type *type;pin_to_reg_bank(drvdata, pins[cnt] - drvdata->ctrl->base,
, &pin_offset, &bank);
type = bank->type;
mask = (1 << type->fld_width[PINCFG_TYPE_FUNC]) - 1;
shift = pin_offset * type->fld_width[PINCFG_TYPE_FUNC];
if (shift >= 32) {
/* Some banks have two config registers */
shift -= 32;
reg += 4;
}spin_lock_irqsave(&bank->slock, flags);
data = readl(reg + type->reg_offset[PINCFG_TYPE_FUNC]);
data &= ~(mask << shift);
if (enable)
data |= drvdata->pin_groups[group].func << shift;
writel(data, reg + type->reg_offset[PINCFG_TYPE_FUNC]);spin_unlock_irqrestore(&bank->slock, flags);
}
}
该函数主要用来enable一个指定function。具体指定function的时候要给出function selector和pin group的selector 。具体的操作涉及很多底层的寄存器操作(TODO)。
(5)samsung_pinmux_gpio_set_direction
该函数的代码如下:
static int samsung_pinmux_gpio_set_direction(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range, unsigned offset, bool input)
{
struct samsung_pin_bank_type *type;
struct samsung_pin_bank *bank;
struct samsung_pinctrl_drv_data *drvdata;
void __iomem *reg;
u32 data, pin_offset, mask, shift;
unsigned long flags;bank = gc_to_pin_bank(range->gc);
type = bank->type;
drvdata = pinctrl_dev_get_drvdata(pctldev);pin_offset = offset - bank->pin_base;
reg = drvdata->virt_base + bank->pctl_offset +
type->reg_offset[PINCFG_TYPE_FUNC];mask = (1 << type->fld_width[PINCFG_TYPE_FUNC]) - 1;
shift = pin_offset * type->fld_width[PINCFG_TYPE_FUNC];
if (shift >= 32) {
/* Some banks have two config registers */
shift -= 32;
reg += 4;
}spin_lock_irqsave(&bank->slock, flags);
data = readl(reg);
data &= ~(mask << shift);
if (!input)
data |= FUNC_OUTPUT << shift;
writel(data, reg);spin_unlock_irqrestore(&bank->slock, flags);
return 0;
}
该函数用来设定GPIO的方向。
4、配置引脚的特性的struct pinconf_ops数据结构的各个成员定义如下:
(1)samsung_pinconf_get
(2)samsung_pinconf_set
(3)samsung_pinconf_group_get
(4)samsung_pinconf_group_set
(1)和(2)是对单个pin的配置进行读取或者设定,(3)和(4)是对pin group中的所有pin进行配置进行读取或者设定。这些函数的底层都是samsung_pinconf_rw,该函数代码如下:
static int samsung_pinconf_rw(struct pinctrl_dev *pctldev, unsigned int pin,
unsigned long *config, bool set)
{
struct samsung_pinctrl_drv_data *drvdata;
struct samsung_pin_bank_type *type;
struct samsung_pin_bank *bank;
void __iomem *reg_base;
enum pincfg_type cfg_type = PINCFG_UNPACK_TYPE(*config);
u32 data, width, pin_offset, mask, shift;
u32 cfg_value, cfg_reg;
unsigned long flags;drvdata = pinctrl_dev_get_drvdata(pctldev);
pin_to_reg_bank(drvdata, pin - drvdata->ctrl->base, ?_base,
&pin_offset, &bank);
type = bank->type;if (cfg_type >= PINCFG_TYPE_NUM || !type->fld_width[cfg_type])
return -EINVAL;width = type->fld_width[cfg_type];
cfg_reg = type->reg_offset[cfg_type];spin_lock_irqsave(&bank->slock, flags);
mask = (1 << width) - 1;
shift = pin_offset * width;
data = readl(reg_base + cfg_reg);if (set) {
cfg_value = PINCFG_UNPACK_VALUE(*config);
data &= ~(mask << shift);
data |= (cfg_value << shift);
writel(data, reg_base + cfg_reg);
} else {
data >>= shift;
data &= mask;
*config = PINCFG_PACK(cfg_type, data);
}spin_unlock_irqrestore(&bank->slock, flags);
return 0;
}