0、说明
pinctl子系统完成引脚列举、复用、上下拉配置速率等。pinctl是linux抽象出来的一个虚拟控制器,硬件并不存在,为了更加便捷的去配置引脚复用功能和配置而出现。重点在于如何用pinctl,以及pinctl实现的思路。
pinctrl的出现的意义:解放引脚复用和配置代码。BSP实现整体功能,用户配置设备树一步完成配置。
本文重点分析,如何使用pinctrl,pintctrl的设计思路或者说pintctrl实现过程。
1、环境
1.1 硬件环境
- imx6ull开发板
1.2 软件环境
- VM ubuntu 18.04
- windows 10
2、pinctl基础
2.0 pinctl的使用和pinctrl子系统实现思路
pinctl出现后,我们希望配置硬件复位功能的时候,在设备树中通过如下实现。这样我们就配置了串口3的管脚配置。pinctrl-names+pinctrl-0组合。
&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
status = "okay";
};
如下,设置了网卡1的硬件配置。我们不在需要一个一个寄存器去设置让这些引脚配置成网卡,我们只需要设备树指定,然后由BSP框架实现了初始化。当然有些时候需要我们自己去定义功能和配置参数。
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <ðphy0>;
phy-reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
phy-reset-duration = <26>;
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@0 {
compatible = "ethernet-phy-ieee802.3-c22";
smsc,disable-energy-detect;
reg = <0>;
};
ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
smsc,disable-energy-detect;
reg = <1>;
};
};
};
一些设备树资源会解析成platform_device,在probe的时候,会解析上面pinctrl信息,并设置引脚状态。
pinctrl控制器用于管理单个引脚或者引脚组,同时提供引脚设置函数,从而操作寄存器,实现引脚配置。
2.1 代码路径
drivers/pinctrl/core.c
drivers/pinctrl/devicetree.c
drivers/pinctrl/freescale/pinctrl-imx6ul.c
drivers/pinctrl/freescale/pinctrl-imx.c
2.2 pinctl的作用
- 管理并列举所有的引脚或者引脚组
- 引脚复用功能选择,是gpio还是i2c
- 引脚配置,上下拉,驱动能力等
2.3 设备树中的pinctl
对于pinctl controller在设备中的形式如下:
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
pinctrl_zigbee_default: zigbee-gpio {
fsl,pins = <
MX6UL_PAD_JTAG_TMS__GPIO1_IO11 0x1b0b0
MX6UL_PAD_JTAG_TDO__GPIO1_IO12 0x1b0b0
MX6UL_PAD_JTAG_TRST_B__GPIO1_IO15 0x1b0b0
>;
};
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
MX6UL_PAD_JTAG_MOD__GPIO1_IO10 0x17059 /* WiFi module power */
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x17059 /* USB OTG1 ID */
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x1b0b0 /* LCD_DISP */
>;
};
pinctrl_wifi: wifigrp {
fsl,pins = <
MX6UL_PAD_JTAG_MOD__GPIO1_IO10 0x17059 /* WiFi module power */
>;
};
...
}
}
对于pinctl在设备端使用设备树一般形式如下:
&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
status = "okay";
};
2.4 pinctl 数据结构
描述ZYNQ pinctl控制器(imx_pinctrl)
struct imx_pinctrl {
struct device *dev;
struct pinctrl_dev *pctl;
void __iomem *base;
void __iomem *input_sel_base;
const struct imx_pinctrl_soc_info *info;
};
pinctrl_dev
struct pinctrl_dev {
struct list_head node;
struct pinctrl_desc *desc;
struct radix_tree_root pin_desc_tree;
#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
struct radix_tree_root pin_group_tree;
unsigned int num_groups;
#endif
#ifdef CONFIG_GENERIC_PINMUX_FUNCTIONS
struct radix_tree_root pin_function_tree;
unsigned int num_functions;
#endif
struct list_head gpio_ranges;
struct device *dev;
struct module *owner;
void *driver_data;
struct pinctrl *p;
struct pinctrl_state *hog_default;
struct pinctrl_state *hog_sleep;
struct mutex mutex;
#ifdef CONFIG_DEBUG_FS
struct dentry *device_root;
#endif
};
pinctrl_desc (重点分析成员)
struct pinctrl_desc {
const char *name; //“zynq_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;
#ifdef CONFIG_GENERIC_PINCONF
unsigned int num_custom_params;
const struct pinconf_generic_params *custom_params;
const struct pin_config_item *custom_conf_items;
#endif
bool link_consumers;
};
pinctrl_pin_desc
/**
* struct pinctrl_pin_desc - boards/machines provide information on their
* pins, pads or other muxable units in this struct
* @number: unique pin number from the global pin number space
* @name: a name for this pin
* @drv_data: driver-defined per-pin data. pinctrl core does not touch this
*/
struct pinctrl_pin_desc {
unsigned number;
const char *name;
void *drv_data;
};
zynq_pctrl_group
struct zynq_pctrl_group {
const char *name;
const unsigned int *pins;
const unsigned int npins;
};
pinctrl_ops
/**
* struct pinctrl_ops - global pin control operations, to be implemented by
* pin controller drivers.
* @get_groups_count: Returns the count of total number of groups registered.
* @get_group_name: return the group name of the pin group
* @get_group_pins: return an array of pins corresponding to a certain
* group selector @pins, and the size of the array in @num_pins
* @pin_dbg_show: optional debugfs display hook that will provide per-device
* info for a certain pin in debugfs
* @dt_node_to_map: parse a device tree "pin configuration node", and create
* mapping table entries for it. These are returned through the @map and
* @num_maps output parameters. This function is optional, and may be
* omitted for pinctrl drivers that do not support device tree.
* @dt_free_map: free mapping table entries created via @dt_node_to_map. The
* top-level @map pointer must be freed, along with any dynamically
* allocated members of the mapping table entries themselves. This
* function is optional, and may be omitted for pinctrl drivers that do
* not support device tree.
*/
struct pinctrl_ops {
int (*get_groups_count) (struct pinctrl_dev *pctldev);
const char *(*get_group_name) (struct pinctrl_dev *pctldev,
unsigned selector);
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);
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);
};
pinmux_ops
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);
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;
};
pinconf_ops
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
bool is_generic;
#endif
int (*pin_config_get) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *config);
int (*pin_config_set) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *configs,
unsigned num_configs);
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);
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);
};
3、 pinctl控制器分析
设备树中有了compatible = "xlnx,pinctrl-zynq",自然会在启动阶段完成pinctl的probe过程,可以对应找到驱动程序位于pinctrl-zynq.c中,其他平台类似在pinctrl-xxx.c中。
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)
{
struct resource *res;
struct zynq_pinctrl *pctrl;
/* 申请 */
pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL);
if (!pctrl)
return -ENOMEM;
pctrl->syscon = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
"syscon");
if (IS_ERR(pctrl->syscon)) {
dev_err(&pdev->dev, "unable to get syscon\n");
return PTR_ERR(pctrl->syscon);
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "missing IO resource\n");
return -ENODEV;
}
/* 设置 */
pctrl->pctrl_offset = res->start;
pctrl->groups = zynq_pctrl_groups;
pctrl->ngroups = ARRAY_SIZE(zynq_pctrl_groups);
pctrl->funcs = zynq_pmux_functions;
pctrl->nfuncs = ARRAY_SIZE(zynq_pmux_functions);
/* 注册 */
pctrl->pctrl = devm_pinctrl_register(&pdev->dev, &zynq_desc, pctrl);
if (IS_ERR(pctrl->pctrl))
return PTR_ERR(pctrl->pctrl);
platform_set_drvdata(pdev, pctrl);
dev_info(&pdev->dev, "zynq pinctrl initialized\n");
return 0;
}
3.1 申请zynq_pinctrl并初始化
zynq_pinctrl描述了ZYNQ平台pinctl的信息。
主要成员:pinctrl_dev、zynq_pctrl_group(引脚组)、ngroups(组个数)
3.1.1 完成对组的初始化
pctrl->groups = zynq_pctrl_groups;
pctrl->ngroups = ARRAY_SIZE(zynq_pctrl_groups);
描述一个组:
struct zynq_pctrl_group {
const char *name; //名字
const unsigned int *pins; //组内引脚号
const unsigned int npins; //组内引脚个数
};
root@linux_prj:/sys/kernel/debug/pinctrl/700.pinctrl-zynq_pinctrl# cat pingroups
registered pin groups:
group: ethernet0_0_grp
pin 16 (MIO16)
pin 17 (MIO17)
pin 18 (MIO18)
pin 19 (MIO19)
pin 20 (MIO20)
pin 21 (MIO21)
pin 22 (MIO22)
pin 23 (MIO23)
pin 24 (MIO24)
pin 25 (MIO25)
pin 26 (MIO26)
pin 27 (MIO27)
group: ethernet1_0_grp
pin 28 (MIO28)
pin 29 (MIO29)
pin 30 (MIO30)
pin 31 (MIO31)
pin 32 (MIO32)
3.1.2 完成对function的初始化
pctrl->funcs = zynq_pmux_functions;
pctrl->nfuncs = ARRAY_SIZE(zynq_pmux_functions);
function: can0, groups = [ can0_0_grp can0_1_grp can0_2_grp can0_3_grp can0_4_grp can0_5_grp can0_6_grp can0_7_grp can0_8_grp can0_9_grp can0_10_grp ]
function: can1, groups = [ can1_0_grp can1_1_grp can1_2_grp can1_3_grp can1_4_grp can1_5_grp can1_6_grp can1_7_grp can1_8_grp can1_9_grp can1_10_grp can1_11_grp ]
function: ethernet0, groups = [ ethernet0_0_grp ]
function: ethernet1, groups = [ ethernet1_0_grp ]
3.2 注册pinctl
pctrl->pctrl = devm_pinctrl_register(&pdev->dev, &zynq_desc, pctrl);
3.3 zynq_desc
zynq_desc(struct pinctrl_desc),描述了pinctl支持的所有引脚,及相关ops。
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,
};
3.3.1 支持的引脚pins
pins、npins 描述了支持的引脚以及个数。pinctrl_pin_desc 结构体来描述一个引脚。
3.3.2 pinctrl_ops
static const struct pinctrl_ops zynq_pctrl_ops = {
.get_groups_count = zynq_pctrl_get_groups_count,
.get_group_name = zynq_pctrl_get_group_name,
.get_group_pins = zynq_pctrl_get_group_pins,
.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
.dt_free_map = pinctrl_utils_free_map,
};
get_groups_count:获取group数量
get_group_name :获取组的名字
zynq_pctrl_get_group_pins:获取组内引脚
dt_node_to_map :将设备树device转换为pinctrl_map
3.3.3 zynq_pinmux_ops
static const struct pinmux_ops zynq_pinmux_ops = {
.get_functions_count = zynq_pmux_get_functions_count,
.get_function_name = zynq_pmux_get_function_name,
.get_function_groups = zynq_pmux_get_function_groups,
.set_mux = zynq_pinmux_set_mux,
};
get_functions_count :获取function数量
get_function_name :获取function名字
get_function_groups :获取支持该function的组
set_mux :设置复用功能
3.3.4 zynq_pinconf_ops
static const struct pinconf_ops zynq_pinconf_ops = {
.is_generic = true,
.pin_config_get = zynq_pinconf_cfg_get,
.pin_config_set = zynq_pinconf_cfg_set,
.pin_config_group_set = zynq_pinconf_group_set,
};
完成获取与设置配置。
pinctrl_desc主要描述了访问pinctrl控制器的一些接口。其最重要的是为dt_node_to_map函数服务。从而将设备树中的配置,转化为最终的寄存器。
3.4 pinctrl 控制器端小节
pinctrl控制器端代码,主要任务:
- 完成对引脚、组的描述
- 提供dt_node_to_map可解析设备树转换为配置map
- 提供若干访问pin、group的接口为dt_node_to_map服务
- 提供复用配置、功能配置的接口
4、pinctl device节点的匹配与设置
pinctl驱动的probe只是完成了内核对所有引脚管理信息的一个统一。并未对具体的引脚进行配置。而在如上设备树一节,根据特殊的用途,如gmac,i2c,gpio等定义的pinctl信息,如何生效并完成配置是本节分析的重点。
如下面何时被配置:
&gpio0 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio0_default>;
};
gpio0,在驱动probe:xlnx,zynq-gpio-1.0时,完成对其配置。
driver_probe_device
really_probe
/* If using pinctrl, bind pins now before probing */
pinctrl_bind_pins(dev);
...
drv->probe(dev);
4.1 pinctrl_bind_pins(dev)函数
4.1.1 初始化dev_pin_info
device模型中含有一个dev_pin_info 里面记录pinctl信息。
struct device {
struct kobject kobj;
struct device *parent;
struct device_private *p;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set_drvdata/dev_get_drvdata */
...
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
...
}
/**
* struct dev_pin_info - pin state container for devices
* @p: pinctrl handle for the containing device
* @default_state: the default state for the handle, if found
* @init_state: the state at probe time, if found
* @sleep_state: the state at suspend time, if found
* @idle_state: the state at idle (runtime suspend) time, if found
*/
struct dev_pin_info {
struct pinctrl *p;
struct pinctrl_state *default_state;
struct pinctrl_state *init_state;
#ifdef CONFIG_PM
struct pinctrl_state *sleep_state;
struct pinctrl_state *idle_state;
#endif
};
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
dev->pins->p = devm_pinctrl_get(dev);
4.1.2 devm_pinctrl_get
devm_pinctrl_get
pinctrl_get(dev) //drivers/pinctrl/core.c
create_pinctrl(dev, NULL) //drivers/pinctrl/core.c
pinctrl_dt_to_map(p, pctldev);
for_each_maps(maps_node, i, map) {
add_setting(p, pctldev, map);
}
pinctrl_dt_to_map
pinctrl_dt_to_map
/* 处理多个state pinctrl-0 pinctrl-1 ... */
for (state = 0; ; state++) {
propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state);
prop = of_find_property(np, propname, &size);
list = prop->value;
/* 处理每个state内部的config:pinctrl_gpio0_default
pinctrl-0 = <&pinctrl_gpio0_default>; */
for (config = 0; config < size; config++) {
np_config = of_find_node_by_phandle(phandle);
dt_to_map_one_config(p, pctldev, statename,np_config);
}
}
dt_to_map_one_config
ops->dt_node_to_map(pctldev, np_config, &map, &num_maps);
5、实验
5.1 硬件连接
PS_LED连接至ZYNQ的MIO9引脚。
5.2 设备树文件
project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
/include/ "system-conf.dtsi"
/ {
alinxled {
compatible = "alinx-led";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led_default>;
alinxled-gpios = <&gpio0 9 0>;
};
};
&pinctrl0 {
pinctrl_led_default: led-default {
mux {
groups = "gpio0_9_grp";
function = "gpio0";
};
conf {
pins = "MIO9";
io-standard = <1>;
bias-disable;
slew-rate = <0>;
};
};
};
设备树其他主体文件由petalinux工程自动产生的,在
yangf@ubuntu:~/src/xilinx/linux_prj/components/plnx_workspace/device-tree/device-tree$ ls
device-tree.mss include ps7_init.c ps7_init_gpl.h ps7_init.html ps_uart_wrapper.bit system-conf.dtsi zynq-7000.dtsi
hardware_description.xsa pcw.dtsi ps7_init_gpl.c ps7_init.h ps7_init.tcl skeleton.dtsi system-top.dts
5.3 编译内核
make xilinx_zynq_defconfig
make
产生内核文件:arch/arm/boot/zImage
5.4 打包内核
在petalinux搜索its文件,可以找到一个类似如下的文件,这个文件就是用来打包image.ub的配置文件,修改配置文件使用新编译的内核,其它文件使用petalinux编译产生的,因为我们这里没有单独编译设备树,而是使用petalinux去编译更新设备树。
/dts-v1/;
/ {
description = "U-Boot fitImage for PetaLinux/5.10+gitAUTOINC+c830a552a6/zynq-generic";
#address-cells = <1>;
images {
kernel-1 {
description = "Linux kernel";
data = /incbin/("zImage");
type = "kernel";
arch = "arm";
os = "linux";
compression = "none";
load = <0x200000>;
entry = <0x200000>;
hash-1 {
algo = "sha256";
};
};
fdt-system-top.dtb {
description = "Flattened Device Tree blob";
data = /incbin/("/home/yangf/src/xilinx/AX7021/linux_prj/build/tmp/work/zynq_generic-xilinx-linux-gnueabi/linux-xlnx/5.10+gitAUTOINC+c830a552a6-r0/recipe-sysroot/boot/devicetree/system-top.dtb");
type = "flat_dt";
arch = "arm";
compression = "none";
hash-1 {
algo = "sha256";
};
};
ramdisk-1 {
description = "petalinux-image-minimal";
data = /incbin/("/home/yangf/src/xilinx/AX7021/linux_prj/build/tmp/deploy/images/zynq-generic/petalinux-image-minimal-zynq-generic.cpio.gz");
type = "ramdisk";
arch = "arm";
os = "linux";
compression = "none";
hash-1 {
algo = "sha256";
};
};
};
configurations {
default = "conf-system-top.dtb";
conf-system-top.dtb {
description = "1 Linux kernel, FDT blob, ramdisk";
kernel = "kernel-1";
fdt = "fdt-system-top.dtb";
ramdisk = "ramdisk-1";
hash-1 {
algo = "sha256";
};
};
};
};
5.5 BOOT.BIN
该实验虽然独立编译了内核,但是其余文件均有petalinux编译后自动产生的。因此还是需要通过petalinux完成整个编译,再通过如上方式制作image.ub。
petalinux-package --boot --fsbl --fpga --u-boot --force
5.6 编译驱动及测试APP
通过如上已经可以使ZYNQ跑起来自己单独编译的内核,因此可以使用该内核编译LED驱动程序。
LED驱动中,主要是解析设备树,操作GPIO。