LinuxGPIO子系统2

LinuxGPIO子系统2(基于Linux6.6)---pin control subsystem介绍


一、概述

Linux Pin Control Subsystem 是 Linux 内核中用于管理和控制硬件引脚(GPIO引脚、复用引脚等)的一个子系统。它提供了一个统一的接口,使得开发者能够在不同的硬件平台上,灵活地配置和控制芯片的引脚功能。这个子系统不仅支持 GPIO 功能的配置,还可以控制其他功能如串行通信(UART、SPI、I2C)、PWM、复用功能等。

1.1、Pin Control Subsystem的主要目标

  • 硬件抽象:提供一个硬件抽象层(HAL),使得上层应用可以独立于底层硬件平台来访问引脚功能。
  • 引脚复用:支持引脚复用功能,允许将单个引脚配置为多个功能,如将同一个引脚用作 GPIO 或串行通信接口。
  • 配置灵活性:提供丰富的引脚配置选项,如上拉、下拉电阻、驱动能力、输入输出模式、输入电平等。
  • 节能:通过合适的引脚配置,减少不必要的电流消耗,实现节能。

1.2、Pin Control Subsystem 的组成

Pin Control Subsystem 主要由以下几个核心部分组成:

  1. Pin Configuration

    • Pinmux(引脚复用):指引脚的功能选择机制。不同的硬件平台往往会有多种不同的功能可以分配到同一个引脚上,Pinmux允许灵活地选择该引脚的具体功能。例如,一个引脚可能既能用作GPIO,也能用作SPI接口。
    • Pin功能(功能配置):每个引脚的具体功能,如上拉、下拉、开漏、驱动能力等。Pin功能配置决定了引脚在不同状态下的电气特性。
  2. Pin Control Drivers

    • 每个硬件平台(或 SoC)都有一个对应的 Pin Control 驱动程序,这些驱动程序管理和配置该平台上的引脚控制寄存器。
    • 驱动程序通常会包括设置引脚复用功能、配置引脚的电气特性(如上拉、下拉电阻,驱动能力等)。
  3. Pin Control API

    • 提供了一个统一的接口,供内核和用户空间应用程序访问和操作硬件引脚。通过这些 API,内核可以配置、控制和读取引脚的状态。
    • Pin Control API 为不同硬件平台提供了通用的操作接口,使得驱动程序可以抽象出不同硬件平台的差异,简化了引脚控制的复杂性。

 

二、pin control subsystem的软件框架图

2.1、功能和原理概述

主要功能和特性

  1. 引脚复用(Pin Multiplexing)

    • 在许多 SoC 上,同一个引脚可以被配置为不同的功能。例如,一个引脚可以在不同模式下作为 UART TX/RX 或 SPI MOSI/MISO,引脚复用允许硬件资源的高效利用。
  2. 引脚方向与电气特性配置

    • 输入/输出模式:配置引脚为输入或输出。
    • 电平配置:设置输入引脚的电平状态(如启用上拉电阻、下拉电阻)。
    • 驱动能力:设置输出引脚的驱动能力,如驱动电流、驱动电压等。
  3. 引脚中断支持

    • 在 Linux 中,Pin Control Subsystem 还可以支持引脚中断的管理。开发者可以通过配置相应的引脚,使其在特定的电平变化或边沿变化时触发中断,进而响应外部事件。
  4. 电源管理

    • Pin Control 子系统与电源管理紧密结合,可以根据需要启用或禁用某些引脚,或者将某些引脚配置为低功耗模式,从而降低系统的功耗。
  5. 平台支持

    • Linux 提供了对多个平台的支持,如 ARM、x86 等 SoC 系统,Pin Control 子系统在不同硬件平台之间抽象了引脚控制的差异,简化了跨平台的开发。

Pin Control 的工作原理

  1. 设备树(Device Tree)

    • 在基于 ARM 的嵌入式系统中,设备树(Device Tree)文件中包含了硬件平台的配置信息,其中包括了引脚复用和引脚电气特性配置。
    • Pin Control 子系统通过解析设备树中的配置,来设置相应硬件的引脚复用和电气特性。
  2. 驱动层

    • 通过设备树或平台代码,Pin Control 驱动程序会读取引脚配置,并将其写入硬件寄存器,来控制引脚的电气属性和复用功能。
    • 驱动层还负责处理引脚的状态管理,如是否启用中断、是否启用上拉下拉电阻等。

3.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必须有交互。

