自学内容网 自学内容网

Pinctrl子系统中Pincontroller和client驱动程序的编写

往期内容

本专栏往期内容:

  1. Pinctrl子系统和其主要结构体引入
  2. Pinctrl子系统pinctrl_desc结构体进一步介绍
  3. Pinctrl子系统中client端设备树相关数据结构介绍和解析
  4. inctrl子系统中Pincontroller构造过程驱动分析:imx_pinctrl_soc_info结构体
  5. Pinctrl子系统中client端使用pinctrl过程的驱动分析

input子系统专栏:

  1. 专栏地址:input子系统
  2. input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
    – 末片,有往期内容观看顺序

I2C子系统专栏:

  1. 专栏地址:IIC子系统
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有往期内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有往期内容观看顺序

img

1.回顾Pinctrl的三大作用

记住pinctrl的三大作用,有助于理解所涉及的数据结构:

  • 引脚枚举与命名(Enumerating and naming)

    • 单个引脚
    • 各组引脚
  • 引脚复用(Multiplexing):比如用作GPIO、I2C或其他功能

  • 引脚配置(Configuration):比如上拉、下拉、open drain、驱动强度等

Pinctrl子系统和其主要结构体引入

Pinctrl子系统pinctrl_desc结构体进一步介绍

Pinctrl子系统中client端设备树相关数据结构介绍和解析

2.需要做什么

img

  • pin controller:

    • 创建设备树节点
    • 编写驱动程序
  • 测试:

    • 创建client设备树节点
    • 编写驱动程序

3.硬件功能

假设这个虚拟的pin controller有4个引脚:

img

  • pin0,1,2,3都可以配置为GPIO功能 — function 1
  • pin0,1还可以配置为I2C功能 — function 2
  • pin2,3还可以配置为UART功能 — function 3

4.编写设备树

       Pincontroller:
virtual_pincontroller {
compatible = "XXX,virtual_pinctrl";
i2cgrp: i2cgrp {
functions = "i2c", "i2c";
groups = "pin0", "pin1";
configs = <0x11223344  0x55667788>;
};
};

      client:
virtual_i2c {
compatible = "XXX,virtual_i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2cgrp>;
};

5.编写Pinctrl驱动程序

5.1 核心:pinctrl_desc

  • 分配pinctrl_desc结构体
  • 设置pinctrl_desc结构体
  • 注册pinctrl_desc结构体

5.2 辅助函数

\Linux-4.9.88\include\linux\of.h📎of.h

include/linux/of.h
    for_each_child_of_node
    of_get_child_count
    of_find_property
    of_property_read_u32
    of_property_read_u32_index
    of_property_read_string_index

5.2.1 for_each_child_of_node

#define for_each_child_of_node(parent, child) \
for (child = of_get_next_child(parent, NULL); child != NULL; \
     child = of_get_next_child(parent, child))

for_each_child_of_node 是一个宏,用于遍历给定父节点的所有直接子节点。它在遍历每个子节点时会将其赋值给变量 child,供后续操作使用。

参数

  • parent: 指向父节点的指针。
  • child: 用于存储当前遍历的子节点的指针。

说明

  • 该宏循环调用 of_get_next_child 函数,遍历所有直接子节点。
  • 在遍历的过程中,child 会依次指向 parent 的每一个子节点。
  • 使用 for_each_child_of_node 遍历时,用户需要确保释放所有的子节点,避免资源泄露。

5.2.2 of_get_child_count

static inline int of_get_child_count(const struct device_node *np)
{
struct device_node *child;
int num = 0;

for_each_child_of_node(np, child)
num++;

return num;
}

功能

of_get_child_count 函数用于计算指定设备树节点的直接子节点数量。

参数

  • np: 指向设备树节点的指针。

说明

  • 该函数使用 for_each_child_of_node 宏遍历节点的所有直接子节点,每找到一个子节点,计数器 num 加 1。
  • of_get_child_count 通常用于确定设备树节点的子节点数量,以便在后续操作中做动态分配或判断。

5.2.3 of_find_property

extern struct property *of_find_property(const struct device_node *np,
 const char *name,
 int *lenp);

功能

of_find_property 函数用于在指定节点中查找属性。

