Linux虚拟Pinctrl Demo驱动-- Demo Code

Posted ZHONGCAI0901

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux虚拟Pinctrl Demo驱动-- Demo Code相关的知识,希望对你有一定的参考价值。

1. 前言

为了更进一步了解Linux的Pinctrl子系统,这里编写一个虚拟的Pinctrl Demo驱动。下面是编写的内容文件列表:

下面是在加载驱动virt_pinctrl_client.kovirt_pinctrl_driver.ko驱动后的运行效果:


下面主要是分析virt_pinctrl_driver.cvirt_pinctrl_client.c只是驱动模板什么都没有做。之所以需要virt_pinctrl_client.c驱动,是在设备树中引用了vt_pinctrl@12340000里面的vt_pinctrl_i2c2。驱动加载probe时,就调用Pinctrl子系统。

2. Device Tree的配置

修改arch/arm/boot/dts/imx6ull-14x14-evk.dts文件,增加定义的虚拟Pinctrl设备配置信息如下:

#include "imx6ull-benjamin-pinfun.h"

virt_pinctrl: vt_pinctrl@12340000 
	compatible = "imx,virt_pinctrl";
	reg = <0x12340000 0x4000>;
	
	vt_pinctrl_i2c1: vt_i2c1grp 
		virt,pins = <
			VIRT_PINCTRL_PAD_PIN2__I2C1_SCL
			VIRT_PINCTRL_PAD_PIN3__I2C1_SDA
		>;
	;
	
	vt_pinctrl_i2c2: vt_i2c2grp 
		virt,pins = <
			VIRT_PINCTRL_PAD_PIN6__I2C2_SCL
			VIRT_PINCTRL_PAD_PIN7__I2C2_SDA
		>;
	;
	
	vt_pinctrl_uart1: vt_uart1grp 
		virt,pins = <
			VIRT_PINCTRL_PAD_PIN12__UART2_DCE_TX
			VIRT_PINCTRL_PAD_PIN13__UART2_DCE_RX
			VIRT_PINCTRL_PAD_PIN14__UART2_DCE_RTS
			VIRT_PINCTRL_PAD_PIN15__UART2_DCE_CTS
		>;
	;
;

virtual_i2c 
	compatible = "imx,virtual_i2c";
	pinctrl-names = "default";
	pinctrl-0 = <&vt_pinctrl_i2c2>;
;

在dts的文件目录arch/arm/boot/dts/imx6ull-benjamin-pinfun.h添加该文件,内容如下:

#ifndef __DTS_IMX6ULL_BENJAMIN_PINFUNC_H
#define __DTS_IMX6ULL_BENJAMIN_PINFUNC_H

//      Pin Name                                            <PIN Number> <Register Addr> <Mux Mode> <Pin Config>
#define VIRT_PINCTRL_PAD_PIN2__I2C1_SCL                     0x02         0x12340008      0x01       0x32567890
#define VIRT_PINCTRL_PAD_PIN3__I2C1_SDA                     0x03         0x1234000C      0x01       0x32567890
#define VIRT_PINCTRL_PAD_PIN6__I2C2_SCL                     0x06         0x12340018      0x01       0x32567890
#define VIRT_PINCTRL_PAD_PIN7__I2C2_SDA                     0x07         0x1234001C      0x01       0x32567890

#define VIRT_PINCTRL_PAD_PIN12__UART2_DCE_TX                0x0C         0x12340030      0x02       0x32567890
#define VIRT_PINCTRL_PAD_PIN13__UART2_DCE_RX                0x0D         0x12340034      0x02       0x32567890
#define VIRT_PINCTRL_PAD_PIN14__UART2_DCE_RTS               0x0E         0x12340038      0x02       0x32567890
#define VIRT_PINCTRL_PAD_PIN15__UART2_DCE_CTS               0x0F         0x1234003C      0x02       0x32567890

#endif // __DTS_IMX6ULL_BENJAMIN_PINFUNC_H

3. virt_pinctrl_probe

virt_pinctrl_probe主要是申请struct pinctrl_desc,然后初始化后注册到devm_pinctrl_register。通过probe后,他们之间的架构大致如下。下面图片所示是imx6ull的架构,虚拟pinctrl也是类似的。

