一、背景
cpu的gpio引脚可以复用成多个功能,如可以配置成I2C或者普通GPIO模式。配置方式一般是通过写引脚复用的配置寄存器,但是不同芯片厂商配置寄存器格式内容各不相同,设置引脚复用无法做到通用且自由的配置,只能在启动初始化时候在soc驱动初始化时对每个引脚配置好。 linux 3.0之后内核中抽象出了pinctrl子系统,每个soc注册设置引脚复用的方法以及将soc的每个引脚可选的复用功能。
二、pinctrl实现
2.1 API
# 用于soc向pinctrl系统中注册设置引脚复用方法以及设置引脚复用状态
# driver_data用于soc驱动私有数据,一般用来保存pin 引脚的可选复用功能列表。
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,struct device *dev, void *driver_data)
# 设置引脚复用状态,用来设置引脚复用状态
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
# 用于具体设备驱动(i2c等)关联pinctrl节点,以及设置默认复用模式,设备树解析时调用
int pinctrl_bind_pins(struct device *dev)
2.2 数据结构
数据结构关系图:
这里主要有几个对象:
- pin_group 和function : 引脚复用一般是多个引脚组成一个group,一起设置复用状态的。多个引脚复用为某个功能,成为function。
- pin_conf :设置引脚的上下拉电阻等电气参数
- pin_mux:设置引脚复用
pinctrl_desc
核心数据结构是pinctrl_desc,pinctrl的全局描述。
struct pinctrl_desc {
const char *name;
/*系统种有多少个pinctrl*/
const struct pinctrl_pin_desc *pins;
unsigned int npins;
/*引脚控制的操作集,不同芯片方案不同*/
const struct pinctrl_ops *pctlops;
const struct pinmux_ops *pmxops;
const struct pinconf_ops *confops;
struct module *owner;
};
pinctrl_pin_desc
描述系统中的所有引脚,drv_data保存驱动私有数据。
struct pinctrl_pin_desc {
unsigned number;
const char *name;
void *drv_data;
};
pinctrl_ops
回调函数,获取pin group的操作,以及解析dts。
struct pinctrl_ops {
/*获取系统中有多少个pin groups*/
int (*get_groups_count) (struct pinctrl_dev *pctldev);
/*获取指定group(由索引selector指定)的名称,由select确认*/
const char *(*get_group_name) (struct pinctrl_dev *pctldev,
unsigned selector);
/*获取指定group的所用pin信息*/
int (*get_group_pins) (struct pinctrl_dev *pctldev,
unsigned selector,
const unsigned **pins,
unsigned *num_pins);
void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,
unsigned offset);
/*将对应驱动转换成pin map*/
int (*dt_node_to_map) (struct pinctrl_dev *pctldev,
struct device_node *np_config,
struct pinctrl_map **map, unsigned *num_maps);
void (*dt_free_map) (struct pinctrl_dev *pctldev,
struct pinctrl_map *map, unsigned num_maps);
};
pinconf_ops
配置管脚状态:pinctrl也提供管脚状态如(上下拉、开漏等)的接口。
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
bool is_generic;
#endif
/*获取pin脚的当前状态*/
int (*pin_config_get) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *config);
/*设置pin脚状态*/
int (*pin_config_set) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *configs,
unsigned num_configs);
/*获取或者设置指定pin group的配置项*/
int (*pin_config_group_get) (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *config);
int (*pin_config_group_set) (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *configs,
unsigned num_configs);
int (*pin_config_dbg_parse_modify) (struct pinctrl_dev *pctldev,
const char *arg,
unsigned long *config);
void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev,
struct seq_file *s,
unsigned offset);
void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev,
struct seq_file *s,
unsigned selector);
void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev,
struct seq_file *s,
unsigned long config);
};
pinmux_ops
设置或获取引脚复用情况,对应硬件是iomux。设置某个pin为某个function,通过set_mux设置function selecter。 一个group设置复用状态。
struct pinmux_ops {
int (*request) (struct pinctrl_dev *pctldev, unsigned offset);
int (*free) (struct pinctrl_dev *pctldev, unsigned offset);
int (*get_functions_count) (struct pinctrl_dev *pctldev);
const char *(*get_function_name) (struct pinctrl_dev *pctldev,
unsigned selector);
int (*get_function_groups) (struct pinctrl_dev *pctldev,
unsigned selector,
const char * const **groups,
unsigned *num_groups);
/*将指定的pin group(group_selector)设置为指定的function(func_selector)*/
int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,
unsigned group_selector);
int (*gpio_request_enable) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
void (*gpio_disable_free) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
int (*gpio_set_direction) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset,
bool input);
bool strict;
};
pinctrl_map
pinctrl_map 某个gpio具体的引脚配置和复用状态,由dts中定义,dt_node_to_map函数解析,和具体设备驱动关联。
struct pinctrl_map {
//device的名称
const char *dev_name;
//pin state的名称
const char *name;
//该map的类型
enum pinctrl_map_type type;
//pin controller device的名字
const char *ctrl_dev_name;
union {
struct pinctrl_map_mux mux;
struct pinctrl_map_configs configs;
} data;
};
enum pinctrl_map_type {
PIN_MAP_TYPE_INVALID,
//不需要任何配置,仅仅为了表示state的存在
PIN_MAP_TYPE_DUMMY_STATE,
//配置管脚复用
PIN_MAP_TYPE_MUX_GROUP,
//配置pin
PIN_MAP_TYPE_CONFIGS_PIN,
//配置pin group
PIN_MAP_TYPE_CONFIGS_GROUP,
};
struct pinctrl_map_mux {
//group的名字
const char *group;
//function的名字
const char *function;
};
struct pinctrl_map_configs {
//该pin或者pin group的名字
const char *group_or_pin;
//configuration数组
unsigned long *configs;
//配置项的个数
unsigned num_configs;
};
三、实现流程
linux6.1.11为例,pinctrl系统融入内核设备树解析以及内核设备驱动模型中支持。以zynq为例从dts配置解析入手,了解pinctrl的工作原理。dts中主要关注:
1)pinctrl 子系统:描述系统多少引脚,每个引脚有多少复用情况。
2)I2C、GPIO、SPI等driver默认配置的pinctrl复用设置
《zynq-7000.dtsi》中,如列出i2C和uart的复用配置,其中只是选择了i2c0_10_grp这个pin_group,并且配置为i2c0的复用功能,具体引脚的引脚号等信息是在代码中定义的。
mux 和 conf的作用?
pinctrl0: pinctrl@700 {
compatible = "xlnx,pinctrl-zynq";
reg = <0x700 0x200>;
syscon = <&slcr>;
/*设置引脚的复用状态*/
pinctrl_i2c0_default: i2c0-default {
mux {
/*pin_group*/
groups = "i2c0_10_grp";
/*复用功能为i2c0*/
function = "i2c0";
};
conf {
groups = "i2c0_10_grp";
bias-pull-up; #上拉电阻
slew-rate = <0>; #引脚转换速率
io-standard = <1>;
};
};
pinctrl_uart1_default: uart1-default {
mux {
groups = "uart1_10_grp";
function = "uart1";
};
conf {
groups = "uart1_10_grp";
slew-rate = <0>;
io-standard = <1>;
};
conf-rx {
pins = "MIO49";
bias-high-impedance;
};
conf-tx {
pins = "MIO48";
bias-disable;
};
};
}
- 其他驱动如何关联pinctrl以及配置默认引脚复用?
对应驱动中的dts新增如下两个属性,在设备驱动device关联driver时,会解析如下字段,关联pinctrl驱动,并设置引脚复用状态,这里是选择pinctrl_i2c0_default。
&i2c0 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c0_default>;
3.1 驱动注册
《linux-6.1.11\drivers\pinctrl\pinctrl-zynq.c》
构造一个pinctrl_desc结构,然后注册到pinctrl系统中。
static struct pinctrl_desc zynq_desc = {
.name = "zynq_pinctrl",
.pins = zynq_pins,
.npins = ARRAY_SIZE(zynq_pins),
.pctlops = &zynq_pctrl_ops,
.pmxops = &zynq_pinmux_ops,
.confops = &zynq_pinconf_ops,
.num_custom_params = ARRAY_SIZE(zynq_dt_params),
.custom_params = zynq_dt_params,
#ifdef CONFIG_DEBUG_FS
.custom_conf_items = zynq_conf_items,
#endif
.owner = THIS_MODULE,
};
static int zynq_pinctrl_probe(struct platform_device *pdev)
{
pctrl->pctrl = devm_pinctrl_register(&pdev->dev, &zynq_desc, pctrl);
}
pin引脚定义:
/*定义系统中引脚*/
static const struct pinctrl_pin_desc zynq_pins[] = {
PINCTRL_PIN(0, "MIO0"),
PINCTRL_PIN(1, "MIO1"),
...
}
/*引脚可选复用功能*/
enum zynq_pinmux_functions {
ZYNQ_PMUX_can0,
ZYNQ_PMUX_can1,
ZYNQ_PMUX_ethernet0,
ZYNQ_PMUX_ethernet1,
}
/* pin groups 组,每个引脚功能对应的pin脚定义*/
static const unsigned int ethernet0_0_pins[] = {16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27};
static const unsigned int ethernet1_0_pins[] = {28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39};
static const unsigned int mdio0_0_pins[] = {52, 53};
static const unsigned int mdio1_0_pins[] = {52, 53};
/*zynq维护的私有数据结构pin_group组信息,关联上述pin脚*/
static const struct zynq_pctrl_group zynq_pctrl_groups[] = {
DEFINE_ZYNQ_PINCTRL_GRP(ethernet0_0),
DEFINE_ZYNQ_PINCTRL_GRP(ethernet1_0),
DEFINE_ZYNQ_PINCTRL_GRP(mdio0_0),
DEFINE_ZYNQ_PINCTRL_GRP(mdio1_0),
DEFINE_ZYNQ_PINCTRL_GRP(qspi0_0),
DEFINE_ZYNQ_PINCTRL_GRP(qspi1_0),
DEFINE_ZYNQ_PINCTRL_GRP(qspi_fbclk),
DEFINE_ZYNQ_PINCTRL_GRP(qspi_cs1),
DEFINE_ZYNQ_PINCTRL_GRP(spi0_0),
}
对应的ops实现这里不展开,具体写寄存器信息。
3.2 具体驱动关联pinctrl
具体i2c、spi等驱动初始化时如何设置引脚复用情况? dts中定义i2c选择pinctrl_i2c0_default的引脚复用。 pinctrl-names用于表示pinctrl_state,有init、default,init是在驱动初始化的状态,default是默认状态。 pinctrl-0 选择复用情况,这两个属性是pinctrl的标准属性,设备树解析框架中实现,在设备驱动匹配时关联。
&i2c0 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c0_default>;
时机:设备驱动匹配时关联,以及设置默认复用状态。
关键函数:pinctrl_bind_pins
《linux-6.1.11\drivers\base\dd.c》
__driver_probe_device -> really_probe -> pinctrl_bind_pins
- 解析dts,关联pinctrl_map
调用pinconf_generic_dt_node_to_map_all解析dts。关联pinctrl_i2c0_default的pinctrl_map。
pinctrl_bind_pins -> create_pinctrl -> pinctrl_dt_to_map -> pinconf_generic_dt_node_to_map_all
- 设置引脚复用
pinctrl_bind_pins -> pinctrl_select_state (选择default state) -> pinctrl_commit_state
-> pinmux_disable_setting (禁用旧的复用)
-> pinmux_enable_setting (设置新复用)
-> pinconf_apply_setting (设置引脚状态)
/**
* pinctrl_bind_pins() - called by the device core before probe
* @dev: the device that is just about to probe
*/
int pinctrl_bind_pins(struct device *dev)
{
int ret;
if (dev->of_node_reused)
return 0;
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
if (!dev->pins)
return -ENOMEM;
dev->pins->p = devm_pinctrl_get(dev);
if (IS_ERR(dev->pins->p)) {
dev_dbg(dev, "no pinctrl handle\n");
ret = PTR_ERR(dev->pins->p);
goto cleanup_alloc;
}
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_DEFAULT);
if (IS_ERR(dev->pins->default_state)) {
dev_dbg(dev, "no default pinctrl state\n");
ret = 0;
goto cleanup_get;
}
dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_INIT);
if (IS_ERR(dev->pins->init_state)) {
/* Not supplying this state is perfectly legal */
dev_dbg(dev, "no init pinctrl state\n");
ret = pinctrl_select_state(dev->pins->p,
dev->pins->default_state);
} else {
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
}
if (ret) {
dev_dbg(dev, "failed to activate initial pinctrl state\n");
goto cleanup_get;
}
#ifdef CONFIG_PM
/*
* If power management is enabled, we also look for the optional
* sleep and idle pin states, with semantics as defined in
* <linux/pinctrl/pinctrl-state.h>
*/
dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_SLEEP);
if (IS_ERR(dev->pins->sleep_state))
/* Not supplying this state is perfectly legal */
dev_dbg(dev, "no sleep pinctrl state\n");
dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_IDLE);
if (IS_ERR(dev->pins->idle_state))
/* Not supplying this state is perfectly legal */
dev_dbg(dev, "no idle pinctrl state\n");
#endif
return 0;
/*
* If no pinctrl handle or default state was found for this device,
* let's explicitly free the pin container in the device, there is
* no point in keeping it around.
*/
cleanup_get:
devm_pinctrl_put(dev->pins->p);
cleanup_alloc:
devm_kfree(dev, dev->pins);
dev->pins = NULL;
/* Return deferrals */
if (ret == -EPROBE_DEFER)
return ret;
/* Return serious errors */
if (ret == -EINVAL)
return ret;
/* We ignore errors like -ENOENT meaning no pinctrl state */
return 0;
}