参数

  • np: 指向设备树节点的指针。
  • name: 要查找的属性名称(字符串)。
  • lenp: 指向一个整数的指针,用于存储找到的属性的长度(以字节为单位)。

说明

  • 该函数适用于检查节点中是否存在某个属性。若 lenp 不为 NULL,将返回属性的长度。
  • of_find_property 常用于从设备树中查找如 compatiblestatus 等属性。

5.2.4 of_property_read_u32

static inline int of_property_read_u32(const struct device_node *np,
       const char *propname,
       u32 *out_value)
{
return of_property_read_u32_array(np, propname, out_value, 1);
}

功能

of_property_read_u32 函数用于读取设备树节点中指定属性的 u32 整数值。

参数

  • np: 指向设备树节点的指针。
  • propname: 要读取的属性名称。
  • out_value: 指向 u32 类型的变量的指针,用于存储读取到的属性值

说明

  • 该函数通过调用 of_property_read_u32_array 实现,从设备树中读取一个 u32 类型的属性值。
  • 如果属性为数组且包含多个值,则只会读取第一个元素。如果需要读取多个值,可以直接调用 of_property_read_u32_array

5.2.5 of_property_read_u32_index

extern int of_property_read_u32_index(const struct device_node *np,
       const char *propname,
       u32 index, u32 *out_value);

功能

of_property_read_u32_index 函数用于读取设备树节点中 u32 类型数组属性的指定索引值。

参数

  • np: 指向设备树节点的指针。
  • propname: 属性名称。
  • index: 数组中的索引。
  • out_value: 指向 u32 变量的指针,用于存储读取到的值。

说明

  • 该函数适用于读取包含多个 u32 元素的数组属性,例如 reginterrupts 等。
  • of_property_read_u32_index 可以用来精确访问设备树中数组属性的特定元素。

5.2.6 of_property_read_string_index

/**
 * of_property_read_string_index() - Find and read a string from a multiple
 * strings property.
 * @np:device node from which the property value is to be read.
 * @propname:name of the property to be searched.
 * @index:index of the string in the list of strings
 * @out_string:pointer to null terminated return string, modified only if
 *return value is 0.
 *
 * Search for a property in a device tree node and retrieve a null
 * terminated string value (pointer to data, not a copy) in the list of strings
 * contained in that property.
 * Returns 0 on success, -EINVAL if the property does not exist, -ENODATA if
 * property does not have a value, and -EILSEQ if the string is not
 * null-terminated within the length of the property data.
 *
 * The out_string pointer is modified only if a valid string can be decoded.
 */
static inline int of_property_read_string_index(const struct device_node *np,
const char *propname,
int index, const char **output)
{
int rc = of_property_read_string_helper(np, propname, output, 1, index);
return rc < 0 ? rc : 0;
}

功能

of_property_read_string_index 函数用于读取设备树节点中字符串数组属性的指定索引值。

参数

  • np: 指向设备树节点的指针。
  • propname: 属性名称。
  • index: 要读取的字符串在数组中的索引。
  • output: 指向 const char * 指针的指针,用于存储读取到的字符串地址。

说明

  • 该函数常用于读取如 pinctrl-names 之类的字符串数组属性,能够获取数组属性中的第 index 个字符串。
  • 例如,如果 pinctrl-names 属性中定义了 default, idle 两个状态值,可以使用该函数获取具体的字符串内容。

5.3 代码

📎core.h

📎virtual_pinctrl_driver.c

#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"

// 全局变量,保存 pinctrl 设备结构体指针
static struct pinctrl_dev *global_pinctrl_dev;

// 定义一个 pins 数组,描述 GPIO 引脚
static const struct pinctrl_pin_desc pin_descs[] = {
    {0, "pin0", NULL},
    {1, "pin1", NULL},
    {2, "pin2", NULL},
    {3, "pin3", NULL},
};

// 全局配置数组
static unsigned long pin_configs[4];

// 虚拟功能描述结构体
struct virtual_function_desc {
    const char *function_name;
    const char **groups;
    int group_count;
};

// 定义不同功能组的引脚
static const char *function0_groups[] = {"pin0", "pin1", "pin2", "pin3"};
static const char *function1_groups[] = {"pin0", "pin1"};
static const char *function2_groups[] = {"pin2", "pin3"};

