Pinctrl子系统主要有三大作用,分别是引脚枚举与命名(获取单个引脚或引脚组、设备树解析等)、引脚复用功能配置(比如用作GPIO、I2C或其他功能)、引脚电气特性配置(比如上拉、下拉、open drain、驱动强度等)
驱动代码编写
实验程序是一个虚拟的pinctrl控制器,它一共有6个引脚,每个引脚分别有gpio、af0~af15、analog、reserved等复用功能,在程序中将这6个引脚分别作为一个引脚组(即每个组一个引脚),另外程序还用到了/drivers/pinctrl目录下core.h文件,因为此文件未包含在内核编译头文件路径中,这里采用将此头文件复制到虚拟驱动实在目录方案。
设备树编写
设备树包括两个部分,分别是虚拟pinctr控制器l的节点和使用pinctrl的虚拟设备节点
//在顶层设备树的跟节点中加入如下内容
//虚拟pinctrl的节点
virtual_pinctrl {
compatible = "atk,virtual_pinctrl";
//描述设备引脚的节点,节点内容通过dt_node_to_map解析到pinctrl_map中
pinctrl_i2c: i2cgrp {
functions = "af1", "af5";
groups = "pin0", "pin1";
configs = <0x1122 0x3344>;
};
};
//使用pinctrl的虚拟设备
virtual_i2c {
compatible = "atk,virtual_i2c";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c>;
};
//用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树
//用新的.dtb文件启动系统
驱动代码编写
编写一个pinctrl驱动主要包括以下步骤
- 分配一个pinctrl_desc
- 设置pinctrl_desc的名字等参数
- 实现对单个引脚的描述,主要实现pinctrl_desc中pins和npins部分内容
- 实现引脚组获取、设备树解析的功能,主要实现pinctrl_desc中pctlops部分内容
- 实现引脚电气特性配置相关功能,主要实现pinctrl_desc中confops部分内容
- 实现引脚复用功能配置相关功能,主要实现pinctrl_desc中pmxops部分内容
- 使用pinctrl_desc在系统分配并注册pinctrl_dev
#include <linux/clk.h>
#include <linux/gpio/driver.h>
#include <linux/hwspinlock.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinconf-generic.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include "core.h"
/* 引脚支持的复用功能列表 */
static const char * const pin_functions[] = {
"gpio", "af0", "af1",
"af2", "af3", "af4",
"af5", "af6", "af7",
"af8", "af9", "af10",
"af11", "af12", "af13",
"af14", "af15", "analog",
"reserved",
};
/* 引脚列表 */
static const struct pinctrl_pin_desc pins_desc[] = {
{0, "pin0", NULL},
{1, "pin1", NULL},
{2, "pin2", NULL},
{3, "pin3", NULL},
{4, "pin4", NULL},
{5, "pin5", NULL},
};
/* 引脚组,将每个引脚单独作为一个组 */
const char *pin_groups[] = {pins_desc[0].name, pins_desc[1].name, pins_desc[2].name, pins_desc[3].name, pins_desc[4].name, pins_desc[5].name};
/* 缓存引脚电气特性配置 */
unsigned long pin_configs[ARRAY_SIZE(pins_desc)] = {0};
/* 缓存引脚复用功能配置 */
unsigned long pin_mux[ARRAY_SIZE(pins_desc)] = {0};
/* 获取总共多少组,将一个引脚作为一个组 */
static int virtual_get_groups_count(struct pinctrl_dev *pctldev)
{
return ARRAY_SIZE(pins_desc);
}
/* 获取每某一组的名称,组名与引脚名相同 */
static const char *virtual_get_group_name(struct pinctrl_dev *pctldev, unsigned group)
{
if (group >= ARRAY_SIZE(pins_desc))
return NULL;
//每个引脚都作为一个组
return pins_desc[group].name;
}
/* 获取某一组的所有引脚 */
static int virtual_get_group_pins(struct pinctrl_dev *pctldev, unsigned group, const unsigned **pins, unsigned *npins)
{
if (group >= ARRAY_SIZE(pins_desc))
return -EINVAL;
//每个引脚都作为一个组
*pins = &pins_desc[group].number;
*npins = 1;
return 0;
}
/* 解析设备树内容为pinctrl_map列表 */
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_groups;
int num_functions;
u32 config;
char *buffer;
const char *group;
const char *function;
unsigned long *configs;
struct pinctrl_map *pctl_map;
/* 获取设备树中引脚组数组的长度,即此节点描述了多少个引脚 */
for(num_groups=0; ; num_groups++)
{
if(of_property_read_string_index(np, "groups", num_groups, &group) < 0)
break;
}
/* 获取设备树中复用功能组数组的长度 */
for(num_functions=0; ; num_functions++)
{
if(of_property_read_string_index(np, "functions", num_functions, &function) < 0)
break;
}
/* 引脚数数组长度和复用功能数组长度必须相等,即每个引脚都对应一个复用功能 */
if((num_groups != num_functions) || (num_groups == 0))
{
printk("device tree error\r\n");
return -EINVAL;
}
/* 分配pinctrl_map和以及存储配置参数的内存,每个(组)引脚2个pinctrl_map加4byte配置字(分别用于存储配置复用功能配置和电气特性配置),pinctrl_map位于前面部分空间 */
buffer = kmalloc((sizeof(struct pinctrl_map) * num_groups * 2) + (num_groups * sizeof(unsigned long)), GFP_KERNEL);
if(!buffer) {
printk("alloc pinctrl_map failed\r\n");
return -ENOMEM;
}
/* 偏移到pinctrl_map地址 */
pctl_map = (struct pinctrl_map*)buffer;
/* 偏移到配置字地址 */
configs = (unsigned long*)(buffer + sizeof(struct pinctrl_map) * num_groups * 2);
for(i=0; i<num_groups; i++)
{
//获取引脚组名称
of_property_read_string_index(np, "groups", i, &group);
//获取引脚组的复用功能
of_property_read_string_index(np, "functions", i, &function);
//获取引脚组的配置参数
if(of_property_read_u32_index(np, "configs", i, &config) == 0)
configs[i] = config;
else
configs[i] = 0x9988;
//复用功能配置的pctl_map
pctl_map[i*2].type = PIN_MAP_TYPE_MUX_GROUP;
pctl_map[i*2].data.mux.function = function;
pctl_map[i*2].data.mux.group = group;
//电气特性配置的pctl_map
pctl_map[i*2+1].type = PIN_MAP_TYPE_CONFIGS_GROUP;
pctl_map[i*2+1].data.configs.group_or_pin = group;
pctl_map[i*2+1].data.configs.num_configs = 1;
pctl_map[i*2+1].data.configs.configs = &configs[i];
}
//pctl_map地址和数量
*map = pctl_map;
*num_maps = num_groups * 2;
return 0;
}
/* 释放virtual_dt_node_to_map中分配的资源 */
static void virtual_dt_free_map(struct pinctrl_dev *pctldev, struct pinctrl_map *map, unsigned num_maps)
{
kfree(map);
}
/* 获取引脚组的引脚和处理设备树操的作函数集合 */
static const struct pinctrl_ops virtual_ops = {
.get_groups_count = virtual_get_groups_count,
.get_group_name = virtual_get_group_name,
.get_group_pins = virtual_get_group_pins,
.dt_node_to_map = virtual_dt_node_to_map,
.dt_free_map = virtual_dt_free_map,
};
/* 获取引脚配置 */
static int virtual_pconf_get(struct pinctrl_dev *pctldev, unsigned pin, unsigned long *config)
{
if(pin >= ARRAY_SIZE(pin_configs))
return -EINVAL;
*config = pin_configs[pin];
return 0;
}
/* 配置引脚 */
static int virtual_pconf_set(struct pinctrl_dev *pctldev, unsigned pin, unsigned long *configs, unsigned num_configs)
{
if((num_configs != 1) || (pin >= ARRAY_SIZE(pin_configs)))
return -EINVAL;
pin_configs[pin] = *configs;
printk("pin config %s as 0x%lx\n", pctldev->desc->pins[pin].name, *configs);
return 0;
}
/* 获取引脚组配置 */
static int virtual_pconf_group_get(struct pinctrl_dev *pctldev, unsigned group, unsigned long *config)
{
if(group >= ARRAY_SIZE(pin_configs))
return -EINVAL;
//因为一个引脚对应一个引脚组,所以引脚的配置与引脚组的配置一样
*config = pin_configs[group];
return 0;
}
/* 配置引脚组 */
static int virtual_pconf_group_set(struct pinctrl_dev *pctldev, unsigned group, unsigned long *configs, unsigned num_configs)
{
if((num_configs != 1) || (group >= ARRAY_SIZE(pin_configs)))
return -EINVAL;
pin_configs[group] = *configs;
printk("group config %s as 0x%lx\n", pctldev->desc->pins[group].name, *configs);
return 0;
}
static void virtual_pconf_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s, unsigned pin)
{
if(pin >= ARRAY_SIZE(pin_configs))
return ;
seq_printf(s, "0x%lx", pin_configs[pin]);
}
static void virtual_pconf_group_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s, unsigned group)
{
if(group >= ARRAY_SIZE(pin_configs))
return ;
seq_printf(s, "0x%lx", pin_configs[group]);
}
/* 引脚配置的操作函数集合 */
static const struct pinconf_ops virtual_pconf_ops = {
.pin_config_get = virtual_pconf_get,
.pin_config_set = virtual_pconf_set,
.pin_config_group_get = virtual_pconf_group_get,
.pin_config_group_set = virtual_pconf_group_set,
.pin_config_dbg_show = virtual_pconf_dbg_show,
.pin_config_group_dbg_show = virtual_pconf_group_dbg_show,
};
/* 获取共计多少种复用功能 */
static int virtual_pmx_get_funcs_cnt(struct pinctrl_dev *pctldev)
{
return ARRAY_SIZE(pin_functions);
}
/* 获取某种复用功能的名称 */
static const char *virtual_pmx_get_func_name(struct pinctrl_dev *pctldev, unsigned function)
{
if(function >= ARRAY_SIZE(pin_functions))
return NULL;
return pin_functions[function];
}
/* 获取支持某复用功能的引脚组 */
static int virtual_pmx_get_func_groups(struct pinctrl_dev *pctldev, unsigned function, const char * const **groups, unsigned * const num_groups)
{
if(function >= ARRAY_SIZE(pin_functions))
return -EINVAL;
//所有引脚都支持相同的复用功能
*groups = pin_groups;
*num_groups = ARRAY_SIZE(pin_groups);
return 0;
}
/* 设置引脚组的复用功能 */
static int virtual_pmx_set_mux(struct pinctrl_dev *pctldev, unsigned function, unsigned group)
{
if((group >= ARRAY_SIZE(pin_mux)) || (function >= ARRAY_SIZE(pin_functions)))
return -EINVAL;
pin_mux[group] = function;
printk("group mux %s as 0x%x\n", pctldev->desc->pins[group].name, function);
return 0;
}
/* 引脚复用功能选择和GPIO控制相关的操作函数集合 */
static const struct pinmux_ops virtual_pmx_ops = {
.get_functions_count = virtual_pmx_get_funcs_cnt,
.get_function_name = virtual_pmx_get_func_name,
.get_function_groups = virtual_pmx_get_func_groups,
.set_mux = virtual_pmx_set_mux,
.strict = true,
};
//设备和驱动匹配成功执行
static int virtual_probe(struct platform_device *pdev)
{
struct pinctrl_desc *pctl_desc;
struct pinctrl_dev *pctl_dev;
printk("%s\n", __FUNCTION__);
/* a. 分配pinctrl_desc */
pctl_desc = devm_kzalloc(&pdev->dev, sizeof(struct pinctrl_desc), GFP_KERNEL);
if(!pctl_desc)
{
printk("alloc pctl_desc failed\r\n");
return -ENOMEM;
}
/* b. 设置pinctrl_desc */
/* b.1 设置名称和所属模块 */
pctl_desc->name = dev_name(&pdev->dev);
pctl_desc->owner = THIS_MODULE;
/* b.2 描述单个引脚 */
pctl_desc->pins = pins_desc;
pctl_desc->npins = ARRAY_SIZE(pins_desc);
/* b.3 绑定用于获取引脚组的引脚和处理设备树操的作函数集合 */
pctl_desc->pctlops = &virtual_ops;
/* b.5 绑定引脚配置的操作函数集合 */
pctl_desc->confops = &virtual_pconf_ops;
/* b.4 绑定引脚复用功能选择和GPIO控制相关的操作函数集合 */
pctl_desc->pmxops = &virtual_pmx_ops;
/* c. 注册pinctrl_desc,devm表示模块卸载时自动释放 */
pctl_dev = devm_pinctrl_register(&pdev->dev, pctl_desc, NULL);
if (IS_ERR(pctl_dev))
{
printk("Failed pinctrl register\n");
return PTR_ERR(pctl_dev);
}
return 0;
}
//设备或驱动卸载时执行
static int virtual_remove(struct platform_device *pdev)
{
printk("%s\n", __FUNCTION__);
return 0;
}
/* 匹配列表,用于设备树和平台驱动匹配 */
static const struct of_device_id virtual_of_match[] = {
{.compatible = "atk,virtual_pinctrl"},
{ /* Sentinel */ }
};
/* 平台驱动 */
static struct platform_driver virtual_drv = {
.driver = {
.name = "virtual_pinctrl",
.owner = THIS_MODULE,
.pm = NULL,
.of_match_table = virtual_of_match,
},
.probe = virtual_probe,
.remove = virtual_remove,
};
static int __init virtual_drv_init(void)
{
int result;
printk("%s\r\n", __FUNCTION__);
//注册平台驱动
result = platform_driver_register(&virtual_drv);
if(result != 0)
printk("add virtual driver failed\r\n");
return result;
}
static void __exit virtual_drv_exit(void)
{
printk("%s\r\n", __FUNCTION__);
//注销平台驱动
platform_driver_unregister(&virtual_drv);
}
module_init(virtual_drv_init);
module_exit(virtual_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("CSDN");
MODULE_DESCRIPTION("virtual_drv");
驱动测试程序
驱动测试程序是一个platform driver它没有实现如何具体的功能,仅仅是在设备树节点中引用了虚拟pinctrl的引脚描述节点
#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>
static int virtual_client_probe(struct platform_device *pdev)
{
printk("%s\n", __FUNCTION__);
return 0;
}
static int virtual_client_remove(struct platform_device *pdev)
{
printk("%s\n", __FUNCTION__);
return 0;
}
static const struct of_device_id virtual_client_of_match[] = {
{ .compatible = "atk,virtual_i2c", },
{ },
};
static struct platform_driver virtual_client_driver = {
.probe = virtual_client_probe,
.remove = virtual_client_remove,
.driver = {
.name = "atk,virtual_i2c",
.of_match_table = of_match_ptr(virtual_client_of_match),
}
};
/* 1. 入口函数 */
static int __init virtual_client_init(void)
{
printk("%s\n", __FUNCTION__);
/* 1.1 注册一个platform_driver */
return platform_driver_register(&virtual_client_driver);
}
/* 2. 出口函数 */
static void __exit virtual_client_exit(void)
{
printk("%s\n", __FUNCTION__);
/* 2.1 反注册platform_driver */
platform_driver_unregister(&virtual_client_driver);
}
module_init(virtual_client_init);
module_exit(virtual_client_exit);
MODULE_LICENSE("GPL");
上机测试
- 修改设备树,然后用新的设备树启动目标板
- 从这里下载代码。然后进行编译,并拷贝到目标板root目录
- 执行命令insmod virtual_pinctrl.ko加载虚拟pinctrl控制器驱动
- 执行命令insmod virtual_client.ko加载使用虚拟pinctrl控制器的platform driver,可以看到在执行probe函数之前引脚电气特性配置函数和复用功能配置函数被调用