3.3、pin control subsystem内部block diagram

 

起始理解了接口部分内容,阅读和解析pin control subsystem的内部逻辑已经很简单。

四、pin control subsystem向其他driver提供的接口

4.1、概述

在 Linux 驱动开发中,普通驱动(如设备驱动)调用 Pin Control Subsystem 的主要目标是为了配置和管理硬件引脚(GPIO、复用引脚、输入输出模式等)的行为,以确保硬件与内核驱动之间的交互和功能正常。具体目标包括:

1. 引脚复用(Pin Multiplexing)

  • 目标:允许硬件引脚在不同功能之间进行灵活配置。许多 SoC 上的引脚可以用作多种功能,例如同时支持串行通信、SPI、GPIO等。驱动通过 Pin Control Subsystem 配置引脚的复用功能,使其可以在不同模式下工作。
  • 例子:一个引脚可以在不同时刻被配置为 UART 信号线或 SPI 信号线,这样硬件资源可以得到更高效的利用。

2. 引脚电气特性配置

  • 目标:配置引脚的电气特性,例如设置上拉、下拉电阻、输出驱动能力等,以满足硬件设计要求。
  • 例子:可以配置某个输入引脚启用上拉电阻,或者设置输出引脚的电流驱动能力,以保证外部电路的正常工作。

3. 引脚方向管理(Input/Output Configuration)

  • 目标:配置引脚为输入或输出模式。Pin Control Subsystem 提供了接口来动态切换引脚的方向,使其能够在不同的工作场景下以不同方式进行工作。
  • 例子:驱动可以将引脚配置为输出模式,用于控制外部设备(如 LED),或者配置为输入模式,用于读取外部信号。

4. 引脚中断配置

  • 目标:支持引脚中断功能。通过 Pin Control Subsystem,驱动可以配置某些引脚在特定电平变化或边沿触发时产生中断,以响应外部事件。
  • 例子:配置一个引脚为 GPIO 输入,当检测到外部信号发生电平变化时,触发中断并执行相应的处理。

5. 电源管理

  • 目标:通过配置引脚的状态(如禁用不必要的功能或将某些引脚设置为低功耗模式)来节省功耗。
  • 例子:在不需要某些引脚时,可以通过 Pin Control Subsystem 将这些引脚配置为低功耗模式,减少系统的功耗。

6. 跨平台硬件抽象

  • 目标:为不同平台(如不同的 SoC)提供统一的接口,使得驱动能够在不同硬件平台上复用相同的代码。
  • 例子:使用统一的 Pin Control API,驱动可以在 ARM、x86 等不同平台上配置引脚,而不需要关心底层硬件的细节。

7. 配置平台特定功能

  • 目标:根据硬件平台的需要,配置特定的引脚功能,例如某些平台可能需要特定的引脚来控制外部设备或接口(如音频、显示、I2C 总线等)。
  • 例子:一个驱动可以调用 Pin Control 子系统配置特定的引脚功能,使其与外部设备(如显示屏、摄像头等)兼容。

普通driver调用pin control subsystem的接口从逻辑上将主要是:

(1)获取pin control state holder的句柄。

(2)设定pin control状态。

(3)释放pin control state holder的句柄。

pin control state holder的定义如下:

drivers/pinctrl/core.h 

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的定义如下:

drivers/pinctrl/core.h 

struct pinctrl_state {
    struct list_head node;---挂入链表头的节点
    const char *name;-----该state的名字
    struct list_head settings;---属于该状态的所有的settings
};

一个pin state包含若干个setting,所有的settings被挂入一个链表中,链表头就是pin state中的settings成员,定义如下:

drivers/pinctrl/core.h 

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有各种类型,定义如下:

 include/linux/pinctrl/machine.h

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),定义如下:

 drivers/pinctrl/core.h

struct pinctrl_setting_mux {
    unsigned group;--------该setting所对应的group selector
    unsigned func;---------该setting所对应的function selector
};

有了function selector以及属于该functiong的roup selector就可以进行该device和pin mux相关的设定了。设定电气特性的settings定义如下:

 include/linux/pinctrl/machine.h

struct pinctrl_map_configs {
    const char *group_or_pin;----该pin或者pin group的名字
    unsigned long *configs;----要设定的值的列表。这个值被用来写入HW
    unsigned num_configs;----列表中值的个数
};

4.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是一个大工程,分析一下这段代码:

 drivers/pinctrl/core.c

