嵌入式工程师总是要处理各种各样的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的模块图:
pinctrl
底层的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的模块图:
gpio
基本上这个软件框架图和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接口 |
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向其他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接口。
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 ID
ret = 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
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信息注册到系统中
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接口 |
pinctrl主要功能包括:
(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 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 = ;--------------(2)
pinctrl-1 = ;
};
(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引出。
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 device
const 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设备来抽象对应的声卡设备。
我们再看看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 bank
u32 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;// --------A
bank = 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_registe
r函数就是把各个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的核心函数,可以参考上文中的描述。
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类型的中断控制器代码分析。
具体的代码如下:
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。
(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+-mux
const char **groups;-----指向pin groups的指针数组
u8 num_groups;------属于该function的pin group的个数
};
在具体的代码实现中num_groups总是等于1。
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;
}
具体分析TODO