【Linux驱动】pinctrl子系统主要数据结构


在前面的文章中学习了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_devpinctrl_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_registerpinctrl_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 devicepinctrl_desc->ops->dt_node_to_map来处理设备树中的"引脚配置节点"。
    例如 pinctrl-0 = <&uart0_xfer &uart0_cts &uart0_rts>uart0_xferuart0_ctsuart0_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;
			}		

在这里插入图片描述

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值