static struct pinctrl *create_pinctrl(struct device *dev,
				      struct pinctrl_dev *pctldev)
{
	struct pinctrl *p;
	const char *devname;
	struct pinctrl_maps *maps_node;
	int i;
	const struct pinctrl_map *map;
	int ret;

   分配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中

    }
		if (ret == -EPROBE_DEFER) {
			pinctrl_free(p, false);
			mutex_unlock(&pinctrl_maps_mutex);
			return ERR_PTR(ret);
		}
	}
	mutex_unlock(&pinctrl_maps_mutex);

	if (ret < 0) {
		/* If some other error than deferral occurred, return here */
		pinctrl_free(p, false);
		return ERR_PTR(ret);
	}

	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文件中:

include/linux/pinctrl/pinctrl-state.h 

#define PINCTRL_STATE_DEFAULT "default"
#define PINCTRL_STATE_INIT "init"
#define PINCTRL_STATE_IDLE "idle"
#define PINCTRL_STATE_SLEEP "sleep"

(4)pinctrl_select_state。设定一个具体的pin control state接口。

五、和GPIO subsystem交互

5.1、为何pin control subsystem要和GPIO subsystem交互?

期望的硬件设计应该如下图所示:

GPIO的HW block应该和其他功能复用的block是对等关系的,它们共同输入到一个复用器block,这个block的寄存器控制哪一个功能电路目前是active的。pin configuration是全局的,不论哪种功能是active的,都可以针对pin进行电气特性的设定。这样的架构下,上图中红色边框的三个block是完全独立的HW block,其控制寄存器在SOC datasheet中应该是分成三个章节描述,同时,这些block的寄存器应该分别处于不同的地址区间。

然而实际上SOC的设计并非总是向软件工程师期望的那样,有的SOC的设计框架图如下:

这时候,GPIO block是alway active的,而红色边框的三个block是紧密的捆绑在一起,它们的寄存器占据了一个memory range。硬件寄存器的控制都是pin controller来处理,GPIO相关的操作都要经过pin controller driver,这时候,pin controller driver要作为GPIO driver的back-end出现。

5.2、具体的接口形态

(1)pinctrl_request_gpio。该接口主要用来申请GPIO。GPIO也是一种资源,使用前应该request,使用完毕后释放。具体的代码如下:

drivers/pinctrl/core.c 

int pinctrl_gpio_request(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使用)。多么丑陋的代码,作为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初始化的时候,会调用pinctrl_add_gpio_range将每个GPIO bank表示的gpio range挂入到pin control device的range list中(gpio_ranges成员)。pinctrl_gpio_range 的定义如下:

include/linux/pinctrl/pinctrl.h

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有两种映射关系,一种是线性映射,也就是说,对于这个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_gpio_free。有申请就有释放,这是pinctrl_gpio_request的逆函数。

drivers/pinctrl/core.c

void pinctrl_gpio_free(unsigned gpio)
{
	struct pinctrl_dev *pctldev;
	struct pinctrl_gpio_range *range;
	int ret;
	int pin;

	ret = pinctrl_get_device_gpio_range(gpio, &pctldev, &range);
	if (ret) {
		return;
	}
	mutex_lock(&pctldev->mutex);

	/* Convert to the pin controllers number space */
	pin = gpio_to_pin(range, gpio);

	pinmux_free_gpio(pctldev, pin, range);

	mutex_unlock(&pctldev->mutex);
}
EXPORT_SYMBOL_GPL(pinctrl_gpio_free);

(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函数如下:

 drivers/base/dd.c

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的代码如下:

drivers/base/pinctrl.c 

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相关的接口

7.1、概述

device tree或者machine driver这两个模块主要是为 pin control subsystem提供pin mapping database的支持。这个database的每个entry用下面的数据结构表示:

include/linux/pinctrl/machine.h 

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;
};

7.2、通过machine driver静态定义的数据来建立pin mapping database

machine driver定义一个巨大的mapping table,描述,然后在machine初始化的时候,调用pinctrl_register_mappings将该table注册到pin control subsystem中。

7.3、通过device tree来建立pin mapping database

pin mapping信息定义在dts中,主要包括两个部分,一个是定义在各个具体的device node中,另外一处是定义在pin controller的device node中。

一个典型的device tree中的外设node定义如下

device-node-name { 
        定义该device自己的属性  

        pinctrl-names = "sleep", "default";
        pinctrl-0 = ;
        pinctrl-1 = ;        
    };