static int virt_pinctrl_probe(struct platform_device *ofdev)

    int ret;
	struct virt_pinctrl *ipctl;
    struct pinctrl_desc *virt_pinctrl_desc = NULL;

	/* Create state holders etc for this driver */
	ipctl = devm_kzalloc(&ofdev->dev, sizeof(*ipctl), GFP_KERNEL);
	if (!ipctl)
		return -ENOMEM;

    /* 申请Pin配置的空间,保存所有Pin的配置值,模拟寄存器。*/
    ipctl->configs = devm_kmalloc_array(&ofdev->dev, ARRAY_SIZE(virt_pinctrl_pads), 
                        sizeof(*ipctl->configs), GFP_KERNEL);

    memset(ipctl->configs, 0x00, ARRAY_SIZE(virt_pinctrl_pads)*sizeof(*ipctl->configs));

	virt_pinctrl_desc = devm_kzalloc(&ofdev->dev, sizeof(*virt_pinctrl_desc), GFP_KERNEL);
	if (!virt_pinctrl_desc)
		return -ENOMEM;

	virt_pinctrl_desc->name = dev_name(&ofdev->dev);
	virt_pinctrl_desc->pins = virt_pinctrl_pads; // 虚拟Pinctrl该有16个pin,里面包含了pin name和pin number
	virt_pinctrl_desc->npins = ARRAY_SIZE(virt_pinctrl_pads); // 虚拟Pinctrl中pin的个数,16。
	virt_pinctrl_desc->pctlops = &virt_pctrl_ops;  // 全局的pinctrl操作,主要是dt_node_to_map将设备树pin control config保存到map table,和通过pin_group_tree list获取struct group_desc,从而得到所有的group name和group count。
	virt_pinctrl_desc->pmxops = &virt_pmx_ops; // 主要是设置Pin的复用模式,也就是它到底工作在什么模式,如:GPIO、SPI或者I2C等其中之一。
	virt_pinctrl_desc->confops = &virt_pinconf_ops; // 主要是配置Pin的电气特性,如:pull-up、pull-down和open-drain等。
	virt_pinctrl_desc->owner = THIS_MODULE;

    ipctl->dev = &ofdev->dev;
    platform_set_drvdata(ofdev, ipctl); // 将struct virt_pinctrl设置到driver data,为后续其它函数调用可以获取到。
    ipctl->pctl = devm_pinctrl_register(&ofdev->dev, virt_pinctrl_desc, ipctl); // 注册pinctrl
    
    ret = virt_pinctrl_probe_dt(ofdev, ipctl); // 解析device tree
	if (ret) 
		dev_err(&ofdev->dev, "fail to probe dt properties\\n");
		return ret;
	

    return 0;

4. virt_pinctrl_probe_dt

virt_pinctrl_probe_dt主要是解析device tree,将解析出来的数据保存到struct radix_tree_root pin_group_treestruct radix_tree_root pin_function_tree。这里的function可以理解为board的,这里只有一个board的配置。这是参考imx6ull的架构来的。

static int virt_pinctrl_parse_functions(struct platform_device *pdev, struct virt_pinctrl *ipctl)

    int i = 0;
    int num_group_names;
    const char **group_names;
    struct device_node *child;
    struct device_node *np = pdev->dev.of_node;
    struct pinctrl_dev *pctl = ipctl->pctl;

	/* 解析vt_pinctrl@12340000节点下面有多少个子节点,这里是3个。 */
	num_group_names = of_get_child_count(np);
	if (num_group_names == 0) 
		dev_err(ipctl->dev, "no groups defined in %pOF\\n", np);
		return -EINVAL;
	
	
	/* 为每一个group都申请一个指针,它指向子节点的name,也就是group name。 */
	group_names = devm_kcalloc(ipctl->dev, num_group_names, sizeof(char *), GFP_KERNEL);
	if (!group_names)
		return -ENOMEM;

	/* 遍历vt_pinctrl@12340000的每个子节点,也就是每个group。 */
	for_each_child_of_node(np, child) 
		group_names[i] = child->name; // group_names指向子节点的name,也就是group name。
		i++;
	

	/* 将这个function(board)解析出来所有的group_names保存到struct radix_tree_root pin_group_tree中。 */
    pinmux_generic_add_function(pctl, np->name, group_names, num_group_names, NULL);

    return 0;


