当前位置: 首页 > news >正文

Linux学习笔记--Pinctrl子系统驱动

1. 引脚定义和数据结构

/* 定义4个虚拟引脚 */
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"};  /* gpio */
static const char *func1_grps[] = {"pin0", "pin1"};                  /* i2c */
static const char *func2_grps[] = {"pin2", "pin3"};                  /* uart */static struct virtual_functions_desc g_funcs_des[] = {{"gpio", func0_grps, 4},{"i2c",  func1_grps, 2},{"uart", func2_grps, 2},
};

2. pinctrl_ops 实现

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_dt_node_to_map(struct pinctrl_dev *pctldev,struct device_node *np,struct pinctrl_map **map, unsigned *num_maps)
{/* 解析设备树中的配置:* i2cgrp {*     functions = "i2c", "i2c";*     groups = "pin0", "pin1";*     configs = <0x11223344 0x55667788>;* };*//* 1. 计算引脚数量 */while (1) {if (of_property_read_string_index(np, "groups", num_pins, &pin) == 0)num_pins++;elsebreak;}/* 2. 分配映射数组:每个引脚需要2个map(复用+配置) */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);/* 3. 创建复用映射 */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;/* 4. 创建配置映射 */configs = kmalloc(sizeof(*configs), GFP_KERNEL);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;}
}

virtual_dt_node_to_map 函数是 pinctrl 驱动中最关键的设备树解析函数,负责将设备树中的引脚配置转换为内核能够理解的 pinctrl_map 结构

将设备树节点转换为 pinctrl 映射表,建立"设备状态"到"具体引脚配置"的映射关系。

设备树输入
i2cgrp {functions = "i2c", "i2c";                /* 功能名称 */groups = "pin0", "pin1";                 /* 引脚组名称 */  configs = <0x11223344 0x55667788>;       /* 配置值 */
};
详细执行流程
1. 计算引脚数量
/* 1. 计算引脚数量 */
while (1) {if (of_property_read_string_index(np, "groups", num_pins, &pin) == 0)num_pins++;elsebreak;
}

工作过程

  • 循环读取设备树中 groups 属性的字符串

  • 每次成功读取一个引脚名,num_pins 加1

  • 当读取失败时退出循环

  • 结果:对于示例,num_pins = 2(pin0, pin1)

2. 分配映射数组
/* 2. 分配映射数组:每个引脚需要2个map(复用+配置) */
new_map = kmalloc(sizeof(struct pinctrl_map) * num_pins * 2, GFP_KERNEL);

分配策略

  • 每个引脚需要 2个 pinctrl_map

    • 1个用于 引脚复用 (PIN_MAP_TYPE_MUX_GROUP)

    • 1个用于 引脚配置 (PIN_MAP_TYPE_CONFIGS_PIN)

  • 总大小num_pins × 2 × sizeof(struct pinctrl_map)

  • 示例:2个引脚 → 分配4个 pinctrl_map

3. 循环处理每个引脚
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);

读取设备树数据

  • groups[i] → 引脚名称(如 "pin0")

  • functions[i] → 功能名称(如 "i2c")

  • configs[i] → 配置值(如 0x11223344)

4. 创建复用映射
/* 3. 创建复用映射 */
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;

映射内容

// 对于 i=0, pin0:
new_map[0] = {.type = PIN_MAP_TYPE_MUX_GROUP,.data.mux = {.function = "i2c",.group = "pin0"}
}

含义:将引脚 "pin0" 的功能设置为 "i2c"

5. 创建配置映射
/* 4. 创建配置映射 */
configs = kmalloc(sizeof(*configs), GFP_KERNEL);
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;

映射内容