// 定义功能描述数组
static struct virtual_function_desc virtual_functions[] = {
    {"gpio", function0_groups, 4},
    {"i2c",  function1_groups, 2},
    {"uart", function2_groups, 2},
};

// 设备树匹配表
static const struct of_device_id virtual_pinctrl_of_match[] = {
    { .compatible = "XXX,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)); // 打印设备名称
}

// 将设备树节点映射到 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_pins = 0;
    const char *pin;
    const char *function;
    unsigned int config;
    struct pinctrl_map *new_map;
    unsigned long *configs;

    // 计算 groups 数组的数量
    while (of_property_read_string_index(np, "groups", num_pins, &pin) == 0)
        num_pins++;

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

    // 获取引脚、功能和配置,并填充到新映射中
    for (i = 0; i < num_pins; i++) {
        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);

        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; // 配置数量为 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++;
    }
}

// pinctrl 操作结构体
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(virtual_functions);
}

// 获取功能名称
static const char *virtual_pmx_get_func_name(struct pinctrl_dev *pctldev,
                                              unsigned selector) {
    return virtual_functions[selector].function_name;
}

// 获取功能组
static int virtual_pmx_get_groups(struct pinctrl_dev *pctldev, unsigned selector,
                                   const char * const **groups,
                                   unsigned * const num_groups) {
    *groups = virtual_functions[selector].groups; // 返回功能组
    *num_groups = virtual_functions[selector].group_count; // 返回功能组数量

    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, virtual_functions[selector].function_name);
    return 0;
}

// pinmux 操作结构体
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 = pin_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; // 错误处理
    
    pin_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", pin_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", pin_configs[pin_id]);
}

// pinconf 操作结构体
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,
};

// probe 函数,用于初始化 pinctrl
static int virtual_pinctrl_probe(struct platform_device *pdev) {
    struct pinctrl_desc *pinctrl_desc;
    
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    // 分配 pinctrl_desc 结构体
    pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*pinctrl_desc), GFP_KERNEL);
    
    // 初始化 pinctrl_desc 结构体
    pinctrl_desc->name = dev_name(&pdev->dev);
    pinctrl_desc->owner = THIS_MODULE;
    
    // 设置引脚描述和数量
    pinctrl_desc->pins = pin_descs;
    pinctrl_desc->npins = ARRAY_SIZE(pin_descs);

    pinctrl_desc->pctlops = &virtual_pctrl_ops; // 设置 pinctrl 操作
    pinctrl_desc->pmxops = &virtual_pmx_ops; // 设置 pinmux 操作
    pinctrl_desc->confops = &virtual_pinconf_ops; // 设置 pinconf 操作
    
    // 注册 pinctrl 设备
    global_pinctrl_dev = devm_pinctrl_register(&pdev->dev, pinctrl_desc, NULL);
    
    return 0;
}

// remove 函数,清理 pinctrl
static int virtual_pinctrl_remove(struct platform_device *pdev) {
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

// platform driver 结构体
static struct platform_driver virtual_pinctrl_driver = {
    .probe      = virtual_pinctrl_probe,
    .remove     = virtual_pinctrl_remove,
    .driver     = {
        .name   = "XXX,virtual_pinctrl",
        .of_match_table = of_match_ptr(virtual_pinctrl_of_match),
    }
};

// 初始化函数
static int __init virtual_pinctrl_init(void) {
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    // 注册 platform driver
    return platform_driver_register(&virtual_pinctrl_driver);
}

// 清理函数
static void __exit virtual_pinctrl_exit(void) {
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    // 注销 platform driver
    platform_driver_unregister(&virtual_pinctrl_driver);
}

// 模块入口和出口
module_init(virtual_pinctrl_init);
module_exit(virtual_pinctrl_exit);

MODULE_LICENSE("GPL");

img

5.4 编写测试的client驱动程序

#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>

// 设备树匹配表,匹配虚拟 I2C 设备
static const struct of_device_id virtual_i2c_of_match[] = {
    { .compatible = "XXX,virtual_i2c", },
    { },
};

// probe 函数,在设备被检测到时调用
static int virtual_i2c_probe(struct platform_device *pdev) {
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 输出当前文件和函数信息
    return 0; // 返回 0 表示成功
}

// remove 函数,在设备被移除时调用
static int virtual_i2c_remove(struct platform_device *pdev) {
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 输出当前文件和函数信息
    return 0; // 返回 0 表示成功
}

// 定义平台驱动结构体
static struct platform_driver virtual_i2c_driver = {
    .probe      = virtual_i2c_probe, // 设备探测函数
    .remove     = virtual_i2c_remove, // 设备移除函数
    .driver     = {
        .name   = "100ask_virtual_client", // 驱动名称
        .of_match_table = of_match_ptr(virtual_i2c_of_match), // 设备树匹配表
    }
};

/* 1. 模块初始化函数 */
static int __init virtual_i2c_init(void) {    
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 输出当前文件和函数信息
    // 注册平台驱动
    return platform_driver_register(&virtual_i2c_driver);
}

/* 2. 模块清理函数 */
static void __exit virtual_i2c_exit(void) {
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); // 输出当前文件和函数信息
    // 注销平台驱动
    platform_driver_unregister(&virtual_i2c_driver);
}

