往期内容
本专栏往期内容:
- Pinctrl子系统和其主要结构体引入
- Pinctrl子系统pinctrl_desc结构体进一步介绍
- Pinctrl子系统中client端设备树相关数据结构介绍和解析
- inctrl子系统中Pincontroller构造过程驱动分析:imx_pinctrl_soc_info结构体
- Pinctrl子系统中client端使用pinctrl过程的驱动分析
- Pinctrl子系统中Pincontroller和client驱动程序的编写
- GPIO子系统层次与数据结构详解-CSDN博客
- GPIO子系统中Controller驱动源码分析
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序 – 末片,有往期内容观看顺序
I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客 – 末篇,有往期内容观看顺序
总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客 – 末篇,有往期内容观看顺序
目录
前言
Linux 4.x内核文档
- Linux-4.9.88\Documentation\gpio📎drivers-on-gpio.txt📎gpio.txt📎gpio-legacy.txt📎sysfs.txt📎board.txt📎consumer.txt📎driver.txt
- Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt📎gpio.txt
- Linux-4.9.88\drivers\gpio\gpio-mxc.c📎gpio-mxc.c
- Linux-4.9.88\arch\arm\boot\dts\imx6ull.dtsi
本文主要讲解了如何在Linux 4.9.88内核中为虚拟GPIO控制器编写驱动程序,并展示了GPIO和Pinctrl子系统之间的交互方式。假设该虚拟GPIO控制器有4个引脚,然后在设备树中为其设置相应的设备节点和引脚配置。通过代码示例展示了如何实现GPIO的输入、输出功能,包括GPIO的值读取与设置。文介绍了GPIO控制器与Pinctrl的关系以及两者的映射机制,解释了gpio_pin_range和pinctrl_gpio_range结构体的作用,并提供了GPIO子系统中调用Pinctrl的流程示例。
1.编写虚拟的GPIO控制器的驱动程序
1.1 硬件功能
假设这个虚拟的GPIO Controller有4个引脚:
1.2 编写设备树文件
修改
arch/arm/boot/dts/100ask_imx6ull-14x14.dts
,添加如下代码:
/{
gpio_virt: virtual_gpiocontroller {
compatible ="example,virtual_gpio";
gpio-controller;#gpio-cells =<2>;
ngpios =<4>;};
myled {
compatible ="XXX,leddrv";
led-gpios =<&gpio_virt 2 GPIO_ACTIVE_LOW>;};};
1.3 代码编写
代码编写:📎virtual_gpio_driver.c
#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/gpio/consumer.h>#include<linux/gpio/driver.h>#include<linux/slab.h>#include<linux/regmap.h>// 定义一个全局的GPIO芯片结构体指针staticstructgpio_chip*g_virtual_gpio_chip;// 用于模拟GPIO状态的全局变量staticint g_gpio_status =0;// 设备树匹配结构,匹配支持的设备staticconststructof_device_id virtual_gpio_of_match[]={{.compatible ="example,virtual_gpio",},{},};// 设置GPIO为输出模式的函数实现staticintvirtual_gpio_set_direction_output(structgpio_chip*chip,unsigned offset,int value){printk("Setting GPIO pin %d as output %s\n", offset, value ?"high":"low");return0;// 成功返回0}// 设置GPIO为输入模式的函数实现staticintvirtual_gpio_set_direction_input(structgpio_chip*chip,unsigned offset){printk("Setting GPIO pin %d as input\n", offset);return0;// 成功返回0}// 获取GPIO当前值的函数实现staticintvirtual_gpio_get_value(structgpio_chip*chip,unsigned offset){int value =(g_gpio_status &(1<< offset))?1:0;printk("Reading GPIO pin %d, current value = %d\n", offset, value);return value;// 返回当前值}// 设置GPIO值的函数实现staticvoidvirtual_gpio_set_value(structgpio_chip*chip,unsigned offset,int value){printk("Setting GPIO pin %d to %d\n", offset, value);if(value){
g_gpio_status |=(1<< offset);// 设置为高电平}else{
g_gpio_status &=~(1<< offset);// 设置为低电平}}// 设备探测函数staticintvirtual_gpio_probe(structplatform_device*pdev){int ret;unsignedint ngpios;printk("Probing virtual GPIO driver: %s, line: %d\n", __FUNCTION__,__LINE__);// 分配gpio_chip结构体内存
g_virtual_gpio_chip =devm_kzalloc(&pdev->dev,sizeof(*g_virtual_gpio_chip), GFP_KERNEL);// 配置gpio_chip的相关信息
g_virtual_gpio_chip->label = pdev->name;
g_virtual_gpio_chip->direction_output = virtual_gpio_set_direction_output;
g_virtual_gpio_chip->direction_input = virtual_gpio_set_direction_input;
g_virtual_gpio_chip->get = virtual_gpio_get_value;
g_virtual_gpio_chip->set = virtual_gpio_set_value;
g_virtual_gpio_chip->parent =&pdev->dev;// 设置父设备
g_virtual_gpio_chip->owner = THIS_MODULE;// 设置模块所有者// 读取ngpios属性
g_virtual_gpio_chip->base =-1;// 默认未分配基地址
ret =of_property_read_u32(pdev->dev.of_node,"ngpios",&ngpios);
g_virtual_gpio_chip->ngpio = ngpios;// 设置GPIO数量// 注册gpio_chip
ret =devm_gpiochip_add_data(&pdev->dev, g_virtual_gpio_chip,NULL);return ret;// 返回结果}// 设备移除函数staticintvirtual_gpio_remove(structplatform_device*pdev){printk("Removing virtual GPIO driver: %s, line: %d\n", __FUNCTION__,__LINE__);return0;// 成功返回0}// 定义platform_driverstaticstructplatform_driver virtual_gpio_driver ={.probe = virtual_gpio_probe,.remove = virtual_gpio_remove,.driver ={.name ="example_virtual_gpio",.of_match_table =of_match_ptr(virtual_gpio_of_match),},};// 初始化函数staticint __init virtual_gpio_init(void){printk("Initializing virtual GPIO driver: %s, line: %d\n", __FUNCTION__,__LINE__);returnplatform_driver_register(&virtual_gpio_driver);// 注册platform_driver}// 清理函数staticvoid __exit virtual_gpio_exit(void){printk("Exiting virtual GPIO driver: %s, line: %d\n", __FUNCTION__,__LINE__);platform_driver_unregister(&virtual_gpio_driver);// 注销platform_driver}// 模块初始化和清理宏module_init(virtual_gpio_init);module_exit(virtual_gpio_exit);// 模块许可证MODULE_LICENSE("GPL");
of_property_read_u32:📎of.h
2.GPIO子系统与Pinctrl子系统的交互
Linux-4.9.88\drivers\gpio\gpio-74x164.c📎gpio-74x164.c
2.1 使用GPIO前应该设置Pinctrl
假设使用这个虚拟的GPIO Controller的pinA来控制LED:
要使用pinA来控制LED,首先要通过Pinctrl子系统把它设置为GPIO功能,然后才能设置它为输出引脚、设置它的输出值。
所以在设备树文件里,应该添加Pinctrl的内容:
virtual_pincontroller {
compatible ="XXX,virtual_pinctrl";
myled_pin: myled_pin {
functions ="gpio"; // 设置引脚功能为GPIO
groups="pin0"; // 指定该引脚所在的组
configs =<0x11223344>; // 配置该引脚的其他特性
};};
gpio_virt: virtual_gpiocontroller {
compatible ="XXX,virtual_gpio";
gpio-controller; // 标识这是一个GPIO控制器
#gpio-cells = <2>; // GPIO单元的数量
ngpios =<4>; // GPIO引脚数量
};
myled {
compatible ="XXX,leddrv"; // LED驱动
led-gpios =<&gpio_virt 0 GPIO_ACTIVE_LOW>; // 指定使用的GPIO引脚
pinctrl-names ="default"; // 指定Pinctrl的名称
pinctrl-0 =<&myled_pin>; // 指定使用的Pinctrl设置
};
许多芯片并不需要在设备树中显式地将引脚配置为GPIO功能。这是因为某些芯片(例如STM32MP157)在内部实现上已经将GPIO功能与引脚复用整合在一起。因此,Pinctrl的使用在某些情况下是虚拟的,它与GPIO密切相关。
2.2 GPIO和Pinctrl的映射关系
2.2.1 示例
- 左边的Pinctrl支持8个引脚,在Pinctrl的内部编号为0~7
- 图中有2个GPIO控制器- GPIO0内部引脚编号为03,假设在GPIO子系统中全局编号为100103- GPIO1内部引脚编号为03,假设在GPIO子系统中全局编号为104107
- 假设我们要使用pin1_1,应该这样做:- 根据GPIO1的内部编号1,可以换算为Pinctrl子系统中的编号5- 使用Pinctrl的函数,把第5个引脚配置为GPIO功能
2.2.2 数据结构
gpio_pin_range
结构体定义了 GPIO 控制器控制的引脚范围和对应的 pinctrl 设备,允许 GPIO 控制器管理一个 pinctrl 设备的引脚范围。
/**
* struct gpio_pin_range - GPIO 控制器所控制的引脚范围
* @node: 连接到范围列表的链表节点,用于内部维护 pin range 的集合。
* @pctldev: 指向管理相应引脚的 pinctrl 设备(pinctrl_dev)的指针,
* 该设备将处理引脚复用、方向控制等功能。
* @range: 由 GPIO 控制器控制的引脚的实际范围,包含引脚的起始位置、数量等信息。
*/structgpio_pin_range{structlist_head node;// 列表头,维护 GPIO 范围的链表结构structpinctrl_dev*pctldev;// 与 GPIO 控制器关联的 pinctrl 设备structpinctrl_gpio_range range;// 由 GPIO 控制器控制的引脚范围信息};
node
:这是一个链表节点,通常用于将多个gpio_pin_range
链接成一个链表,以便管理和迭代多个引脚范围。pctldev
:指向 pinctrl 设备的指针,pinctrl 设备负责管理 GPIO 引脚的复用和配置。range
:定义了 GPIO 控制器管理的实际引脚范围,包含引脚基地址和数量等信息。
pinctrl_gpio_range
结构体描述了由 GPIO 控制器所处理的 GPIO 引脚范围,为每个 GPIO 控制器提供一个编号空间的子范围。
/**
* struct pinctrl_gpio_range - 每个 GPIO 控制器提供的 GPIO 编号范围
* @node: 用于内部使用的链表节点,连接到范围列表中
* @name: 此 GPIO 控制器范围的名称,用于标识 GPIO 控制器
* @id: 该范围的芯片 ID,用于在范围内区分 GPIO 控制器
* @base: GPIO 范围的基地址偏移,用于计算 GPIO 引脚编号
* @pin_base: 如果 pins 数组为 NULL,此字段表示 GPIO 引脚范围的基引脚编号
* @pins: 指向引脚枚举数组的指针,该数组列出 GPIO 范围中的所有引脚
* @npins: GPIO 范围内的引脚数量,包含基引脚编号
* @gc: 可选指针,指向 gpio_chip 结构体,该结构体用于 GPIO 控制器的基本信息
*/structpinctrl_gpio_range{structlist_head node;// 内部链表节点,连接到 GPIO 范围列表constchar*name;// 范围名称,标识 GPIO 控制器unsignedint id;// GPIO 范围 ID,唯一标识该范围unsignedint base;// GPIO 编号范围的基地址偏移,用于计算引脚编号unsignedint pin_base;// 引脚范围的基引脚编号,如果 pins 数组为 NULL 使用此基数unsignedconst*pins;// 引脚数组的指针,列出该 GPIO 范围内的所有引脚unsignedint npins;// GPIO 范围中的引脚数量,包括基引脚编号structgpio_chip*gc;// 可选的 gpio_chip 指针,用于指向 GPIO 控制器信息};
node
:内部链表节点,用于管理pinctrl_gpio_range
的链表结构。name
:范围的名称,通常是 GPIO 控制器的名称,用于标识该范围。id
:范围 ID,唯一标识该 GPIO 范围,用于在控制器中区分不同的 GPIO 范围。base
:GPIO 编号范围的基地址偏移量,通过该偏移量确定 GPIO 控制器管理的具体引脚编号。pin_base
:基引脚编号,表示该 GPIO 范围的起始引脚,通常在pins
数组为空时使用。pins
:指向引脚编号数组的指针,列出该 GPIO 范围内的所有 GPIO 引脚。npins
:范围内的引脚数量,包括基引脚编号。gc
:可选的指向gpio_chip
结构体的指针,用于与 GPIO 控制器相关联,提供 GPIO 控制器的基本信息。
2.3 GPIO调用Pinctrl的过程
GPIO子系统中的request函数,用来申请某个GPIO引脚,
它会导致Pinctrl子系统中的这2个函数之一被调用:
pmxops->gpio_request_enable
或
pmxops->request
调用关系如下:
gpiod_get //获取GPIO描述符的主函数,通常用于申请和配置GPIO引脚。
gpiod_get_index
desc =of_find_gpio(dev, con_id, idx,&lookupflags);//查找设备树中指定的GPIO。
ret =gpiod_request(desc, con_id ? con_id : devname);//用于确认请求并进行最终设置。
ret =gpiod_request_commit(desc, label);//如果GPIO芯片结构体中的request指针非空,它会调用芯片的request函数。if(chip->request){
ret = chip->request(chip, offset);}
编写GPIO驱动程序时,所设置
chip->request
函数,一般直接调用
gpiochip_generic_request
,它导致Pinctrl把引脚复用为GPIO功能。
gpiochip_generic_request(structgpio_chip*chip,unsigned offset)pinctrl_request_gpio(chip->gpiodev->base + offset)//该函数负责将引脚配置为GPIO功能
ret =pinctrl_get_device_gpio_range(gpio,&pctldev,&range);// gpio是引脚的全局编号/* Convert to the pin controllers number space */
pin =gpio_to_pin(range, gpio);
ret =pinmux_request_gpio(pctldev, range, pin, gpio);
ret =pin_request(pctldev, pin, owner, range);
Pinctrl子系统中的pin_request函数就会把引脚配置为GPIO功能:
staticintpin_request(structpinctrl_dev*pctldev,int pin,constchar*owner,structpinctrl_gpio_range*gpio_range){conststructpinmux_ops*ops = pctldev->desc->pmxops;// 检查是否有用于请求引脚的操作if(gpio_range && ops->gpio_request_enable){// 请求并启用单个GPIO引脚
status = ops->gpio_request_enable(pctldev, gpio_range, pin);}elseif(ops->request){// 如果有通用请求函数,则调用
status = ops->request(pctldev, pin);}else{// 默认情况下,不做任何操作
status =0;}return status;// 返回请求状态}
2.4 编程_GPIO使用Pinctrl
2.4.1 做什么
如果不想在使用GPIO引脚时,在设备树中设置Pinctrl信息,
如果想让GPIO和Pinctrl之间建立联系,
需要做这些事情:
- 表明GPIO和Pinctrl间的联系
在GPIO设备树中使用
gpio-ranges
来描述它们之间的联系:
- GPIO系统中有引脚号
- Pinctrl子系统中也有自己的引脚号
- 2个号码要建立映射关系
- 在GPIO设备树中使用如下代码建立映射关系
// 当前GPIO控制器的0号引脚, 对应pinctrlA中的128号引脚, 数量为12
gpio-ranges =<&pinctrlA 012812>;
- 解析这些联系
在GPIO驱动程序中,解析跟Pinctrl之间的联系:处理
gpio-ranges
:
- 这不需要我们自己写代码
- 注册gpio_chip时会自动调用
intgpiochip_add_data(structgpio_chip*chip,void*data)
status =of_gpiochip_add(chip);
status =of_gpiochip_add_pin_range(chip);
of_gpiochip_add_pin_range
for(;; index++){
ret =of_parse_phandle_with_fixed_args(np,"gpio-ranges",3,
index,&pinspec);
pctldev =of_pinctrl_get(pinspec.np);// 根据gpio-ranges的第1个参数找到pctldev// 增加映射关系 /* npins != 0: linear range */
ret =gpiochip_add_pin_range(chip,pinctrl_dev_get_devname(pctldev),
pinspec.args[0],
pinspec.args[1],
pinspec.args[2]);
- 编程
- 在GPIO驱动程序中,提供
gpio_chip->request
- 在Pinctrl驱动程序中,提供
pmxops->gpio_request_enable
或pmxops->request
2.4.2 编程
- GPIO控制器编程
在编写GPIO驱动程序时,需要关注以下几个方面:
- 设置请求函数:- 确保在GPIO芯片结构体中正确设置
chip->request
指针,通常指向gpiochip_generic_request
。这样可以确保引脚被正确复用为GPIO功能。- 理解设备树:- 如果GPIO引脚来自设备树,确保在设备树中正确描述GPIO的相关属性和配置,以便
gpiod_get
和相关函数能够正确查找和请求GPIO。- 错误处理:- 在每个调用中检查返回值,确保处理潜在的错误,避免因引脚请求失败而导致驱动不稳定。
- 功能分离:- 在实现请求和释放引脚时,确保保持代码的清晰和可读,尽量将复杂逻辑分离到辅助函数中,以便于维护。
gpio_chip中提供request函数:
c chip->request = gpiochip_generic_request;
📎virtual_gpio_driver.c
(参考)>>
📎virtual_gpio_driver0.c
- Pinctrl编程
staticconststructpinmux_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,.gpio_request_enable = virtual_pmx_gpio_request_enable,};
📎virtual_pinctrl_driver.c📎core.h
- led:
📎virtual_pinctrl_client.c(参考) 》 📎leddrv.c
📎ledtest.c
2.4.3 注意
IMX6ULL使用GPIO时必须设置Pinctrl,如果不设置,只有那些默认就是GPIO功能的引脚可以正常使用。
原因:
- GPIO控制器的设备树中,没有
gpio-ranges
- Pinctrl驱动中并没有提供
pmxops->gpio_request_enable
或pmxops->request
- gpio_chip结构体中
direction_input
、direction_output
,并没有配置引脚为GPIO功能
3.GPIO子系统的sysfs接口
- Linux-4.9.88\drivers\gpio\gpiolib-sysfs.c📎gpiolib-sysfs.c
3.1 驱动程序
📎gpiolib-sysfs.c
3.2 常用的sysfs文件
3.2.1 有哪些GPIO控制器
/sys/bus/gpio/devices
目录下,列出了所有的GPIO控制器,如下表示有11个GPIO控制器:
/sys/bus/gpio/devices/gpiochip0
/sys/bus/gpio/devices/gpiochip1
/sys/bus/gpio/devices/gpiochip2
/sys/bus/gpio/devices/gpiochip3
/sys/bus/gpio/devices/gpiochip4
/sys/bus/gpio/devices/gpiochip5
/sys/bus/gpio/devices/gpiochip6
/sys/bus/gpio/devices/gpiochip7
/sys/bus/gpio/devices/gpiochip8
/sys/bus/gpio/devices/gpiochip9
/sys/bus/gpio/devices/gpiochip10
3.2.2 每个GPIO控制器的详细信息
/sys/class/gpio/gpiochipXXX
下,有这些信息:
/sys/class/gpio/gpiochip508]# ls -1
base // 这个GPIO控制器的GPIO编号
device
label // 名字
ngpio // 引脚个数
power
subsystem
uevent
3.2.3 查看GPIO使用情况
cat /sys/kernel/debug/gpio
3.2.4 通过sysfs使用GPIO
如果只是简单的引脚控制(比如输出、查询输入值),可以不编写驱动程序。
但是涉及中断的话,就需要编写驱动程序了。
1. 确定GPIO编号
查看每个
/sys/class/gpio/gpiochipXXX
目录下的label,确定是你要用的GPIO控制器,也称为GPIO Bank。
根据它名字gpiochipXXX,就可以知道基值是XXX。
基值加上引脚offset,就是这个引脚的编号。
2. 导出/设置方向/读写值
举例:
echo 509 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio509/direction
echo 1 > /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport
echo 509 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio509/direction
cat /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport
版权归原作者 憧憬一下 所有, 如有侵权,请联系我们删除。