// 对于 i=0, pin0:
new_map[1] = {.type = PIN_MAP_TYPE_CONFIGS_PIN, .data.configs = {.group_or_pin = "pin0",.configs = &0x11223344,  // 指向分配的内存.num_configs = 1}
}

内存分配细节

  • 为每个配置值单独分配内存

  • 存储具体的电气特性配置(如上拉、下拉、驱动强度等)

最终生成的映射表

对于示例设备树,生成的 pinctrl_map 数组如下:

索引类型功能引脚配置值
0MUX_GROUP"i2c""pin0"-
1CONFIGS_PIN-"pin0"0x11223344
2MUX_GROUP"i2c""pin1"-
3CONFIGS_PIN-"pin1"0x55667788

3. pinmux_ops 实现

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

4. pinconf_ops 实现

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

5. 驱动初始化和注册

static int virtual_pinctrl_probe(struct platform_device *pdev)
{struct pinctrl_desc *pictrl;/* 分配并设置 pinctrl_desc */pictrl = devm_kzalloc(&pdev->dev, sizeof(*pictrl), GFP_KERNEL);pictrl->name = dev_name(&pdev->dev);pictrl->owner = THIS_MODULE;pictrl->pins = pins;pictrl->npins = ARRAY_SIZE(pins);pictrl->pctlops = &virtual_pctrl_ops;pictrl->pmxops = &virtual_pmx_ops;pictrl->confops = &virtual_pinconf_ops;/* 注册引脚控制器 */g_pinctrl_dev = devm_pinctrl_register(&pdev->dev, pictrl, NULL);return 0;
}

6. 设备树匹配

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

工作流程总结

1. 系统启动

  • 模块加载,注册 platform_driver

  • 匹配设备树节点,调用 probe 函数

  • 注册 pinctrl_desc,创建 pinctrl_dev

2. 设备树解析

当其他设备引用这个 pinctrl 控制器时:

device_node {pinctrl-0 = <&i2cgrp>;i2cgrp: i2cgrp {functions = "i2c", "i2c";groups = "pin0", "pin1";configs = <0x11223344 0x55667788>;};
}

3. 映射创建流程

virtual_dt_node_to_map 解析设备树

        为每个引脚创建:

                一个 PIN_MAP_TYPE_MUX_GROUP 映射(功能复用)

                一个 PIN_MAP_TYPE_CONFIGS_PIN 映射(电气配置)

                        返回映射数组给 pinctrl 核心

4. 配置应用流程

        设备驱动调用 pinctrl_select_state()

        pinctrl 核心根据映射创建 setting

        调用 virtual_pmx_set() 设置复用功能

        调用 virtual_pinconf_set() 设置电气特性

实际硬件驱动对比

这个虚拟驱动与实际硬件驱动的区别:

虚拟驱动实际硬件驱动
printk 输出配置硬件寄存器
内存数组存储配置写入 SoC 的 IOMUX 寄存器
简单的引脚定义复杂的 SoC 引脚描述

映射示例

假设我们有这样的设备树配置:

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

for 循环详细执行过程

循环开始前

  • num_pins = 2(因为 "pin0", "pin1" 两个引脚)

  • new_map 分配了 4 个 pinctrl_map(2引脚 × 2映射/引脚)

第一次循环 (i = 0)

1. 读取设备树属性
of_property_read_string_index(np, "groups", 0, &pin);      // pin = "pin0"
of_property_read_string_index(np, "functions", 0, &function); // function = "i2c"
of_property_read_u32_index(np, "configs", 0, &config);     // config = 0x11223344
2. 创建复用映射 (i×2 = 0)
new_map[0].type = PIN_MAP_TYPE_MUX_GROUP;
new_map[0].data.mux.function = "i2c";
new_map[0].data.mux.group = "pin0";

映射结果:

new_map[0]: {type: PIN_MAP_TYPE_MUX_GROUP,data: {mux: {function: "i2c",group: "pin0"}}
}
3. 创建配置映射 (i×2+1 = 1)
configs = kmalloc(sizeof(*configs), GFP_KERNEL);  // 分配配置内存
configs[0] = 0x11223344;                         // 存储配置值new_map[1].type = PIN_MAP_TYPE_CONFIGS_PIN;
new_map[1].data.configs.group_or_pin = "pin0";
new_map[1].data.configs.configs = configs;       // 指向配置数组
new_map[1].data.configs.num_configs = 1;

映射结果:

new_map[1]: {type: PIN_MAP_TYPE_CONFIGS_PIN,data: {configs: {group_or_pin: "pin0",configs: [0x11223344],  // 指向分配的配置数组num_configs: 1}}
}

第二次循环 (i = 1)

1. 读取设备树属性
of_property_read_string_index(np, "groups", 1, &pin);      // pin = "pin1"
of_property_read_string_index(np, "functions", 1, &function); // function = "i2c"
of_property_read_u32_index(np, "configs", 1, &config);     // config = 0x55667788
2. 创建复用映射 (i×2 = 2)
new_map[2].type = PIN_MAP_TYPE_MUX_GROUP;
new_map[2].data.mux.function = "i2c";
new_map[2].data.mux.group = "pin1";

映射结果:

new_map[2]: {type: PIN_MAP_TYPE_MUX_GROUP,data: {mux: {function: "i2c",group: "pin1"}}
}
3. 创建配置映射 (i×2+1 = 3)
configs = kmalloc(sizeof(*configs), GFP_KERNEL);  // 分配新的配置内存
configs[0] = 0x55667788;                         // 存储配置值new_map[3].type = PIN_MAP_TYPE_CONFIGS_PIN;
new_map[3].data.configs.group_or_pin = "pin1";
new_map[3].data.configs.configs = configs;       // 指向新的配置数组
new_map[3].data.configs.num_configs = 1;

映射结果:

new_map[3]: {type: PIN_MAP_TYPE_CONFIGS_PIN,data: {configs: {group_or_pin: "pin1",configs: [0x55667788],  // 指向新分配的配置数组num_configs: 1}}
}

最终生成的映射数组

循环结束后,我们得到这样的映射数组:

new_map[0]: MUX_GROUP    - pin0 复用为 i2c 功能
new_map[1]: CONFIGS_PIN  - pin0 配置为 0x11223344
new_map[2]: MUX_GROUP    - pin1 复用为 i2c 功能  
new_map[3]: CONFIGS_PIN  - pin1 配置为 0x55667788

内存布局示意图

new_map (kmalloc 分配)
├── [0] pinctrl_map (MUX_GROUP)
│   ├── type = PIN_MAP_TYPE_MUX_GROUP
│   └── data.mux = {function="i2c", group="pin0"}
├── [1] pinctrl_map (CONFIGS_PIN)
│   ├── type = PIN_MAP_TYPE_CONFIGS_PIN  
│   └── data.configs = {group_or_pin="pin0", configs→[0x11223344], num_configs=1}
├── [2] pinctrl_map (MUX_GROUP)
│   ├── type = PIN_MAP_TYPE_MUX_GROUP
│   └── data.mux = {function="i2c", group="pin1"}
└── [3] pinctrl_map (CONFIGS_PIN)├── type = PIN_MAP_TYPE_CONFIGS_PIN└── data.configs = {group_or_pin="pin1", configs→[0x55667788], num_configs=1}

其中:

  •  表示指针指向另外分配的内存块

  • 每个 CONFIGS_PIN 的 configs 指针都指向独立的 kmalloc 分配的内存

pmx_set例子

virtual_pmx_set 函数是 pinmux(引脚复用)操作的核心函数,负责将指定的引脚组配置为特定的功能。在实际硬件中,这个函数会配置 SoC 的复用控制器寄存器

1. i.MX6ULL 的实际实现示例

static int imx_pmx_set(struct pinctrl_dev *pctldev, unsigned selector,unsigned group)
{struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);const struct imx_pin_group *grp = &ipctl->groups[group];const struct imx_pin_reg *pin_reg;u32 reg, mux;int i;/* 遍历组内的所有引脚 */for (i = 0; i < grp->npins; i++) {pin_reg = &ipctl->pin_regs[grp->pins[i]];/* 读取当前的复用寄存器 */reg = readl(ipctl->base + pin_reg->mux_reg);/* 清除原有的复用设置 */reg &= ~(0x7 << 20);/* 设置新的复用功能 */mux = pin_reg->mux_mode;reg |= (mux << 20);/* 写入寄存器 */writel(reg, ipctl->base + pin_reg->mux_reg);dev_dbg(ipctl->dev, "set pin %d to function %s (mux %d)\n",grp->pins[i], grp->name, mux);}return 0;
}

2. 具体配置场景示例

假设我们要将 UART4 引脚配置为 I2C1 功能

设备树配置:
&iomuxc {i2c1grp: i2c1grp {fsl,pins = <MX6UL_PAD_UART4_RX_DATA__I2C1_SDA  0x4001b8b0MX6UL_PAD_UART4_TX_DATA__I2C1_SCL  0x4001b8b0>;};
};&i2c1 {pinctrl-names = "default";pinctrl-0 = <&i2c1grp>;status = "okay";
};
函数调用过程:
  1. 设备树解析 → 创建映射

  2. I2C1 驱动探测 → 查找 "default" 状态

  3. 调用 virtual_pmx_set

// selector = 1 (对应 "i2c" 功能)
// group = 5 (对应 "i2c1grp" 组)virtual_pmx_set(pctldev, 1, 5);
实际硬件操作:
// 对于 MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 引脚:
// - 找到对应的复用寄存器:IOMUXC_SW_MUX_CTL_PAD_UART4_RX_DATA
// - 读取当前值,清除功能位,设置新的复用模式
reg = readl(base + 0x01E0);
reg &= ~0x0000000F;        // 清除低4位
reg |= 0x00000004;         // 设置为 ALT4 模式 (I2C1_SDA)
writel(reg, base + 0x01E0);// 对于 MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 引脚同样操作
reg = readl(base + 0x01E4);
reg &= ~0x0000000F;
reg |= 0x00000004;         // 设置为 ALT4 模式 (I2C1_SCL)
writel(reg, base + 0x01E4);

复用功能配置的详细过程

1. 功能查找

// selector = 1 对应 g_funcs_des[1] = {"i2c", func1_grps, 2}
const char *func_name = g_funcs_des[selector].func_name;  // "i2c"

2. 引脚组查找

// group = 5 对应具体的引脚组
const char *group_name = pctldev->desc->pins[group].name; // "i2c1grp"

3. 硬件寄存器配置

在真实驱动中,通常需要:

// a. 获取私有数据
struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);// b. 获取引脚组的寄存器信息
const struct imx_pin_group *pin_group = &ipctl->groups[group];// c. 遍历组内所有引脚,配置复用
for (i = 0; i < pin_group->npins; i++) {unsigned pin_id = pin_group->pins[i];void __iomem *mux_reg = ipctl->base + pin_mux_offset[pin_id];// d. 配置复用寄存器writel(new_mux_value, mux_reg);
}

pinconf_set

   virtual_pinconf_set 函数是 pinconf(引脚配置)操作的核心函数,负责设置引脚的电气特性参数。在实际硬件中,这个函数会配置 SoC 的引脚控制寄存器,设置上拉/下拉、驱动强度、压摆率等参数。

static int virtual_pinconf_set(struct pinctrl_dev *pctldev,unsigned pin_id,           // 引脚编号unsigned long *configs,    // 配置值数组unsigned num_configs)      // 配置数量

实际硬件中的典型实现

1. i.MX6ULL 的实际实现示例

static int imx_pinconf_set(struct pinctrl_dev *pctldev,unsigned pin_id, unsigned long *configs,unsigned num_configs)
{struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);const struct imx_pin_reg *pin_reg = &ipctl->pin_regs[pin_id];u32 reg_val;int i;for (i = 0; i < num_configs; i++) {unsigned long config = configs[i];u32 param = pinconf_to_config_param(config);u32 arg = pinconf_to_config_argument(config);switch (param) {case PIN_CONFIG_BIAS_PULL_UP:/* 配置上拉电阻 */reg_val = readl(ipctl->base + pin_reg->conf_reg);reg_val |= BIT(IMX_PULL_UP_BIT);writel(reg_val, ipctl->base + pin_reg->conf_reg);break;case PIN_CONFIG_BIAS_PULL_DOWN:/* 配置下拉电阻 */reg_val = readl(ipctl->base + pin_reg->conf_reg);reg_val |= BIT(IMX_PULL_DOWN_BIT);writel(reg_val, ipctl->base + pin_reg->conf_reg);break;case PIN_CONFIG_DRIVE_STRENGTH:/* 配置驱动强度 */reg_val = readl(ipctl->base + pin_reg->conf_reg);reg_val &= ~IMX_DRIVE_STRENGTH_MASK;reg_val |= (arg << IMX_DRIVE_STRENGTH_SHIFT);writel(reg_val, ipctl->base + pin_reg->conf_reg);break;case PIN_CONFIG_SLEW_RATE:/* 配置压摆率 */reg_val = readl(ipctl->base + pin_reg->conf_reg);if (arg)reg_val |= BIT(IMX_SLEW_RATE_BIT);  /* 快速 */elsereg_val &= ~BIT(IMX_SLEW_RATE_BIT); /* 慢速 */writel(reg_val, ipctl->base + pin_reg->conf_reg);break;default:return -ENOTSUPP;}}return 0;
}

具体配置场景示例

配置 I2C 引脚的电气特性

设备树配置:
&iomuxc {i2c1_pins: i2c1grp {fsl,pins = <MX6UL_PAD_UART4_RX_DATA__I2C1_SDA  0x4001b8b0MX6UL_PAD_UART4_TX_DATA__I2C1_SCL  0x4001b8b0>;};
};
配置值 0x4001b8b0 解析:
  • 0x4001b8b0 是一个 32 位的配置值,包含多个电气特性设置

0x4001b8b0 二进制: 0100 0000 0000 0001 1011 1000 1011 0000
按位域解析:
- HYS (bit 16):    0 - 禁用滞回器
- PUS (bit15-14):  10 - 22K 上拉
- PUE (bit 13):    1  - 上拉/下拉使能
- PKE (bit 12):    1  - 保持器使能
- ODE (bit 11):    0  - 开漏禁用
- SPEED (bit7-6):  10 - 中速 (100MHz)
- DSE (bit5-3):    011 - 驱动强度 R0/3 (40 ohm)
- SRE (bit 0):     0  - 慢速压摆率
函数调用过程:
  1. 设备树解析 → 提取配置值

  2. pinctrl 子系统 → 调用 virtual_pinconf_set

  3. 实际硬件配置

// 对于引脚 MX6UL_PAD_UART4_RX_DATA (假设 pin_id = 0)
// configs[0] = 0x4001b8b0virtual_pinconf_set(pctldev, 0, &config, 1);
实际硬件寄存器操作:
// 假设配置寄存器地址为 IOMUXC_SW_PAD_CTL_PAD_UART4_RX_DATA (0x046C)// 读取当前配置
reg_val = readl(base + 0x046C);// 根据 0x4001b8b0 配置各个位域
reg_val &= ~0xFFFFBFFF;  // 清除相关位
reg_val |= 0x4001B8B0;   // 设置新值// 写入配置寄存器
writel(reg_val, base + 0x046C);

使用标准 pinconf 参数

在实际驱动中,通常使用标准的 pinconf 参数:

2. 使用标准配置参数的实现

static int imx_pinconf_set(struct pinctrl_dev *pctldev,unsigned pin_id, unsigned long *configs,unsigned num_configs)
{struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);const struct imx_pin_reg *pin_reg = &ipctl->pin_regs[pin_id];u32 reg_val;int i;reg_val = readl(ipctl->base + pin_reg->conf_reg);for (i = 0; i < num_configs; i++) {unsigned long config = configs[i];u32 param = pinconf_to_config_param(config);u32 arg = pinconf_to_config_argument(config);switch (param) {case PIN_CONFIG_BIAS_PULL_UP:/* 上拉电阻: 设置 PUS=10, PUE=1, PKE=1 */reg_val &= ~(0x3 << 14);  // 清除 PUSreg_val |= (0x2 << 14);   // 设置 22K 上拉reg_val |= (1 << 13);     // PUE=1reg_val |= (1 << 12);     // PKE=1break;case PIN_CONFIG_BIAS_PULL_DOWN:/* 下拉电阻: 设置 PUS=00, PUE=1, PKE=1 */reg_val &= ~(0x3 << 14);  // 清除 PUSreg_val &= ~(1 << 13);    // PUE=0 (下拉)reg_val |= (1 << 12);     // PKE=1break;case PIN_CONFIG_DRIVE_STRENGTH:/* 驱动强度: 设置 DSE 位域 */reg_val &= ~(0x7 << 3);   // 清除 DSEif (arg <= 260)           // 根据阻值设置 DSEreg_val |= (0x7 << 3); // R0/1 (240 ohm @ 3.3V)else if (arg <= 540)reg_val |= (0x6 << 3); // R0/2 (120 ohm)elsereg_val |= (0x3 << 3); // R0/3 (60 ohm)break;case PIN_CONFIG_SLEW_RATE:/* 压摆率: 设置 SRE 位 */if (arg)reg_val &= ~(1 << 0); // 快速压摆率elsereg_val |= (1 << 0);  // 慢速压摆率break;case PIN_CONFIG_INPUT_ENABLE:/* 输入使能: 设置 HYS 位 */if (arg)reg_val |= (1 << 16); // 使能滞回器elsereg_val &= ~(1 << 16);// 禁用滞回器break;default:return -ENOTSUPP;}}writel(reg_val, ipctl->base + pin_reg->conf_reg);return 0;
}

virtual_pinconf_set 函数的作用:

  • 核心功能:配置引脚的电气特性参数

  • 配置内容:上拉/下拉电阻、驱动强度、压摆率、开漏输出等

  • 实际硬件:写入 SoC 的引脚配置寄存器

  • 使用场景:设置引脚的电气特性以适应不同的外设需求

与 virtual_pmx_set 的区别:

  • pinmux_set:设置引脚的功能(I2C、UART、GPIO 等)

  • pinconf_set:设置引脚的电气特性(上拉、驱动能力等)

PinCtrl_Client

1. 设备树匹配表

static const struct of_device_id virtual_client_of_match[] = {{ .compatible = "100ask,virtual_i2c", },{ },
};
  • 作用:定义与设备树节点匹配的兼容性字符串

  • 匹配规则:当设备树中有 compatible = "100ask,virtual_i2c" 的节点时,这个驱动会被触发

2. probe 和 remove 函数

static int virtual_client_probe(struct platform_device *pdev)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static int virtual_client_remove(struct platform_device *pdev)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}
  • probe:当设备匹配时调用,用于设备初始化

  • remove:当设备移除或驱动卸载时调用,用于资源清理

3. platform_driver 定义

static struct platform_driver virtual_client_driver = {.probe      = virtual_client_probe,.remove     = virtual_client_remove,.driver     = {.name   = "100ask_virtual_client",.of_match_table = of_match_ptr(virtual_client_of_match),}
};
  • 驱动名称"100ask_virtual_client"

  • 匹配表:使用设备树进行匹配

4. 模块初始化和退出

static int __init virtual_client_init(void)
{   printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return platform_driver_register(&virtual_client_driver);
}static void __exit virtual_client_exit(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);platform_driver_unregister(&virtual_client_driver);
}

完整的使用示例

1. 设备树配置示例

假设我们有一个虚拟的 I2C 设备,需要在设备树中描述:

/* 在设备树文件中添加 */
/ {virtual_i2c_dev: virtual_i2c@100 {compatible = "100ask,virtual_i2c";reg = <0x100 0x10>;           /* 假设的寄存器地址范围 */status = "okay";/* 引脚配置(如果使用pinctrl) */pinctrl-names = "default", "sleep";pinctrl-0 = <&virtual_i2c_pins_default>;pinctrl-1 = <&virtual_i2c_pins_sleep>;/* 设备特定属性 */clock-frequency = <100000>;    /* I2C 时钟频率 */device-address = <0x50>;       /* 设备地址 */};
};/* 引脚控制配置(如果使用) */
&virtual_pinctrl {virtual_i2c_pins_default: virtual_i2c_default {functions = "i2c", "i2c";groups = "pin0", "pin1";configs = <0x11223344 0x55667788>;};virtual_i2c_pins_sleep: virtual_i2c_sleep {functions = "gpio", "gpio";groups = "pin0", "pin1";configs = <0x00000000 0x00000000>;};
};

2. 完整的客户端驱动实现

#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/consumer.h>  /* 添加 pinctrl 支持 */
#include <linux/slab.h>
#include <linux/regmap.h>struct virtual_i2c_data {struct device *dev;struct pinctrl *pinctrl;struct pinctrl_state *default_state;struct pinctrl_state *sleep_state;u32 clock_frequency;u32 device_address;void __iomem *reg_base;
};static const struct of_device_id virtual_client_of_match[] = {{ .compatible = "100ask,virtual_i2c", },{ },
};static int virtual_client_probe(struct platform_device *pdev)
{struct virtual_i2c_data *data;struct device_node *np = pdev->dev.of_node;int ret;printk("%s %s %d: Probing virtual I2C device\n", __FILE__, __FUNCTION__, __LINE__);/* 1. 分配设备私有数据结构 */data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);if (!data)return -ENOMEM;data->dev = &pdev->dev;/* 2. 获取 pinctrl 配置 */data->pinctrl = devm_pinctrl_get(&pdev->dev);if (IS_ERR(data->pinctrl)) {dev_err(&pdev->dev, "Failed to get pinctrl\n");return PTR_ERR(data->pinctrl);}/* 3. 查找引脚状态 */data->default_state = pinctrl_lookup_state(data->pinctrl, "default");if (IS_ERR(data->default_state)) {dev_err(&pdev->dev, "Failed to get default pinctrl state\n");return PTR_ERR(data->default_state);}data->sleep_state = pinctrl_lookup_state(data->pinctrl, "sleep");if (IS_ERR(data->sleep_state)) {dev_warn(&pdev->dev, "No sleep pinctrl state found\n");}/* 4. 应用默认引脚配置 */ret = pinctrl_select_state(data->pinctrl, data->default_state);if (ret) {dev_err(&pdev->dev, "Failed to set default pinctrl state\n");return ret;}/* 5. 解析设备树属性 */if (of_property_read_u32(np, "clock-frequency", &data->clock_frequency))data->clock_frequency = 100000;  /* 默认值 */if (of_property_read_u32(np, "device-address", &data->device_address))data->device_address = 0x50;     /* 默认值 *//* 6. 映射寄存器(如果是真实硬件) */data->reg_base = devm_platform_ioremap_resource(pdev, 0);if (IS_ERR(data->reg_base)) {dev_err(&pdev->dev, "Failed to map registers\n");return PTR_ERR(data->reg_base);}/* 7. 保存私有数据 */platform_set_drvdata(pdev, data);/* 8. 初始化硬件(虚拟驱动中跳过) */dev_info(&pdev->dev, "Virtual I2C device probed: clock=%dHz, address=0x%x\n",data->clock_frequency, data->device_address);return 0;
}static int virtual_client_remove(struct platform_device *pdev)
{struct virtual_i2c_data *data = platform_get_drvdata(pdev);printk("%s %s %d: Removing virtual I2C device\n", __FILE__, __FUNCTION__, __LINE__);/* 在移除时可以选择切换到睡眠状态 */if (data->sleep_state && !IS_ERR(data->sleep_state))pinctrl_select_state(data->pinctrl, data->sleep_state);return 0;
}/* 电源管理支持 */
#ifdef CONFIG_PM
static int virtual_client_suspend(struct device *dev)
{struct virtual_i2c_data *data = dev_get_drvdata(dev);if (data->sleep_state && !IS_ERR(data->sleep_state))return pinctrl_select_state(data->pinctrl, data->sleep_state);return 0;
}static int virtual_client_resume(struct device *dev)
{struct virtual_i2c_data *data = dev_get_drvdata(dev);return pinctrl_select_state(data->pinctrl, data->default_state);
}static const struct dev_pm_ops virtual_client_pm_ops = {.suspend = virtual_client_suspend,.resume = virtual_client_resume,
};
#endifstatic struct platform_driver virtual_client_driver = {.probe      = virtual_client_probe,.remove     = virtual_client_remove,.driver     = {.name   = "100ask_virtual_client",.of_match_table = of_match_ptr(virtual_client_of_match),
#ifdef CONFIG_PM.pm     = &virtual_client_pm_ops,
#endif}
};/* 模块初始化和退出 */
static int __init virtual_client_init(void)
{   printk("%s %s %d: Initializing virtual I2C client driver\n", __FILE__, __FUNCTION__, __LINE__);return platform_driver_register(&virtual_client_driver);
}static void __exit virtual_client_exit(void)
{printk("%s %s %d: Exiting virtual I2C client driver\n", __FILE__, __FUNCTION__, __LINE__);platform_driver_unregister(&virtual_client_driver);
}module_init(virtual_client_init);
module_exit(virtual_client_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("100ask");
MODULE_DESCRIPTION("Virtual I2C Client Driver Example");

工作流程

1. 系统启动时

# 加载驱动
insmod virtual_client.ko# 内核日志输出:
# virtual_client.c virtual_client_init 123: Initializing virtual I2C client driver

2. 设备匹配和探测

# 当设备树中存在 compatible = "100ask,virtual_i2c" 的节点时:
# virtual_client.c virtual_client_probe 45: Probing virtual I2C device
# 应用引脚配置,初始化设备

3. 设备移除

# 卸载驱动时:
rmmod virtual_client# 内核日志输出:
# virtual_client.c virtual_client_remove 89: Removing virtual I2C device
# virtual_client.c virtual_client_exit 145: Exiting virtual I2C client driver

实际应用场景

这种客户端驱动模式常用于:

1. I2C 设备驱动

// 真实 I2C 驱动会注册 i2c_driver
static struct i2c_driver real_i2c_driver = {.driver = {.name = "real_i2c_device",.of_match_table = real_i2c_of_match,},.probe = real_i2c_probe,.remove = real_i2c_remove,.id_table = real_i2c_id,
};

2. SPI 设备驱动

// SPI 设备同样使用类似的模式
static struct spi_driver real_spi_driver = {.driver = {.name = "real_spi_device",.of_match_table = real_spi_of_match,},.probe = real_spi_probe,.remove = real_spi_remove,
};

http://www.dtcms.com/a/491852.html

相关文章:

  • 动力无限西安网站建设网络推广是网络营销的基础
  • 如何在conda虚拟环境中设置CUDA_HOME变量
  • 建设厅试验员考试报名网站兰州公司做网站
  • 人工智能的本质是什么
  • SpringBoot-依赖管理和自动配置
  • 网站seo优化有哪些方面定制一款app要多少钱
  • 南通建设工程造价信息网站网站开发需要解决难题
  • 摄影网站开发的意义企业网站搭建步骤
  • 做网站的好处和坏处厦网站建设培训学校
  • CS50ai: week2 Uncertainty我的笔记B版:一口气从“爬山”到“退火”, 优化与CSP超好懂入门
  • 第十章:技术路线:成为“技术扫地僧”(3)
  • cocos creator学习之typeScript常见语法问题
  • 公司网站突然打不开了品牌网站建设 蝌蚪6小
  • 网站 没有备案 访问不了工商联网站建设方案
  • 存在即合理?数字化转型需破除“流程惯性“思维、重构底层逻辑
  • 南阳企业网站制作做网站推广和网络推广
  • 专业网站建站费用wordpress当前网址参数
  • 天猫网站是用什么技术做的东莞响应式网站哪家强
  • 茶文化网站建设内容网络管理软件免费
  • RocketMQ基础知识
  • 天气预报:基于python天气分析预测系统 气象数据分析 机器学习 爬虫 多元线性回归预测算法 中国天气网 Flask框架(建议收藏)✅
  • React 18 的核心设计理念:并发渲染
  • 昆明 网站建设兼职北京石景山网站建设
  • 中小型网站建设资讯网站及数据库怎么做后门
  • 建设网站财务分析wordpress中修改链接
  • 网站模板 实验室西安国际网站设计
  • 建设实验室网站的意义湖南长沙房价
  • 行业热点丨仿真驱动设计:兼顾性能、可持续性与效益
  • 番禺建设网站策划南充房产管理网
  • 网站建设xywlcn网站云主机