目录
在前面的文章中学习了pinctrl子系统的相关概念和使用方法,对于驱动工程师来说已经够用了,但是如果作为BSP工程师,就需要了解pinctrl是怎么实现的。
🚀主要数据结构
首先回顾一下pinctrl子系统的三大作用,有助于理解所涉及的数据结构:
- 引脚枚举与命名;
- 引脚功能复用;
- 引脚配置。
🚢pin controller的数据结构
🚚struct pinctrl_dev
pin controller
虽然是一个软件的概念,但是它背后是有硬件支持的,所以把它可以看作是一个具有pin controller
功能的硬件设备,在内核中使用一个结构体把它抽象出来,这个结构体就是pinctrl_dev
,其原型如下:
/* drivers/pinctrl/core.h */
struct pinctrl_dev {
struct list_head node; /* 用于构建双向链表,将此pinctrl_dev添加到全局链表pinctrldev_list */
struct pinctrl_desc *desc; /* pin controller描述符,构造pinctrl_dev时使用 */
struct radix_tree_root pin_desc_tree; /* 存储此pincontroller下每个pin对应的pin_desc的基数树,在注册pin时,会为每个pin分配一个struct pin_desc结构体,并将其添加到该基数树上,其中键为pin的编号; */
#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
struct radix_tree_root pin_group_tree; /* 可选,每个引脚组都可以存储在此基数树中 */
unsigned int num_groups; /* 可选,可以在此处保存组数 */
#endif
#ifdef CONFIG_GENERIC_PINMUX_FUNCTIONS
struct radix_tree_root pin_function_tree; /* 可选,每个函数都可以存储在此基数树中 */
unsigned int num_functions; /* 可选,在此处可以保存函数数量 */
#endif
struct list_head gpio_ranges; /* 此 pin controller处理的 GPIO 范围列表,范围在运行时添加到此列表中 */
struct device *dev; /* pin controller的父设备;一般设置为平台设备的dev成员 */
struct module *owner; /* 提供 pin controller的模块,用于引用计数 */
void *driver_data; /* 驱动程序的私有数据 */
struct pinctrl *p; /* pinctrl_get(dev) 结果 */
struct pinctrl_state *hog_default; /* 此设备占用的引脚的默认状态 */
struct pinctrl_state *hog_sleep; /* 此设备占用的引脚的睡眠状态 */
struct mutex mutex; /* 在执行每个 pin controller特定操作时采取的互斥锁 */
#ifdef CONFIG_DEBUG_FS
struct dentry *device_root; /* 此设备的 debugfs 根目录 */
#endif
};
那么,怎么构造出pinctrl_dev
呢?我们只需要描述它,提供一个pinctrl_desc
,然后调用函数pinctrl_register
就可以。
🚚struct pinctrl_desc
在pinctrl
子系统核心层,使用pinctrl_desc
来描述一个pin controller
,也就是pinctrl_dev
。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
};
在文章开头,回顾了pinctrl
子系统的三大作用,分别是引脚枚举与命名、引脚功能复用和引脚特性配置。所以,在结构体pinctrl_desc
中也主要描述这三个方面。
1. 描述、获得引脚(引脚枚举与命名)
- struct pinctrl_pin_desc:
在pinctrl_desc
中,使用结构体pinctrl_pin_desc
来描述一个引脚,原型如下:struct pinctrl_pin_desc { unsigned number; //引脚ID,在全局引脚编号空间中唯一的引脚编号。 const char *name; //这个引脚的名称 void *drv_data; //驱动程序定义的每个引脚的私有数据,pinctrl核心层不会修改此数据 };
- struct pinctrl_ops
在pinctrl_desc
中,使用pinctrl_ops
来操作引脚,主要功能有二:- 来取出某组的引脚:get_groups_count、get_group_pins;
- 处理设备树中pin controller中的某个节点:dt_node_to_map,把device_node(pin controller的子节点)转换为一系列的pinctrl_map。
struct pinctrl_ops { /* 返回注册的pin group总数 */ int (*get_groups_count) (struct pinctrl_dev *pctldev); /* 返回指定的分组名称 */ const char *(*get_group_name) (struct pinctrl_dev *pctldev, unsigned selector); /* 返回与特定pin group选择器@pins对应的引脚数组,并将数组的大小放入@num_pins 中 */ int (*get_group_pins) (struct pinctrl_dev *pctldev, unsigned selector, const unsigned **pins, unsigned *num_pins); /* 可选的 debugfs 显示钩子,提供特定引脚的每个设备的信息 */ 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); /* 释放通过@dt_node_to_map创建的映射表条目 */ void (*dt_free_map) (struct pinctrl_dev *pctldev, struct pinctrl_map *map, unsigned num_maps); };
2. 引脚复用
- struct pinmux_ops
struct pinmux_ops { /* 由核心调用,以查看特定引脚是否可以用于引脚复用 */ int (*request) (struct pinctrl_dev *pctldev, unsigned offset); /* 与request() 回调的相反函数,在申请后释放引脚 */ int (*free) (struct pinctrl_dev *pctldev, unsigned offset); /* 返回pin controller device支持的function的数目 */ int (*get_functions_count) (struct pinctrl_dev *pctldev); /* 给定一个function selector(index),获取指定function的pin groups信息 */ const char *(*get_function_name) (struct pinctrl_dev *pctldev, unsigned selector); /* 给定一个function selector(index),获取指定function的pin groups信息 */ 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); /* 在某个引脚上请求并启用GPIO。仅在可以将每个引脚单独复用为GPIO时实现 */ int (*gpio_request_enable) (struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset); /* 在某个引脚上释放GPIO复用,即 @gpio_request_enable 的相反操作 */ void (*gpio_disable_free) (struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset); /* 由于控制器可能需要根据GPIO配置为输入或输出而需要不同的配置,因此可以实现方向选择器函数作为需要引脚复用的GPIO控制器的支持。 */ int (*gpio_set_direction) (struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset, bool input); bool strict; /* 不允许同时使用相同的引脚用于GPIO和其他函数, 在批准引脚请求之前,应严格检查 gpio_owner 和 mux_owner */ };
3. 引脚配置
- struct pinconf_ops
在构造好一个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); };
pinctrl_desc
结构体变量后,再调用devm_pinctrl_register
或pinctrl_register
,就可以根据pinctrl_desc
构造出pinctrl_dev
,并且把pinctrl_dev
放入链表,函数devm_pinctrl_register
的调用过程如下:devm_pinctrl_register pinctrl_register struct pinctrl_dev *pctldev; pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL); pctldev->owner = pctldesc->owner; pctldev->desc = pctldesc; pctldev->driver_data = driver_data; /* check core ops for sanity */ ret = pinctrl_check_ops(pctldev); /* If we're implementing pinmuxing, check the ops for sanity */ ret = pinmux_check_ops(pctldev); /* If we're implementing pinconfig, check the ops for sanity */ ret = pinconf_check_ops(pctldev); /* Register all the pins */ ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins); list_add_tail(&pctldev->node, &pinctrldev_list);
🚚数据结构体关系图
🚢client的数据结构
在设备树中,一个典型的外设节点(client device
)定义如下:
device{
pinctrl-names = "active", "idle";
pinctrl-0 = <&state_0_node_a>;
pinctrl-1 = <&state_0_node_a &state_1_node_b>;
};
在内核启动阶段,设备树节点要么被转换为platform_device
,或者其他结构体(比如i2c_client
),但是里面都会有一个device
结构体,如下:
🚚struct device
struct device
结构体原型如下:
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
unsigned long dma_pfn_offset;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
bool offline_disabled:1;
bool offline:1;
};
🚚struct dev_pin_info
在每个struct device
里都有一个dev_pin_info
结构体,用来保存设备的引脚状态信息,其中dev_pin_info
原型如下:
/* include/linux/devinfo.h */
struct dev_pin_info {
struct pinctrl *p; /* 指向pinctrl句柄的指针,用于保存该设备的所有状态信息 */
struct pinctrl_state *default_state; /* 指向默认状态的指针,表示该设备上引脚的默认状态 */
struct pinctrl_state *init_state; /* 指向探测时状态的指针,表示探测期间初始化引脚的状态 */
#ifdef CONFIG_PM
struct pinctrl_state *sleep_state; /* 如果在系统的电源管理模块(Power Management,简称 PM)中启用了与设备关联的引脚状态保持功能,则指向睡眠时状态的指针,表示在设备进入睡眠状态时退出的状态 */
struct pinctrl_state *idle_state; /* 如果在 PM 模块中启用了运行时挂起功能,并且与设备关联的引脚状态与 idling 状态有关,则指向 idling 时状态的指针 */
#endif
🚚struct pinctrl
在dev_pin_info
结构体中,有一个指向struct pinctrl
结构体的指针,其中,结构体struct pinctrl
持有一个client device
的所有引脚状态,其原型如下:
/**
* struct pinctrl - 该结构体持有一个client device的所有引脚状态
* @node: 全局链表,系统中的所有struct pinctrl被添加到了全局链表pinctrl_list中;
* @dev: 指向该struct pinctrl对应的client device
* @states: 该client device的所有状态被添加到这个链表中
* @state: client device的当前状态
* @dt_maps: 双向链表头,用于保存从设备树动态解析出来的映射信息;
* @users: 引用计数
*/
struct pinctrl {
struct list_head node;
struct device *dev;
struct list_head states;
struct pinctrl_state *state;
struct list_head dt_maps;
struct kref users;
};
🚚struct pinctrl_state
在dev_pin_info
结构体中,还有一些struct pinctrl_state
类型的成员,用来表示client device
一些常用的状态。struct pinctrl_state
的原型如下:
/**
* struct pinctrl_state - a pinctrl state for a device
* @node: 用于构建双向链表,client device的状态被添加到pinctrl的states链表中;
* @name: 此状态的名字
* @settings: 双向链表头,保存属于该状态的所有的settings;由于一个状态可以对应多个settings,所以这里使用链表来表示;
*/
struct pinctrl_state {
struct list_head node;
const char *name;
struct list_head settings;
};
🚚struct pinctrl_map和pinctrl_setting
- struct pinctrl_map
struct pinctrl_map
它用于描述client device
的映射配置,使用pin controller device
的pinctrl_desc->ops->dt_node_to_map
来处理设备树中的"引脚配置节点"。
例如pinctrl-0 = <&uart0_xfer &uart0_cts &uart0_rts>
,uart0_xfer
、uart0_cts
、uart0_rts
节点均会被dt_node_to_map
函数解析为一系列的pinctrl_map
,并以数组指针形式返回。
struct pinctrl_map
的原型如下:
/**
* struct pinctrl_map - boards/machines shall provide this map for devices
* @dev_name: 设备名称,需要与该client device对应的struct device中的名称一致;
* @name: 状态名称,是传递给pinmux_lookup_state()函数的参数;
* @type: 该映射项的类型
* @ctrl_dev_name: 设备名称,与该client device对应的struct device中的名称一致;
* @data: 该映射项的具体数据,包括mux和configs两种类型,分别描述复用选择和引脚配置。
*/
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;
};
- struct pinctrl_setting
一个状态包含若干个setting,所有的settings被挂入一个链表中,链表头就是struct pinctrl_setting的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; // mux配置数据
struct pinctrl_setting_configs configs; // config配置数据
} data;
};
🚚数据结构体关系图
🚢使用pinctrl_setting
really_probe
pinctrl_bind_pins
pinctrl_select_state
/* Apply all the settings for the new state */
list_for_each_entry(setting, &state->settings, node) {
switch (setting->type) {
case PIN_MAP_TYPE_MUX_GROUP:
ret = pinmux_enable_setting(setting);
ret = ops->set_mux(...);
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
case PIN_MAP_TYPE_CONFIGS_GROUP:
ret = pinconf_apply_setting(setting);
ret = ops->pin_config_group_set(...);
break;
default:
ret = -EINVAL;
break;
}