Linux驱动开发进阶(八)- GPIO子系统BSP驱动

发布于:2025-04-20 ⋅ 阅读:(80) ⋅ 点赞:(0)

1、前言

  1. 学习参考书籍以及本文涉及的示例程序:李山文的《Linux驱动开发进阶》
  2. 本文属于个人学习后的总结,不太具备教学功能。

2、pinctrl子系统

在讨论gpio子系统时,一般要带上pinctrl子系统。pinctrl子系统管理着io的功能复用,gpio子系统依赖于pinctrl子系统。所以先讨论pinctrl子系统。

先看设备树,下面是瑞芯微平台的pinctrl控制器的设备树节点:

&pinctrl {
    compatible = "rockchip,rk3568-pinctrl";
    xl9535:xl9535 {
        rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>;
    };
};

下面是全志平台的pinctrl控制器的设备树节点:

&pio {
    compatible = "allwinner,sun8iw20-pinctrl";
    csi_mclk0_pins_a: csi_mclk0@0 {
        pins = "PE3";
        function = "csi0";
        drive-strength = <10>;
    };
}

上面展示了两个不同平台的pinctrl控制器设备树节点。我们不关心他们配置了什么内容,但值得注意的是,他们用于表示IO复用的属性是不一样的。这也说明了这方面是高度自由的,通常都由厂家来定义。

Linux内核使用一个pinctrl_desc结构体来描述pinctrl控制器:

struct pinctrl_desc {
	const char *name;	// pinctrl的名称
	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; // 用于指示是否将使用该pinctrl控制器的设备链接到该控制器
};

先看引脚描述结构体:

struct pinctrl_pin_desc {
	unsigned number;	// GPIO引脚的编号
	const char *name;	// GPIO引脚名称
	void *drv_data;		// 私有数据
};

再看pinctrl_ops结构体:

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

该结构体的函数都要实现。get_groups_count用于获取IO分组,get_group_name用于获取IO分组名称,get_group_pins用于获取IO引脚资源,pin_dbg_show用于打印调试信息,dt_node_to_map用于从设备树获取设备节点然后映射(这个函数挺重要的),dt_free_map用于释放映射。

再看pinmux_ops结构体:

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

request和free函数指针用于请求和释放一个引脚,get_functions_count函数指针用于获取pin控制器中的function的个数,get_function_name函数指针用于获取指定function的名称,get_function_groups函数指针用于获取指定function所占用的引脚group,set_mux函数指针用于将指定的引脚group(group_selector)设置为指定的function。

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

pin_config_get用于获取被选中引脚的配置,pin_config_set用于配置被选中的引脚,pin_config_group_get用于获取被选中的引脚分组配置,pin_config_group_set用于配置被选中的引脚分组,pin_cofig_dbg_show、pin_config_group_dbg_show用于调试信息。

3、pinctrl bsp驱动

pinctrl的3大作用:

作用1(分为两部分):

  1. 描述、获得单个引脚的信息
  2. 描述、获得某组引脚的信息

pinctrl_ops这个操作集合说是获取各组引脚的信息。但这个“组”到底体现了在哪?有些驱动里是把单个引脚归为一组,所以因此产生了名字和实现不匹配现象。

作用2:

用来把某组引脚(group)复用为某个功能(function)

作用3:

用来配置某个引脚(pin)或某组引脚(group)

关于bsp驱动示例程序可以参考韦东山的驱动大全或者李山文的《Linux驱动开发进阶》的pinctrl bsp示例程序

如下贴出韦东山的示例程序:


#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/slab.h>
#include <linux/regmap.h>

#include "core.h"


static struct pinctrl_dev *g_pinctrl_dev;

static const struct pinctrl_pin_desc pins[] = {
    {0, "pin0", NULL},
    {1, "pin1", NULL},
    {2, "pin2", NULL},
    {3, "pin3", NULL},
};

static unsigned long g_configs[4];

struct virtual_functions_desc {
    const char *func_name;
    const char **groups;
    int num_groups;
};


static const char *func0_grps[] = {"pin0", "pin1", "pin2", "pin3"};
static const char *func1_grps[] = {"pin0", "pin1"};
static const char *func2_grps[] = {"pin2", "pin3"};

static struct virtual_functions_desc g_funcs_des[] = {
    {"gpio", func0_grps, 4},
    {"i2c",  func1_grps, 2},
    {"uart", func2_grps, 2},
};


static const struct of_device_id virtual_pinctrl_of_match[] = {
    { .compatible = "100ask,virtual_pinctrl", },
    { },
};

