LinuxGPIO子系统4

LinuxGPIO子系统4(基于Linux6.6)---pinctrl驱动介绍

 


一、 概述

单纯地从pin controller driver的角度(屏蔽掉pinctrl core的实现细节),理解pinctrl subsystem的设计思想,并掌握pinctrl驱动的移植和实现方法。

移植和实现 pinctrl 驱动(引脚控制驱动)是 Linux 内核中一种常见的硬件平台适配任务。pinctrl 是一个用于控制和管理芯片引脚的内核子系统,它允许开发者对不同硬件平台的引脚进行配置、复用(设置引脚的功能)、电气特性配置(如上拉、下拉、浮空等)等。

移植 pinctrl 驱动的基本步骤包括:

  1. 设备树配置(Device Tree Configuration)
  2. 内核配置和驱动支持
  3. 平台代码实现
  4. 驱动编写和适配

以下是移植和实现 pinctrl 驱动的具体步骤。

二、pin controller的概念和软件抽象

Kernel pinctrl core使用struct pinctrl_desc抽象一个pin controller,该结构的定义如下:

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

2.1 Pin

kernel的pin controller子系统要想管理好系统的pin资源,第一个要搞明白的问题就是:系统中到底有多少个pin?用软件语言来表述就是:要把系统中所有的pin描述出来,并建立索引。这由上面struct pinctrl_desc结构中pins和npins来完成。

对pinctrl core来说,它只关心系统中有多少个pin,并使用自然数为这些pin编号,后续的操作,都是以这些编号为操作对象。至于编号怎样和具体的pin对应上,完全是pinctrl driver自己的事情。

因此,pinctrl driver需要根据实际情况,将系统中所有的pin组织成一个struct pinctrl_pin_desc类型的数组,该类型的定义为:

 include/linux/pinctrl/pinctrl.h


/**
* struct pinctrl_pin_desc - boards/machines provide information on their
  * pins, pads or other muxable units in this struct
  * @number: unique pin number from the global pin number space
  * @name: a name for this pin
  * @drv_data: driver-defined per-pin data. pinctrl core does not touch this
  */
struct pinctrl_pin_desc {
        unsigned number;
        const char *name;
        void *drv_data;
};

2.2 Pin groups

在SoC系统中,有时需要将很多pin组合在一起,以实现特定的功能,例如SPI接口、I2C接口等。因此pin controller需要以group为单位,访问、控制多个pin,这就是pin groups。相应地,pin controller subsystem需要提供一些机制,来获取系统中到底有多少groups、每个groups包含哪些pins、等等。

因此,pinctrl core在struct pinctrl_ops中抽象出三个回调函数,用来获取pin groups相关信息,如下:

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

2.3. Pin configuration(对象是pin或者pin group)

pinctrl subsystem中的操作对象(pin or pin group)以及抽象方法。SoC中的管脚有些属性可以配置,例如上拉、下拉、高阻、驱动能力等。pinctrl subsystem使用pin configuration来封装这些功能,具体体现在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);
        int (*pin_config_dbg_parse_modify) (struct pinctrl_dev *pctldev,
                                            const char *arg,
                                            unsigned long *config);
        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);
}; 

 pin_config_get,获取指定pin(管脚的编号,由2.1中pin的注册信息获得)当前配置,保存在config指针中(配置的具体含义,只有pinctrl driver自己知道,下同)。

pin_config_set,设置指定pin的配置(可以同时配置多个config,具体意义要由相应pinctrl driver解释)。

pin_config_group_get、pin_config_group_set,获取或者设置指定pin group的配置项。

2.4. Pin multiplexing(对象是pin或者pin group)

为了照顾不同类型的产品、不同的应用场景,SoC中的很多管脚可以配置为不同的功能,例如A2和B5两个管脚,既可以当作普通的GPIO使用,又可以配置为I2C0的的SCL和SDA,也可以配置为UART5的TX和RX,这称作管脚的复用(pin multiplexing,简称为pinmux)。kernel pinctrl subsystem使用struct pinmux_ops来抽象pinmux有关的操作,如下:

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

get_functions_count,获取系统中function的个数。

get_function_name,获取指定function的名称。

get_function_groups,获取指定function所占用的pin group(可以有多个)。