对普通device的dts分析在函数pinctrl_dt_to_map中,代码如下:

drivers/pinctrl/devicetree.c 

int pinctrl_dt_to_map(struct pinctrl *p, struct pinctrl_dev *pctldev)
{
	struct device_node *np = p->dev->of_node;
	int state, ret;
	char *propname;
	struct property *prop;
	const char *statename;
	const __be32 *list;
	int size, config;
	phandle phandle;
	struct device_node *np_config;

	/* CONFIG_OF enabled, p->dev not instantiated from DT */
	if (!np) {
		if (of_have_populated_dt())
			dev_dbg(p->dev,
				"no of_node; not parsing pinctrl DT\n");
		return 0;
	}

	/* We may store pointers to property names within the node */
    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的代码如下:

drivers/pinctrl/devicetree.c 

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来抽象,具体如下:

include/linux/pinctrl/pinctrl.h 

struct pinctrl_desc {
	const char *name;
	const struct pinctrl_pin_desc *pins;
	unsigned int npins;
	const struct pinctrl_ops *pctlops;
	const struct pinmux_ops *pmxops;
	const struct pinconf_ops *confops;
	struct module *owner;
#ifdef CONFIG_GENERIC_PINCONF
	unsigned int num_custom_params;
	const struct pinconf_generic_params *custom_params;
	const struct pin_config_item *custom_conf_items;
#endif
	bool link_consumers;
};

pin controller描述符需要描述它可以控制多少个pin(成员npins),每一个pin的信息为何?(成员pins)。这两个成员就确定了一个pin controller所能控制的引脚的信息。

pin controller描述符中包括了三类操作函数:pctlops是一些全局的控制函数,pmxops是复用引脚相关的操作函数,confops操作函数是用来配置引脚的特性(例如:pull-up/down)。struct pinctrl_ops中各个callback函数的具体的解释如下:

include/linux/pinctrl/pinctrl.h

struct pinctrl_ops {
	int (*get_groups_count) (struct pinctrl_dev *pctldev);
	const char *(*get_group_name) (struct pinctrl_dev *pctldev,
				       unsigned selector);
	int (*get_group_pins) (struct pinctrl_dev *pctldev,
			       unsigned selector,
			       const unsigned **pins,
			       unsigned *num_pins);
	void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,
			  unsigned offset);
	int (*dt_node_to_map) (struct pinctrl_dev *pctldev,
			       struct device_node *np_config,
			       struct pinctrl_map **map, unsigned *num_maps);
	void (*dt_free_map) (struct pinctrl_dev *pctldev,
			     struct pinctrl_map *map, unsigned num_maps);
};
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_showdebug fs的callback接口
dt_node_to_map分析一个pin configuration node并把分析的结果保存成mapping table entry,每一个entry表示一个setting(一个功能复用设定,或者电气特性设定)
dt_free_map上面函数的逆函数

复用引脚相关的操作函数的具体解释如下:

include/linux/pinctrl/pinmux.h

struct pinmux_ops {
	int (*request) (struct pinctrl_dev *pctldev, unsigned offset);
	int (*free) (struct pinctrl_dev *pctldev, unsigned offset);
	int (*get_functions_count) (struct pinctrl_dev *pctldev);
	const char *(*get_function_name) (struct pinctrl_dev *pctldev,
					  unsigned selector);
	int (*get_function_groups) (struct pinctrl_dev *pctldev,
				  unsigned selector,
				  const char * const **groups,
				  unsigned *num_groups);
	int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,
			unsigned group_selector);
	int (*gpio_request_enable) (struct pinctrl_dev *pctldev,
				    struct pinctrl_gpio_range *range,
				    unsigned offset);
	void (*gpio_disable_free) (struct pinctrl_dev *pctldev,
				   struct pinctrl_gpio_range *range,
				   unsigned offset);
	int (*gpio_set_direction) (struct pinctrl_dev *pctldev,
				   struct pinctrl_gpio_range *range,
				   unsigned offset,
				   bool input);
	bool strict;
};
call back函数描述
requestpin 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信息
enableenable一个function。当然要给出function selector和pin group的selector
disableenable的逆函数
gpio_request_enablerequest并且enable一个单独的gpio pin
gpio_disable_freegpio_request_enable的逆函数
gpio_set_direction设定GPIO方向的回调函数

配置引脚的特性的struct pinconf_ops数据结构的各个成员定义如下:

 include/linux/pinctrl/pinconf.h

struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
	bool is_generic;
#endif
	int (*pin_config_get) (struct pinctrl_dev *pctldev,
			       unsigned pin,
			       unsigned long *config);
	int (*pin_config_set) (struct pinctrl_dev *pctldev,
			       unsigned pin,
			       unsigned long *configs,
			       unsigned num_configs);
	int (*pin_config_group_get) (struct pinctrl_dev *pctldev,
				     unsigned selector,
				     unsigned long *config);
	int (*pin_config_group_set) (struct pinctrl_dev *pctldev,
				     unsigned selector,
				     unsigned long *configs,
				     unsigned num_configs);
	void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev,
				     struct seq_file *s,
				     unsigned offset);
	void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev,
					   struct seq_file *s,
					   unsigned selector);
	void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev,
					    struct seq_file *s,
					    unsigned long config);
};
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_modifydebug接口
pin_config_dbg_showdebug接口
pin_config_group_dbg_showdebug接口
pin_config_config_dbg_showdebug接口

### 回答1: Linux GPIO子系统是一个用于控制嵌入式系统中通用输入/输出(GPIO)的软件子系统。它提供了一种标准的接口,使得应用程序可以通过文件系统接口来访问GPIO。这个子系统可以用于控制各种设备,例如LED、按钮、传感器等等。如果你需要更多的信息,可以查看Linux内核文档。 ### 回答2: Linux GPIO子系统是一种用于管理通用输入输出(GPIO)引脚的软件层。GPIO引脚是一种通用可编程引脚,可以在嵌入式系统中用来通过读取输入或设置输出与外部设备进行通信。 Linux GPIO子系统负责将底层硬件 GPIO 引脚的操作抽象为文件系统的接口,使开发者可以通过读写文件的方式来访问和控制 GPIO 引脚。通过该子系统,可以实现对 GPIO 引脚的配置、读取和写入等操作,以满足不同应用下对 GPIO 的需求。 Linux GPIO子系统的核心是GPIO驱动程序,它与底层硬件层进行交互,完成对GPIO引脚的操作。驱动程序将GPIO引脚映射到内存,通过读写该内存地址即可对引脚进行操作。用户通过访问特定目录下的文件来和引脚进行交互,例如将引脚配置为输入模式、输出模式,以及读取或写入引脚的状态。 通过Linux GPIO子系统,开发者可以方便地进行GPIO引脚的控制。可以根据不同的应用需求,灵活配置引脚的输入输出模式,监听引脚上的状态变化,并根据需要对其他外设进行控制。 总之,Linux GPIO子系统为开发者提供了便捷的接口,使得在嵌入式系统中使用GPIO引脚更加简单和灵活。它允许开发者通过读写文件的方式访问和控制GPIO引脚,满足各种不同嵌入式应用对GPIO的需求。 ### 回答3: Linux的GPIO(General Purpose Input/Output)子系统是通过软件对硬件上的通用输入/输出引脚进行控制的一种机制。它使得开发者可以利用这些GPIO引脚实现各种功能,比如控制LED灯、读取外部传感器的数据等。 Linux的GPIO子系统提供了许多功能和接口来管理和操作GPIO。首先,它使用sysfs文件系统来组织GPIO资源的目录树,并通过文件的方式来读取和写入GPIO的状态。在/sys/class/gpio目录下,每个GPIO引脚都会有一个对应的目录,在该目录中的文件可以用于配置GPIO的方向(输入或输出)、读取和写入GPIO的电平状态。开发者可以使用命令行工具或者编程语言(如Python、C等)来操作这些文件,从而控制GPIO引脚的行为。 其次,Linux的GPIO子系统还提供了设备树(Device Tree)来描述硬件平台上的GPIO资源。设备树是一种描述硬件的数据结构,在启动时通过设备树绑定机制将设备树中定义的GPIO资源与内核驱动程序关联起来。这样,开发者就可以通过调用相应的驱动程序来控制GPIO引脚,而不需要手动操作sysfs文件系统。 此外,Linux的GPIO子系统还支持中断机制,可以让GPIO引脚在特定事件发生时触发中断。通过注册中断处理函数,开发者可以实现对GPIO输入信号的快速响应,提高系统的实时性。 总之,Linux的GPIO子系统为开发者提供了一种方便且灵活的方式来控制硬件上的GPIO引脚。通过sysfs文件系统或设备树,开发者可以轻松地配置、读取和控制GPIO的状态,从而实现各种功能和应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值