static int virtual_get_groups_count(struct pinctrl_dev *pctldev)
{
    return pctldev->desc->npins;
}

static const char *virtual_get_group_name(struct pinctrl_dev *pctldev,
                                          unsigned selector)
{
    return pctldev->desc->pins[selector].name;
}

static int virtual_get_group_pins(struct pinctrl_dev *pctldev, unsigned selector,
                                  const unsigned **pins,
                                  unsigned *npins)
{
    if (selector >= pctldev->desc->npins)
        return -EINVAL;

    *pins = &pctldev->desc->pins[selector].number;
    *npins = 1;

    return 0;
}

static void virtual_pin_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s,
                                 unsigned offset)
{
    seq_printf(s, "%s", dev_name(pctldev->dev));
}

/*
 i2cgrp {
		 functions = "i2c", "i2c";
		 groups = "pin0", "pin1";
		 configs = <0x11223344	0x55667788>;
 };

 one pin ==> two pinctrl_map (one for mux, one for config)

 */
static int virtual_dt_node_to_map(struct pinctrl_dev *pctldev,
                                  struct device_node *np,
                                  struct pinctrl_map **map, unsigned *num_maps)
{
    int i;
    int num_pins = 0;
    const char *pin;
    const char *function;
    unsigned int config;
    struct pinctrl_map *new_map;
    unsigned long *configs;

    /* 1. 确定pin个数/分配pinctrl_map */
    while (1)
    {
        if (of_property_read_string_index(np, "groups", num_pins, &pin) == 0)
            num_pins++;
        else
            break;
    }

    new_map = kmalloc(sizeof(struct pinctrl_map) * num_pins * 2, GFP_KERNEL);


    for (i = 0; i < num_pins; i++)
    {
        /* 2. get pin/function/config */
        of_property_read_string_index(np, "groups", i, &pin);
        of_property_read_string_index(np, "functions", i, &function);
        of_property_read_u32_index(np, "configs", i, &config);


        /* 3. 存入pinctrl_map   */
        configs = kmalloc(sizeof(*configs), GFP_KERNEL);

        new_map[i*2].type = PIN_MAP_TYPE_MUX_GROUP;
        new_map[i*2].data.mux.function = function;
        new_map[i*2].data.mux.group = pin;

        new_map[i*2+1].type = PIN_MAP_TYPE_CONFIGS_PIN;
        new_map[i*2+1].data.configs.group_or_pin = pin;
 		new_map[i*2+1].data.configs.configs = configs;
		configs[0] = config;
		new_map[i*2+1].data.configs.num_configs = 1;

	}

	*map = new_map;
	*num_maps = num_pins * 2;

	return 0;
}
static void virtual_dt_free_map(struct pinctrl_dev *pctldev,
			 struct pinctrl_map *map, unsigned num_maps)
{
	while (num_maps--)
	{
		if (map->type == PIN_MAP_TYPE_CONFIGS_PIN)
			kfree(map->data.configs.configs);

		 kfree(map);
		 map++;
	}
}

static const struct pinctrl_ops virtual_pctrl_ops = {
	.get_groups_count = virtual_get_groups_count,
	.get_group_name = virtual_get_group_name,
	.get_group_pins = virtual_get_group_pins,
	.pin_dbg_show = virtual_pin_dbg_show,
	.dt_node_to_map = virtual_dt_node_to_map,
	.dt_free_map = virtual_dt_free_map,

};

static int virtual_pmx_get_funcs_count(struct pinctrl_dev *pctldev)
{
	return ARRAY_SIZE(g_funcs_des);
}

static const char *virtual_pmx_get_func_name(struct pinctrl_dev *pctldev,
					  unsigned selector)
{
	return g_funcs_des[selector].func_name;
}

static int virtual_pmx_get_groups(struct pinctrl_dev *pctldev, unsigned selector,
				 const char * const **groups,
				 unsigned * const num_groups)
{
  *groups = g_funcs_des[selector].groups;
  *num_groups = g_funcs_des[selector].num_groups;

  return 0;
}

static int virtual_pmx_set(struct pinctrl_dev *pctldev, unsigned selector,
			unsigned group)
{
	printk("set %s as %s\n", pctldev->desc->pins[group].name, g_funcs_des[selector].func_name);
	return 0;
}

static const struct pinmux_ops virtual_pmx_ops = {
	.get_functions_count = virtual_pmx_get_funcs_count,
	.get_function_name = virtual_pmx_get_func_name,
	.get_function_groups = virtual_pmx_get_groups,
	.set_mux = virtual_pmx_set,
};

