Linux驱动开发进阶(八)- GPIO子系统BSP驱动
文章目录
- 1、前言
- 2、pinctrl子系统
- 3、pinctrl bsp驱动
- 4、gpio子系统
- 5、gpio bsp驱动
1、前言
- 学习参考书籍以及本文涉及的示例程序:李山文的《Linux驱动开发进阶》
- 本文属于个人学习后的总结,不太具备教学功能。
2、pinctrl子系统
在讨论gpio子系统时,一般要带上pinctrl子系统。pinctrl子系统管理着io的功能复用,gpio子系统依赖于pinctrl子系统。所以先讨论pinctrl子系统。
先看设备树,下面是瑞芯微平台的pinctrl控制器的设备树节点:
&pinctrl {compatible = "rockchip,rk3568-pinctrl";xl9535:xl9535 {rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>;};
};
下面是全志平台的pinctrl控制器的设备树节点:
&pio {compatible = "allwinner,sun8iw20-pinctrl";csi_mclk0_pins_a: csi_mclk0@0 {pins = "PE3";function = "csi0";drive-strength = <10>;};
}
上面展示了两个不同平台的pinctrl控制器设备树节点。我们不关心他们配置了什么内容,但值得注意的是,他们用于表示IO复用的属性是不一样的。这也说明了这方面是高度自由的,通常都由厂家来定义。
Linux内核使用一个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;
#ifdef CONFIG_GENERIC_PINCONFunsigned int num_custom_params;const struct pinconf_generic_params *custom_params;const struct pin_config_item *custom_conf_items;
#endifbool link_consumers; // 用于指示是否将使用该pinctrl控制器的设备链接到该控制器
};
先看引脚描述结构体:
struct pinctrl_pin_desc {unsigned number; // GPIO引脚的编号const char *name; // GPIO引脚名称void *drv_data; // 私有数据
};
再看pinctrl_ops结构体:
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);
};
该结构体的函数都要实现。get_groups_count用于获取IO分组,get_group_name用于获取IO分组名称,get_group_pins用于获取IO引脚资源,pin_dbg_show用于打印调试信息,dt_node_to_map用于从设备树获取设备节点然后映射(这个函数挺重要的),dt_free_map用于释放映射。
再看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;
};
request和free函数指针用于请求和释放一个引脚,get_functions_count函数指针用于获取pin控制器中的function的个数,get_function_name函数指针用于获取指定function的名称,get_function_groups函数指针用于获取指定function所占用的引脚group,set_mux函数指针用于将指定的引脚group(group_selector)设置为指定的function。
再看pinconf_ops结构体:
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONFbool is_generic;
#endifint (*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);
};
pin_config_get用于获取被选中引脚的配置,pin_config_set用于配置被选中的引脚,pin_config_group_get用于获取被选中的引脚分组配置,pin_config_group_set用于配置被选中的引脚分组,pin_cofig_dbg_show、pin_config_group_dbg_show用于调试信息。
3、pinctrl bsp驱动
pinctrl的3大作用:
作用1(分为两部分):
- 描述、获得单个引脚的信息
- 描述、获得某组引脚的信息
pinctrl_ops这个操作集合说是获取各组引脚的信息。但这个“组”到底体现了在哪?有些驱动里是把单个引脚归为一组,所以因此产生了名字和实现不匹配现象。
作用2:
用来把某组引脚(group)复用为某个功能(function)
作用3:
用来配置某个引脚(pin)或某组引脚(group)
关于bsp驱动示例程序可以参考韦东山的驱动大全或者李山文的《Linux驱动开发进阶》的pinctrl bsp示例程序。
如下贴出韦东山的示例程序:
#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"static struct pinctrl_dev *g_pinctrl_dev;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"};
static const char *func1_grps[] = {"pin0", "pin1"};
static const char *func2_grps[] = {"pin2", "pin3"};static struct virtual_functions_desc g_funcs_des[] = {{"gpio", func0_grps, 4},{"i2c", func1_grps, 2},{"uart", func2_grps, 2},
};static const struct of_device_id virtual_pinctrl_of_match[] = {{ .compatible = "100ask,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));
}/*i2cgrp {functions = "i2c", "i2c";groups = "pin0", "pin1";configs = <0x11223344 0x55667788>;};one pin ==> two pinctrl_map (one for mux, one for config)*/
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;/* 1. 确定pin个数/分配pinctrl_map */while (1){if (of_property_read_string_index(np, "groups", num_pins, &pin) == 0)num_pins++;elsebreak;}new_map = kmalloc(sizeof(struct pinctrl_map) * num_pins * 2, GFP_KERNEL);for (i = 0; i < num_pins; i++){/* 2. get pin/function/config */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. 存入pinctrl_map */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;}*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++;}
}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(g_funcs_des);
}static const char *virtual_pmx_get_func_name(struct pinctrl_dev *pctldev,unsigned selector)
{return g_funcs_des[selector].func_name;
}static int virtual_pmx_get_groups(struct pinctrl_dev *pctldev, unsigned selector,const char * const **groups,unsigned * const num_groups)
{*groups = g_funcs_des[selector].groups;*num_groups = g_funcs_des[selector].num_groups;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, g_funcs_des[selector].func_name);return 0;
}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 = g_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;g_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", g_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", g_configs[pin_id]);
}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_pinctrl_probe(struct platform_device *pdev)
{struct pinctrl_desc *pictrl;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* a. 分配pinctrl_desc */pictrl = devm_kzalloc(&pdev->dev, sizeof(*pictrl), GFP_KERNEL);/* b. 设置pinctrl_desc */pictrl->name = dev_name(&pdev->dev);pictrl->owner = THIS_MODULE;/* b.1 pins and group */pictrl->pins = pins;pictrl->npins = ARRAY_SIZE(pins);pictrl->pctlops = &virtual_pctrl_ops;/* b.2 pin mux */pictrl->pmxops = &virtual_pmx_ops;/* b.3 pin config */pictrl->confops = &virtual_pinconf_ops;/* c. 注册pinctrl_desc */g_pinctrl_dev = devm_pinctrl_register(&pdev->dev, pictrl, NULL);return 0;
}
static int virtual_pinctrl_remove(struct platform_device *pdev)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static struct platform_driver virtual_pinctrl_driver = {.probe = virtual_pinctrl_probe,.remove = virtual_pinctrl_remove,.driver = {.name = "100ask_virtual_pinctrl",.of_match_table = of_match_ptr(virtual_pinctrl_of_match),}
};/* 1. 入口函数 */
static int __init virtual_pinctrl_init(void)
{ printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1.1 注册一个platform_driver */return platform_driver_register(&virtual_pinctrl_driver);
}/* 2. 出口函数 */
static void __exit virtual_pinctrl_exit(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 2.1 反注册platform_driver */platform_driver_unregister(&virtual_pinctrl_driver);
}module_init(virtual_pinctrl_init);
module_exit(virtual_pinctrl_exit);MODULE_LICENSE("GPL");
重点看virtual_dt_node_to_map,该函数主要是将设备树pinctrl节点信息转成pinctrl_map结构体。同时,内核也提供了该功能的函数,叫pinconf_generic_dt_node_to_map_all,如果这样,设备树pinctrl节点属性就得按照一定的格式来写。
4、gpio子系统
pinctrl子系统的主要作用是管理引脚的功能,即复用功能。而gpio子系统则是控制gpio功能的io口的高低电平。gpio子系统依赖于pinctrl子系统的实现,因此,在加载gpio bsp驱动时,也需要加载pinctrl的bsp驱动,这样gpio bsp驱动才能正常工作。
5、gpio bsp驱动
linux内核抽象出了一个结构体,叫gpio_chip,用来控制IO的电平及获取IO的电平:
注册一个gpio bsp驱动调用如下函数即可:
该函数将gpio_chip注册到内核中,这样用户在调用gpiod_xxx接口时就可以正常工作了。
示例程序可以参考李山文的《Linux驱动开发进阶》的gpio bsp示例程序。