set_mux,将指定的pin group(group_selector)设置为指定的function(func_selector)。

request,检查某个pin是否已作它用,用于管脚复用时的互斥(避免多个功能同时使用某个pin而不知道,导致奇怪的错误)。

free,request的反操作。

gpio_request_enable、gpio_disable_free、gpio_set_direction,gpio有关的操作,等到gpio有关的文章中再说明。

strict,为true时,说明该pin controller不允许某个pin作为gpio和其它功能同时使用。

三、 pinctrl subsystem的控制逻辑

以struct pinctrl_desc为引子,介绍了pinctrl subsystem中有关pin controller的概念抽象,包括pin、pin group、pinconf、pinmux、pinmux function、等等,相当于从provider的角度理解pinctrl subsystem。那么,问题来了,怎么使用pinctrl subsystem提供的功能控制管脚的配置以及功能复用呢?这看似需要由consumer(例如各个外设的驱动)自行处理,实际上却不尽然:

3.1 pin state

根据前面的描述,pinctrl driver抽象出来了一些离散的对象:pin(pin group)、function、configuration,并实现了这些对象的控制和配置方式。回到某一个具体的device上(如SPI2):

该device在某一状态下(如工作状态、休眠状态、等等),所使用的pin(pin group)、pin(pin group)的function和configuration,是唯一确定的。

这个状态在pinctrl subsystem中就称作pin state。而pinctrl driver和具体板型有关的部分,需要负责枚举该板型下所有device(当然,特指那些需要pin资源的device)的所有可能的状态,并详细定义这些状态需要使用的pin(或pin group),以及这些pin(或pin group)需要配置为哪种function、哪种配置项。这些状态确定之后,consumer(device driver)就好办了,直接发号施令就行了:

pinctrl subsystem接收到指令后,找到该state的相关信息(pin、function和configuration),并调用pinctrl driver提供的相应API,控制pin controller即可。

3.2 pin map

在pinctrl subsystem中,pin state有关的信息是通过pin map收集,相关的数据结构如下:

include/linux/pinctrl/machine.h 

struct pinctrl_map {
        const char *dev_name;
        const char *name;
        enum pinctrl_map_type type;
        const char *ctrl_dev_name;
        union {
                struct pinctrl_map_mux mux;
                 struct pinctrl_map_configs configs;
        } data;
};

dev_name,device的名称。

name,pin state的名称。

ctrl_dev_name,pin controller device的名字。

type,该map的类型,包括PIN_MAP_TYPE_MUX_GROUP(配置管脚复用)、PIN_MAP_TYPE_CONFIGS_PIN(配置pin)、PIN_MAP_TYPE_CONFIGS_GROUP(配置pin group)、PIN_MAP_TYPE_DUMMY_STATE(不需要任何配置,仅仅为了表示state的存在。

data,该map需要用到的数据项,是一个联合体,如果map的类型是PIN_MAP_TYPE_CONFIGS_GROUP,则为struct pinctrl_map_mux类型的变量;如果map的类型是PIN_MAP_TYPE_CONFIGS_PIN或者PIN_MAP_TYPE_CONFIGS_GROUP,则为struct pinctrl_map_configs类型的变量。

struct pinctrl_map_mux的定义如下:

 include/linux/pinctrl/machine.h


struct pinctrl_map_mux {
         const char *group;
        const char *function;
};

struct pinctrl_map_configs的定义如下:group,group的名字,指明该map所涉及的pin group。

function,function的名字,表示该map需要将group配置为哪种function。

include/linux/pinctrl/machine.h


struct pinctrl_map_configs {
        const char *group_or_pin;
        unsigned long *configs;
        unsigned num_configs;
};    
static const struct pinctrl_map mapping[] __initconst = {
		{
			.dev_name = "foo-spi.0",
			.name = PINCTRL_STATE_DEFAULT,
			.type = PIN_MAP_TYPE_MUX_GROUP,
			.ctrl_dev_name = "pinctrl-foo",
			.data.mux.function = "spi0",
		},
		{
			.dev_name = "foo-i2c.0",
			.name = PINCTRL_STATE_DEFAULT,
			.type = PIN_MAP_TYPE_MUX_GROUP,
			.ctrl_dev_name = "pinctrl-foo",
			.data.mux.function = "i2c0",
		},
		{
			.dev_name = "foo-mmc.0",
			.name = PINCTRL_STATE_DEFAULT,
			.type = PIN_MAP_TYPE_MUX_GROUP,
			.ctrl_dev_name = "pinctrl-foo",
			.data.mux.function = "mmc0",
		},
	};

通过dts生成pin map这是一个mapping数组,包含4个map entry,定义了"foo-i2c.0"设备的一个pin state(PINCTRL_STATE_DEFAULT,"default"),该state由一个PIN_MAP_TYPE_MUX_GROUP entry、一个PIN_MAP_TYPE_CONFIGS_GROUP entry以及两个PIN_MAP_TYPE_CONFIGS_PIN entry组成。

至于dts中pin map描述的格式是什么,则完全由pinctrl driver自己决定,因为,最终的解析工作(dts to map)也是它自己做的。

四、pinctrl subsystem的整体流程

通过前面分析,对pinctrl subsystem有了一个比较全面的认识,这里以pinctrl整个使用流程为例,简单的总结一下。

1)pinctrl driver根据pin controller的实际情况,实现struct pinctrl_desc(包括pin/pin group的抽象,function的抽象,pinconf、pinmux的operation API实现,dt_node_to_map的实现,等等),并注册到kernel中。

