文章目录
- 1、前言
- 2、pinctrl子系统
- 3、pinctrl bsp驱动
- 4、gpio子系统
- 5、gpio bsp驱动
1、前言
- 学习参考书籍以及本文涉及的示例程序:李山文的《Linux驱动开发进阶》
- 本文属于个人学习后的总结,不太具备教学功能。
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(分为两部分):
- 描述、获得单个引脚的信息
- 描述、获得某组引脚的信息
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示例程序。