static int virtual_pinconf_get(struct pinctrl_dev *pctldev,
			     unsigned pin_id, unsigned long *config)
{
	*config = g_configs[pin_id];
	return 0;
}

static int virtual_pinconf_set(struct pinctrl_dev *pctldev,
			  unsigned pin_id, unsigned long *configs,
			  unsigned num_configs)
{
	if (num_configs != 1)
		return -EINVAL;
	
	g_configs[pin_id] = *configs;
	
	printk("config %s as 0x%lx\n", pctldev->desc->pins[pin_id].name, *configs);
	
	return 0;
}

static void virtual_pinconf_dbg_show(struct pinctrl_dev *pctldev,
				 struct seq_file *s, unsigned pin_id)
{
	  seq_printf(s, "0x%lx", g_configs[pin_id]);
}

static void virtual_pinconf_group_dbg_show(struct pinctrl_dev *pctldev,
			  struct seq_file *s, unsigned pin_id)
{
   seq_printf(s, "0x%lx", g_configs[pin_id]);
}

static const struct pinconf_ops virtual_pinconf_ops = {
	.pin_config_get = virtual_pinconf_get,
	.pin_config_set = virtual_pinconf_set,
	.pin_config_dbg_show = virtual_pinconf_dbg_show,
	.pin_config_group_dbg_show = virtual_pinconf_group_dbg_show,
};

static int virtual_pinctrl_probe(struct platform_device *pdev)
{
	struct pinctrl_desc *pictrl;
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* a. 分配pinctrl_desc */
	pictrl = devm_kzalloc(&pdev->dev, sizeof(*pictrl), GFP_KERNEL);
	
	/* b. 设置pinctrl_desc */
	pictrl->name = dev_name(&pdev->dev);
	pictrl->owner = THIS_MODULE;
	
	/* b.1 pins and group */
	pictrl->pins = pins;
	pictrl->npins = ARRAY_SIZE(pins);

	pictrl->pctlops = &virtual_pctrl_ops;
	
	/* b.2 pin mux */
	pictrl->pmxops = &virtual_pmx_ops;
	
	/* b.3 pin config */
	pictrl->confops = &virtual_pinconf_ops;
	
	/* c. 注册pinctrl_desc */
	g_pinctrl_dev = devm_pinctrl_register(&pdev->dev, pictrl, NULL);
	
	return 0;
}
static int virtual_pinctrl_remove(struct platform_device *pdev)
{

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}


static struct platform_driver virtual_pinctrl_driver = {
	.probe		= virtual_pinctrl_probe,
	.remove		= virtual_pinctrl_remove,
	.driver		= {
		.name	= "100ask_virtual_pinctrl",
		.of_match_table = of_match_ptr(virtual_pinctrl_of_match),
	}
};


/* 1. 入口函数 */
static int __init virtual_pinctrl_init(void)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 1.1 注册一个platform_driver */
	return platform_driver_register(&virtual_pinctrl_driver);
}


/* 2. 出口函数 */
static void __exit virtual_pinctrl_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 2.1 反注册platform_driver */
	platform_driver_unregister(&virtual_pinctrl_driver);
}

module_init(virtual_pinctrl_init);
module_exit(virtual_pinctrl_exit);

MODULE_LICENSE("GPL");

重点看virtual_dt_node_to_map,该函数主要是将设备树pinctrl节点信息转成pinctrl_map结构体。同时,内核也提供了该功能的函数,叫pinconf_generic_dt_node_to_map_all,如果这样,设备树pinctrl节点属性就得按照一定的格式来写。

4、gpio子系统

pinctrl子系统的主要作用是管理引脚的功能,即复用功能。而gpio子系统则是控制gpio功能的io口的高低电平。gpio子系统依赖于pinctrl子系统的实现,因此,在加载gpio bsp驱动时,也需要加载pinctrl的bsp驱动,这样gpio bsp驱动才能正常工作。

5、gpio bsp驱动

linux内核抽象出了一个结构体,叫gpio_chip,用来控制IO的电平及获取IO的电平:

注册一个gpio bsp驱动调用如下函数即可:

该函数将gpio_chip注册到内核中,这样用户在调用gpiod_xxx接口时就可以正常工作了。

示例程序可以参考李山文的《Linux驱动开发进阶》的gpio bsp示例程序


网站公告

今日签到

点亮在社区的每一天
去签到