static int virt_pinctrl_parse_groups(struct platform_device *pdev, struct virt_pinctrl *ipctl)

    int i;
    int size, pin_size;
    int *grp_pins;
    int grp_num_pins;
    void *grp_data;
    const __be32 *list;
    struct virt_pin *pin;
    struct device_node *child;
    struct device_node *np = pdev->dev.of_node;
    struct pinctrl_dev *pctl = ipctl->pctl;

    pin_size = VIRT_PIN_SIZE; // VIRT_PIN_SIZE = 4 * sizeof(uint32_t) = 16

	/* 解析vt_pinctrl@12340000节点下面有多少个子节点,这里是3个。 */
	for_each_child_of_node(np, child) 
	    /* 获取当前group数据 */
		list = of_get_property(child, "virt,pins", &size);
		if (!list) 
			dev_err(ipctl->dev,
				"no virt,pins and pins property in node %pOF\\n", child);
			return -EINVAL;
		

        /* we do not check return since it's safe node passed down */
        if (!size || size % pin_size) 
            dev_err(ipctl->dev, "Invalid virt,pins or pins property in node %pOF\\n", child);
            return -EINVAL;
        

        /* 给当前group中的每个pin申请空间 */
        grp_num_pins = size / pin_size;
        grp_data = devm_kcalloc(ipctl->dev, grp_num_pins, sizeof(struct virt_pin), GFP_KERNEL);
        grp_pins = devm_kcalloc(ipctl->dev, grp_num_pins, sizeof(unsigned int), GFP_KERNEL);
        if (!grp_pins || !grp_data)
            return -ENOMEM;

        /* 将设备树中group pin的数据解析并赋值给grp_data和grp_pins */
        for (i = 0; i < grp_num_pins; i++) 
            pin = &((struct virt_pin *)(grp_data))[i];
            virt_pinctrl_parse_pin_config(ipctl, &grp_pins[i], pin, &list, child);
        

		/* 将每个group的pin数据保存到struct radix_tree_root pin_function_tree中。 */
        pinctrl_generic_add_group(pctl, child->name, grp_pins, grp_num_pins, grp_data);
	

    return 0;


static int virt_pinctrl_probe_dt(struct platform_device *pdev, struct virt_pinctrl *ipctl)

    int ret = 0;

	/* 解析device tree中的function数据 */
    ret = virt_pinctrl_parse_functions(pdev, ipctl);
    /* 解析device tree中的group数据 */
    ret = virt_pinctrl_parse_groups(pdev, ipctl);
    
    return ret;

5. virt_dt_node_to_map

virt_dt_node_to_map主要是解析device tree pinctrl中的group node,将每个group信息保存到struct pinctrl_map中。下面截图是加载驱动时打印的log:

解析出来的架构大致如下:

static int virt_dt_node_to_map(struct pinctrl_dev *pctldev,
           struct device_node *np,
           struct pinctrl_map **map, unsigned *num_maps)
   
    int i, j;
    int map_num = 1;    
	struct virt_pin *pin;
    struct device_node *parent;
	struct pinctrl_map *new_map;
    const struct group_desc *grp;
    struct virt_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);

	/* np->name = vt_i2c2grp,通过name在pin_group_tree中查找对应的struct group_desc */
	grp = virt_pinctrl_find_group_by_name(pctldev, np->name);
	if (!grp) 
		dev_err(ipctl->dev, "unable to find group for node %pOFn\\n", np);
		return -EINVAL;
	

    map_num += grp->num_pins; // 获取vt_i2c2grp的pin个数
	new_map = kmalloc_array(map_num, sizeof(struct pinctrl_map), GFP_KERNEL); // 为每个pin分配一个struct pinctrl_map空间
	if (!new_map)
		return -ENOMEM;

	*map = new_map; // 将分配的地址传递回去
	*num_maps = map_num; // 将该group的pin个数传递回去

	/* 初始化mux map */
	parent = of_get_parent(np);
	if (!parent) 
		kfree(new_map);
		return -EINVAL;
	
	new_map[0].type = PIN_MAP_TYPE_MUX_GROUP;
	new_map[0].data.mux.function = parent->name;
	new_map[0].data.mux.group = np->name;
	of_node_put(parent);

	/* 初始化config map */
	new_map++;
	for (i = j = 0; i < grp->num_pins; i++) 
		pin = &((struct virt_pin *)(grp->data))[i];

		new_map[j].type = PIN_MAP_TYPE_CONFIGS_PIN;
		new_map[j].data.configs.group_or_pin = pin_get_name(pctldev, pin->pin);
		new_map[j].data.configs.configs = (unsigned long *)&pin->config;
		new_map[j].data.configs.num_configs = 1;

		j++;
	

	printk("maps: function %s group %s num %d\\n",
		(*map)->data.mux.function, (*map)->data.mux.group, map_num);

    return 0;