2)pinctrl driver在pin controller的dts node中,根据自己定义的格式,描述每个device的所有pin state。大致的形式如下:

        pinctrl_xxx {                               /* the dts node for pin controller */
                ...
                xxx_state_xxx: xxx_xxx {  /* dts node for xxx device's "xxx state" */
                        xxx_pinmux {             /* pinmux entry */
                                xxx = xxxx;
                                xxx = xxxxxxx;
                                ...
                        };
                        xxx_pinconf {            /* pinconf entry */
                                xxx = xxxx;
                                xxx = xxxxxxx;
                                ...
                        };
                        xxx_pinconf {
                                xxx = xxxx;
                                xxx = xxxxxxx;
                                ...
                        };
                       ...
               };
               ...
        };

3)相应的consumer driver可以在自己的dts node中,引用pinctrl driver所定义的pin state,例如:

                   xxx_device: xxx@xxxxxxxx {
                        compatible = "xxx,xxxx";
                        ...
                        pinctrl-names = "default";
                        pinctrl-0 = <&xxx_state_xxx>;
                        ...
                };

4)consumer driver在需要的时候,可以调用pinctrl_get/devm_pinctrl_get接口,获得一个pinctrl handle(struct pinctrl类型的指针)。pinctrl subsystem在pinctrl get的过程中,解析consumer device的dts node,找到相应的pin state,进行调用pinctrl driver提供的dt_node_to_map API,解析pin state并转换为pin map。以driver probe时为例,调用过程如下:

5)consumer获得pinctrl handle之后,可以调用pinctrl subsystem提供的API(例如pinctrl_select_state),使自己的某个pin state生效。pinctrl subsystem进而调用pinctrl driver提供的各种回调函数,配置pin controller的硬件。

五、 举例应用

1. 设备树配置(Device Tree)

在 Linux 中,设备树(Device Tree)是描述硬件的一种数据结构,它帮助内核知道如何配置硬件资源,包括引脚复用设置。pinctrl 驱动的配置通常涉及设备树中的引脚控制部分。

设备树配置示例

pinctrl {
    uart0_pins: uart0-pins {
        rockchip,pins = <
            /* uart0_rx */
            0 RK_PC0 3 &pcfg_pull_up,
            /* uart0_tx */
            0 RK_PC1 3 &pcfg_pull_up
        >;
    };

    uart0_pins_default: uart0-pins-default {
        rockchip,pins = <
            /* uart0_rx */
            0 RK_PC0 3 &pcfg_pull_up,
            /* uart0_tx */
            0 RK_PC1 3 &pcfg_pull_up
        >;
    };
};

说明:

  • pinctrl:定义引脚控制相关的配置。
  • uart0_pins:指定了与 UART0 相关的引脚配置。
  • rockchip,pins:这是 Rockchip 特有的属性,表示要配置的引脚以及其功能。
  • pcfg_pull_up:指示为这些引脚启用上拉电阻。

通过设备树,pinctrl 子系统能够根据硬件需求动态设置引脚的复用功能。


