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
数组如下:
索引 | 类型 | 功能 | 引脚 | 配置值 |
---|---|---|---|---|
0 | MUX_GROUP | "i2c" | "pin0" | - |
1 | CONFIGS_PIN | - | "pin0" | 0x11223344 |
2 | MUX_GROUP | "i2c" | "pin1" | - |
3 | CONFIGS_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";
};
函数调用过程:
-
设备树解析 → 创建映射
-
I2C1 驱动探测 → 查找 "default" 状态
-
调用
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 - 慢速压摆率
函数调用过程:
-
设备树解析 → 提取配置值
-
pinctrl 子系统 → 调用
virtual_pinconf_set
-
实际硬件配置:
// 对于引脚 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,
};