Linux虚拟Pinctrl Demo驱动-- Demo Code
Posted ZHONGCAI0901
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux虚拟Pinctrl Demo驱动-- Demo Code相关的知识,希望对你有一定的参考价值。
1. 前言
为了更进一步了解Linux的Pinctrl子系统,这里编写一个虚拟的Pinctrl Demo驱动。下面是编写的内容文件列表:
下面是在加载驱动virt_pinctrl_client.ko
和virt_pinctrl_driver.ko
驱动后的运行效果:
下面主要是分析virt_pinctrl_driver.c
,virt_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_tree
和struct 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驱动 -- Debug FS之Pinctrl分析
Linux虚拟Pinctrl Demo驱动 -- Debug FS之Pinctrl分析