// 模块入口和出口
module_init(virtual_i2c_init);
module_exit(virtual_i2c_exit);

// 模块许可证
MODULE_LICENSE("GPL");

6.调试信息

开发板的/sys/kernel/debug/pinctrl/目录下,每一个pin controller都有一个目录,比如virtual_pincontroller。
里面有很多文件,作用如下:

Pinctrl的虚拟文件作用
pins单个引脚信息
pingroups引脚的组信息
pinmux-pins单个引脚的复用信息
pinmux-functionsfunction下的group(支持该function的group)
pinconf-pins单个引脚的配置
pinconf-groups引脚组的配置
pinconf-config可以通过写它修改指定设备、指定状态下、指定(组)引脚的config值
  • 单个引脚信息
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pins
registered pins: 4
pin 0 (pin0) virtual_pincontroller
pin 1 (pin1) virtual_pincontroller
pin 2 (pin2) virtual_pincontroller
pin 3 (pin3) virtual_pincontroller
  • 引脚的组信息
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pingroups
registered pin groups:
group: pin0
pin 0 (pin0)

group: pin1
pin 1 (pin1)

group: pin2
pin 2 (pin2)

group: pin3
pin 3 (pin3)
  • 单个引脚的复用信息
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinmux-pins
Pinmux settings per pin
Format: pin (name): mux_owner gpio_owner hog?
pin 0 (pin0): virtual_i2c (GPIO UNCLAIMED) function i2c group pin0
pin 1 (pin1): virtual_i2c (GPIO UNCLAIMED) function i2c group pin1
pin 2 (pin2): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 3 (pin3): (MUX UNCLAIMED) (GPIO UNCLAIMED)
  • function下的group(支持该function的group)
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinmux-functions
function: gpio, groups = [ pin0 pin1 pin2 pin3 ]
function: i2c, groups = [ pin0 pin1 ]
function: uart, groups = [ pin2 pin3 ]
  • 单个引脚的配置
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-pins
Pin config settings per pin
Format: pin (name): configs
pin 0 (pin0): 0x11223344
pin 1 (pin1): 0x55667788
pin 2 (pin2): 0x0
pin 3 (pin3): 0x0
  • 引脚组的配置
cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-groups
Pin config settings per pin group
Format: group (name): configs
0 (pin0): 0x11223344
1 (pin1): 0x55667788
2 (pin2): 0x0
3 (pin3): 0x0
  • 修改配置值
    内核源码:
drivers\pinctrl\pinconf.c
    pinconf_dbg_config_write

如果pin controller驱动程序中的pinconf_ops提供了pin_config_dbg_parse_modify函数,
就可以通过pinconf-config文件修改某个pin或某个group的配置值。

// 格式: modify <config> <devicename> <state> <pin_name|group_name> <newvalue>
echo "modify config_pin virtual_i2c default pin0 0xaabb" > /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-config

cat /sys/kernel/debug/pinctrl/virtual_pincontroller/pinconf-config

原文地址:https://blog.csdn.net/caiji0169/article/details/143473255

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!