static const struct pinctrl_ops virt_pctrl_ops = 
	.get_groups_count = pinctrl_generic_get_group_count,
	.get_group_name = pinctrl_generic_get_group_name,
	.get_group_pins = pinctrl_generic_get_group_pins,
	.pin_dbg_show = virt_pin_dbg_show,
	.dt_node_to_map = virt_dt_node_to_map,
	.dt_free_map = virt_dt_free_map,
;

运行如下命令后,可以查看加载到内核虚拟的pinctrl-maps:
cat /sys/kernel/debug/pinctrl/pinctrl-maps

6. virt_pmx_set

virt_pmx_set主要是将该group对应的每个pin设置到对应复用模式,下面截图是加载驱动时打印的log:

static int virt_pmx_set(struct pinctrl_dev *pctldev, unsigned selector,
		       unsigned group)

	struct virt_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
	struct function_desc *func;
	struct group_desc *grp;
	struct virt_pin *pin;
	unsigned int npins;
	int i;

	/*
	 * Configure the mux mode for each pin in the group for a specific
	 * function.
	 */
	grp = pinctrl_generic_get_group(pctldev, group);
	if (!grp)
		return -EINVAL;

	func = pinmux_generic_get_function(pctldev, selector);
	if (!func)
		return -EINVAL;

	npins = grp->num_pins;

	printk("enable function %s group %s\\n", func->name, grp->name);

	for (i = 0; i < npins; i++) 
		pin = &((struct virt_pin *)(grp->data))[i];
		/**
		 * 设置Pin的配置到具体的寄存器。
		 * 因为是虚拟Pinctrl,所以在这里只是打印,忽略具体操作。 
		 */
		printk("%s() Pin[%02d] = 0x%X, 0x%X, 0x%X\\n", __func__, pin->pin, pin->config.reg_addr, pin->config.mux_mode, pin->config.pin_conf);
        ipctl->configs[pin->pin].reg_addr = pin->config.reg_addr; // 保存Pin的寄存器地址
		ipctl->configs[pin->pin].mux_mode = pin->config.mux_mode; // 模拟写入Mux复用寄存器,将Pin设置到对应的模式。
	

	return 0;


struct pinmux_ops virt_pmx_ops = 
	.get_functions_count = pinmux_generic_get_function_count,
	.get_function_name = pinmux_generic_get_function_name,
	.get_function_groups = pinmux_generic_get_function_groups,
	.set_mux = virt_pmx_set,
;

7. virt_pinconf_set

virt_pmx_set主要是将对应的pin设置到对应config的电气特性,如:pull-up、pull-down和open-drain等,下面截图是加载驱动时打印的log:

static int virt_pinconf_set(struct pinctrl_dev *pctldev,
              unsigned pin_id, unsigned long *configs,
              unsigned num_configs)

    struct virt_pin_config *config = (struct virt_pin_config *)configs;
    struct virt_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);

    // Note:num_configs在这里为1,virt_dt_node_to_map函数里面每个Pin都只有一个Config

    ipctl->configs[pin_id].reg_addr = config->reg_addr; // 保存Pin的寄存器地址
    ipctl->configs[pin_id].pin_conf = config->pin_conf; // 模拟写入Config配置寄存器,将Pin设置到对应的配置。

    printk("%s() Pin[%02d] = 0x%X, 0x%X, 0x%X\\n", __func__, 
        pin_id, ipctl->configs[pin_id].reg_addr, ipctl->configs[pin_id].mux_mode, ipctl->configs[pin_id].pin_conf);

    return 0;


static const struct pinconf_ops virt_pinconf_ops = 
	.pin_config_set = virt_pinconf_set,
	.pin_config_dbg_show = virt_pinconf_dbg_show,
	.pin_config_group_dbg_show = virt_pinconf_group_dbg_show,
;

8. 烧录验证

加载编译成的驱动KO,打印log如下:

9. 工程代码下载地址

完整的实验工程Demo代码下载地址如下:
https://download.csdn.net/download/ZHONGCAI0901/86734001

以上是关于Linux虚拟Pinctrl Demo驱动-- Demo Code的主要内容,如果未能解决你的问题,请参考以下文章

Linux虚拟Pinctrl Demo驱动

Linux虚拟Pinctrl Demo驱动 -- Debug FS之Pinctrl分析

Linux虚拟Pinctrl Demo驱动 -- Debug FS之Pinctrl分析

Linux虚拟Pinctrl Demo驱动 -- Debug FS之Pinctrl分析

07_编写虚拟的Pinctrl驱动程序

08_调试虚拟的Pinctrl驱动程序