2. 内核配置和驱动支持

在移植 pinctrl 驱动之前,首先需要确保内核启用了相关的引脚控制支持。这通常是在内核的 config 配置文件中进行设置。

启用 pinctrl 驱动的内核配置项:

CONFIG_PINCTRL=y
CONFIG_PINCTRL_GENERIC=y
CONFIG_PINCTRL_XYZ=y  # 针对特定硬件平台的 pinctrl 驱动
  • CONFIG_PINCTRL:启用 pinctrl 子系统支持。
  • CONFIG_PINCTRL_GENERIC:启用通用的引脚控制驱动,适用于大部分平台。
  • CONFIG_PINCTRL_XYZ:针对特定平台的引脚控制驱动,需要根据硬件平台调整此项配置。

如果你在移植特定硬件平台的 pinctrl 驱动,通常需要确保相关的硬件驱动也被启用。对于 Rockchip 平台,比如 CONFIG_PINCTRL_ROCKCHIP


3. 平台代码实现

移植 pinctrl 驱动时,需要在平台的驱动代码中实现引脚控制的操作。这部分代码通常位于硬件平台的驱动目录中,例如 drivers/pinctrl 下。

在平台代码中,通常要实现以下几个部分:

  1. 定义引脚控制资源: 在硬件平台的代码中,你需要定义引脚控制资源,包括引脚配置、引脚复用功能等。通常通过 pinctrl_map 表格来描述这些资源。

  2. 编写引脚控制的操作函数: 包括引脚的初始化、配置、复用和电气特性的设置等。

例如,下面是一个 Rockchip 平台的引脚控制代码片段:

static const struct pinctrl_pin_desc rockchip_pins[] = {
    { .number = RK_PC0, .name = "uart0_rx", },
    { .number = RK_PC1, .name = "uart0_tx", },
};

static const struct pinctrl_ops rockchip_pinctrl_ops = {
    .get_groups_count = rockchip_get_groups_count,
    .get_group_name = rockchip_get_group_name,
    .set_mux = rockchip_set_mux,
    .get_mux = rockchip_get_mux,
};

static int rockchip_pinctrl_probe(struct platform_device *pdev)
{
    struct pinctrl *pctrl;
    int ret;

    pctrl = devm_pinctrl_get(&pdev->dev);
    if (IS_ERR(pctrl))
        return PTR_ERR(pctrl);

    /* 配置引脚复用 */
    ret = pinctrl_select_state(pctrl, rockchip_pins_default);
    if (ret)
        return ret;

    return 0;
}
  • rockchip_pins:定义了 Rockchip 平台上与 UART 相关的引脚。
  • rockchip_pinctrl_ops:实现了引脚控制的操作,例如获取引脚组数量、设置引脚复用功能、获取引脚复用状态等。

4. 驱动编写和适配

pinctrl 驱动移植完成后,需要将它与实际的硬件平台和内核进行适配。这包括:

  • 引脚组和复用配置: 确保引脚复用表正确,适应不同硬件平台的需求。
  • 初始化和调试: 确保 pinctrl 驱动在硬件初始化过程中正确加载,配置引脚,并能够通过设备树正确读取配置。

驱动加载和设备树匹配

确保设备树中的 pinctrl 配置与驱动的实现相匹配。内核会根据设备树加载相应的引脚控制节点。

例如,Rockchip 平台的设备树文件可能包含如下配置:

dts

pinctrl {
    uart0_pins: uart0-pins {
        rockchip,pins = <
            /* uart0_rx */
            0 RK_PC0 3 &pcfg_pull_up,
            /* uart0_tx */
            0 RK_PC1 3 &pcfg_pull_up
        >;
    };
};

内核启动时会根据设备树中的配置调用相应的 pinctrl 驱动来初始化引脚。


总结

移植和实现 pinctrl 驱动通常包括以下几个步骤:

  1. 配置设备树,定义需要的引脚及其复用功能。
  2. 在内核配置中启用 pinctrl 子系统及相关驱动支持。
  3. 在平台驱动中实现 pinctrl 操作接口,并处理硬件平台的引脚配置。
  4. 通过设备树与硬件驱动进行适配,确保引脚配置正确无误。
### 回答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、付费专栏及课程。

余额充值