linux驱动_gpio(sysfs_gpiolib)实现

本文原理部分主要参考以下文章(包括图片):
https://blog.youkuaiyun.com/zhoutaopower/article/details/98082006

GPIO 的使用

linux 系统中可以采用自己编写GPIO驱动方式,通过实现GPIO的字符设备驱动程序,用户层可以通过ioctl进行控制。还有一种方式是通过linux系统通用的GPIO框架实现,目前项目采用这种方式。这种方式应用层控制GPIO的方法在<<linux驱动_leds-pgio>>这篇文章有提到过,这里把操作简单列一下:

echo 6 > /sys/class/gpio/export             # pin脚6 GPIO使能
echo 6 > /sys/class/gpio/unexport           # pin脚6 GPIO去使能
echo out > /sys/class/gpio/gpio6/direction  # 输出使能
echo in > /sys/class/gpio/gpio6/direction   # 输入使能
echo 1 > /sys/class/gpio/gpio6/value        # 输出高电平
echo 0 > /sys/class/gpio/gpio6/value        # 输出低电平
cat /sys/class/gpio/gpio6/value             # 读pin脚电平

注意:内核要支持通过sysfs方式控制GPIO(/sys/class/gpio),记得要在编译内核时(make menuconfig)勾选:Device Drivers -> GPIO Support -> /sys/class/gpio/…(sysfs interface)。

gpiolib 基础

Linux Kernel 中对 GPIO 资源进行了抽象,抽象出 Gpiolib,作为 GPIO 资源的管理核心存在,框架如下图(图片来源于网络)。Gpiolib 汇总了 GPIO 的通用操作,根据 GPIO 的特性,Gpiolib 对上(其他 Drivers)提供的一套统一通用的操作 GPIO 的软件接口,屏蔽了不同芯片的具体实现。对下,Gpiolib 提供了针对不同芯片操作的一套 framework,针对不同芯片,只需要实现 Specific Chip Driver ,然后使用 Gpiolib 提供的注册函数,将其挂接到 Gpiolib 上,这样就完成了。
gpiolib架构

gpiolib 相关数据结构

  1. struct gpio_chip (include/linux/gpio/driver.h),此结构是为了抽象 GPIO 的所有操作,同时适配不同芯片的一个 common 的结构。
  2. struct gpio_desc 结构,包含了一个 gpio_device 的结构和 flag,以及 lable 和 name,gdev 指针指向了这个 gpio_desc 所属的 gpio_device(马上描述),flag 代表了这个 GPIO 的属性状态, gpio_chip 和 gpio_desc 应该是包含关系,但是 Kernel 中并没有直接将其两个结构联系上,而是通过另外一个结构将其联系在一起,这个结构就是 gpio_device。
  3. struct gpio_device结构,gpio_device 结构包含了 gpio_chip(对接芯片的操作集),gpio_desc(一些 GPIO 的描述);这个结构贯穿了整个 Gpiolib,Linux Kernel 把多个 gpio_device 串成一个名为 gpio_devices 的链表。
struct gpio_device {
	int			id;
	struct device		dev;
	struct cdev		chrdev;
	struct device		*mockdev;
	struct module		*owner;
	struct gpio_chip	*chip;
	struct gpio_desc	*descs;
	int			base;
	u16			ngpio;
	const char		*label;
	void			*data;
	struct list_head        list;
    // ...
};

// 链接
LIST_HEAD(gpio_devices);

Gpiolib 为其他驱动提供的 APIs

1、gpio_request:向内核申请 gpio, 要使用 GPIO 首先应该向内核进行申请,返回 0,代表申请成功,可以进行后续操作
2、gpio_free: 对应 gpio_request,是使用完gpio以后把gpio释放掉
3、gpio_direction_input :设置 GPIO 为输入
4、gpio_direction_output:设置 GPIO 为输出
5、gpio_get_value :读取 GPIO 的值
6、gpio_set_value:设置 GPIO 口的值

Gpiolib 对接芯片底层

底层需要对接那些通用的操作,也就是 gpio_chip ,所以,对接底层的部分,主要关心的是这个结构体,并且对这个结构体进行赋值的过程。在底层对接到 Gpiolib 的时候,主要是对 gpio_chip 进行实现,然后调用 gpiochip_add 的接口,向 Gpiolib 注册你的 GPIO 。实现的过程,主要是根据芯片手册,实现对应的 GPIO 的操作,也就是说,把寄存器操作编程成为函数,对接到这个 gpio_chip 结构体上。

SSD202D gpio驱动实现

本项目GPIO驱动的入口是mdrv_gpio_io.c文件,初始化时向平台注册了一个驱动,这个文件的简略代码如下:

// 跟当前平台相关的头文件,其它通用的头文件被省略了
#include "mdrv_gpio_io.h"
#include "mhal_gpio.h"
#include "mdrv_gpio.h"
#include "ms_platform.h"
#include "gpio.h"

// 其中一个接口,后面其它接口的代码大同小异,被省略了
int camdriver_gpio_request(struct gpio_chip *chip, unsigned offset)
{
    // 会调用 MDrv_GPIO_Pad_Set -> MHal_GPIO_Pad_Set
    MDrv_GPIO_Pad_Set(offset); 
    return 0;
}

static void camdriver_gpio_free(struct gpio_chip *chip, unsigned offset)
{
// ...
}

void camdriver_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
{
    if(value==0)
        MDrv_GPIO_Pull_Low(offset);
    else
        MDrv_GPIO_Pull_High(offset);
}

int camdriver_gpio_get(struct gpio_chip *chip, unsigned offset)
{
// ...
}

int camdriver_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
// ...
}

int camdriver_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
                    int value)
{
// ...
}

int camdriver_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
// ...
}

// 全局gpio_chip 对象,把操作硬件的实现挂接上
static struct gpio_chip camdriver_gpio_chip = {
    .label          = "gpio",
    .request        = camdriver_gpio_request,
    .free           = camdriver_gpio_free,
    .direction_input    = camdriver_gpio_direction_input,
    .get            = camdriver_gpio_get,
    .direction_output   = camdriver_gpio_direction_output,
    .set            = camdriver_gpio_set,
    .to_irq         = camdriver_gpio_to_irq,
    .base           = 0,
};

static const struct of_device_id camdriver_gpio_of_match[] = {
    { .compatible = "sstar,gpio" },
    { },
};

// 设备探测函数
static int camdriver_gpio_probe(struct platform_device *pdev)
{
    const struct of_device_id *match;
    int ret;

    dev = &pdev->dev;

    // 驱动与设备匹配名字
    match = of_match_device(camdriver_gpio_of_match, &pdev->dev);
    if (!match) {
        printk("Err:[gpio] No dev found\n");
        return -ENODEV;
    }

    // gpio 个数,/kernel/drivers/sstar/include/infinity2m/gpio.h 文件定义
    camdriver_gpio_chip.ngpio = GPIO_NR; 
    camdriver_gpio_chip.of_node = pdev->dev.of_node;
    ret = gpiochip_add(&camdriver_gpio_chip); // 注册到gpiolib
    if (ret < 0) {
        printk("[gpio] add err\n");
        return ret;
    }

    MDrv_GPIO_Init(); // 初始化硬件
    return 0;
}

static struct platform_driver camdriver_gpio_driver = {
    .driver     = {
        .name   = "gpio",
        .owner  = THIS_MODULE,
        .of_match_table = camdriver_gpio_of_match,
    },
    .probe      = camdriver_gpio_probe,
};

static int __init camdriver_gpio_init(void)
{// 注册平台驱动
    return platform_driver_register(&camdriver_gpio_driver);
}
postcore_initcall(camdriver_gpio_init); // 模块初始化

如上面代码,模块初始化时注册了一个平台驱动,名字是"gpio"。设备是在设备树里面定义的,文件在/kernel/arch/arm/boot/dts 目录下,内核编译后会在该目录下生成一个.dtb文件,查看同文件名的dts文件即可。我这里要看的文件是:
infinity2m-spinand-ssc011a-s01a-rgb565-rmii-doublenet.dts
同时它包含了几个文件,也要一起看,如下:

#include "infinity2m-doublenet.dtsi"
#include "infinity2m-ssc011a-s01a-rgb565-rmii-doublenet.dtsi"
#include "infinity2m-ssc011a-s01a-padmux-rgb565-rmii-doublenet.dtsi"

查到的gpio设备定义如下:

gpio:gpio{
    compatible = "sstar,gpio";
    #gpio-cells = <2>;
    gpio-controller;
};

从上面可见,基本只是定义了一个gpio设备,没有指明硬件资源,即硬件资源直接在驱动里面定义了,设备树文件里面定义gpio设备只是为了让内核启动时probe gpio驱动。那么gpio硬件的资源在哪里定义的呢?
从上面驱动代码调用的地方查找:

MDrv_GPIO_Init();

int camdriver_gpio_request(struct gpio_chip *chip, unsigned offset)
{
    // 会调用 MDrv_GPIO_Pad_Set -> MHal_GPIO_Pad_Set
    MDrv_GPIO_Pad_Set(offset); 
    return 0;
}

上面mdrv_gpio.c文件的camdriver_gpio_request函数会调用mdrv_gpio.c的MDrv_GPIO_Pad_Set函数,然后调用到mhal_gpio.c的MHal_GPIO_Pad_Set函数,然后调用到mhal_pinmux.c文件的 HalPadSetVal 函数这里面会用到padmux_tables.c 文件里面定义的硬件资源。
再看看上面代码中 camdriver_gpio_set 函数,调用链路是:
camdriver_gpio_set(mdrv_gpio_io.c) -> MDrv_GPIO_Pull_High(mdrv_gpio.c) -> MHal_GPIO_Pull_High(mhal_gpio.c) -> MHal_RIU_REG(mhal_gpio.c)
而 MHal_RIU_REG 是个宏定义

U32 gChipBaseAddr    = 0xFD203C00;
U32 gPmSleepBaseAddr = 0xFD001C00;
U32 gSarBaseAddr     = 0xFD002800;
U32 gRIUBaseAddr     = 0xFD000000;

#define MHal_CHIPTOP_REG(addr)  (*(volatile U8*)(gChipBaseAddr + (((addr) & ~1)<<1) + (addr & 1)))
#define MHal_PM_SLEEP_REG(addr) (*(volatile U8*)(gPmSleepBaseAddr + (((addr) & ~1)<<1) + (addr & 1)))
#define MHal_SAR_GPIO_REG(addr) (*(volatile U8*)(gSarBaseAddr + (((addr) & ~1)<<1) + (addr & 1)))
#define MHal_RIU_REG(addr)      (*(volatile U8*)(gRIUBaseAddr + (((addr) & ~1)<<1) + (addr & 1)))

void MHal_GPIO_Pull_High(U8 u8IndexGPIO)
{
    MHal_RIU_REG(gpio_table[u8IndexGPIO].r_out) |= gpio_table[u8IndexGPIO].m_out;
}

上面 gpio_table 是在 gpio_table.c 文件定义,gpio_table.h 头文件代码:

#include "mhal_gpio.h"

struct gpio_setting {
    U32 r_oen;
    U8  m_oen;
    U32 r_out;
    U8  m_out;
    U32 r_in;
    U8  m_in;
};

extern const struct gpio_setting gpio_table[];

gpio_table.c 文件 部分代码:

#include "gpio_table.h"

#define _CONCAT( a, b )     a##b
#define CONCAT( a, b )      _CONCAT( a, b )

#define GPIO0_PAD        PAD_GPIO0
#define GPIO0_OEN        0x103C00, BIT5
#define GPIO0_OUT        0x103C00, BIT4
#define GPIO0_IN         0x103C00, BIT0

// ...

#define GPIO89_PAD        PAD_HDMITX_HPD
#define GPIO89_OEN        0x103C88, BIT5
#define GPIO89_OUT        0x103C88, BIT4
#define GPIO89_IN         0x103C88, BIT0

#define GPIO90_PAD        PAD_SATA_GPIO
#define GPIO90_OEN        0x103C8A, BIT5
#define GPIO90_OUT        0x103C8A, BIT4
#define GPIO90_IN         0x103C8A, BIT0


const struct gpio_setting gpio_table[] =
{
#define __GPIO__(_x_)   { CONCAT(CONCAT(GPIO, _x_), _OEN), CONCAT(CONCAT(GPIO, _x_), _OUT), CONCAT(CONCAT(GPIO, _x_), _IN) }
#define __GPIO(_x_)     __GPIO__(_x_)

//
// !! WARNING !! DO NOT MODIFIY !!!!
//
// These defines order must match following
// 1. the PAD name in GPIO excel
// 2. the perl script to generate the package header file
//
    //__GPIO(999), // 0 is not used
    __GPIO(0), __GPIO(1), __GPIO(2), __GPIO(3), __GPIO(4), __GPIO(5), __GPIO(6), __GPIO(7),
    __GPIO(8), __GPIO(9), __GPIO(10), __GPIO(11), __GPIO(12), __GPIO(13), __GPIO(14), __GPIO(15),
    __GPIO(16), __GPIO(17), __GPIO(18), __GPIO(19), __GPIO(20), __GPIO(21), __GPIO(22), __GPIO(23),
    __GPIO(24), __GPIO(25), __GPIO(26), __GPIO(27), __GPIO(28), __GPIO(29), __GPIO(30), __GPIO(31),
    __GPIO(32), __GPIO(33), __GPIO(34), __GPIO(35), __GPIO(36), __GPIO(37), __GPIO(38), __GPIO(39),
    __GPIO(40), __GPIO(41), __GPIO(42), __GPIO(43), __GPIO(44), __GPIO(45), __GPIO(46), __GPIO(47),
    __GPIO(48), __GPIO(49), __GPIO(50), __GPIO(51), __GPIO(52), __GPIO(53), __GPIO(54), __GPIO(55),
    __GPIO(56), __GPIO(57), __GPIO(58), __GPIO(59), __GPIO(60), __GPIO(61), __GPIO(62), __GPIO(63),
    __GPIO(64), __GPIO(65), __GPIO(66), __GPIO(67), __GPIO(68), __GPIO(69), __GPIO(70), __GPIO(71),
    __GPIO(72), __GPIO(73), __GPIO(74), __GPIO(75), __GPIO(76), __GPIO(77), __GPIO(78), __GPIO(79),
    __GPIO(80), __GPIO(81), __GPIO(82), __GPIO(83), __GPIO(84), __GPIO(85), __GPIO(86), __GPIO(87),
    __GPIO(88), __GPIO(89), __GPIO(90),
};

从上面代码可知,mhal_gpio.c 文件定义了gpio 基址, gpio_table.c 文件把/kernel/drivers/sstar/include/infinity2m/gpio.h 定义的所有gpio pin脚的硬件资源(偏移)定义在 gpio_table 数组,gpio 硬件实现基本清楚。下面把 gpio.h 部分代码贴上,这些pin脚的ID与硬件data sheet对应,ID号也与用户空间使用的ID号一致。

#ifndef __GPIO_H__
#define __GPIO_H__

#define PAD_GPIO0                       0
#define PAD_GPIO1                       1

// ...

#define PAD_HSYNC_OUT                   85
#define PAD_VSYNC_OUT                   86
#define PAD_HDMITX_SCL                  87
#define PAD_HDMITX_SDA                  88
#define PAD_HDMITX_HPD                  89
#define PAD_SATA_GPIO                   90

#define GPIO_NR                         91
#define PAD_UNKNOWN     0xFFFF

#endif // __GPIO_H__

end

<think>好的,用户想了解Linux内核中gpio_export的用法和实现。首,我需要回忆一下GPIO在内核中的基本概念。GPIO(通用输入输出)是嵌入式系统中常用的接口,用于控制硬件引脚。内核中的GPIO子系统提供了一系列API来管理这些引脚,包括申请、配置、导出等操作。 用户提到的gpio_export函数,应该是在内核中用于将GPIO引脚导出到用户空间的函数。根据引用[3],GPIO芯片被抽象为gpio_chip结构,每个Bank管理一组GPIO。导出GPIO后,用户空间可以通过sysfs接口访问和控制这些引脚。 接下来,我需要确认gpio_export的具体用法。通常在内核驱动中,开发者会使用gpio_request申请GPIO,然后调用gpio_export将其导出。可能还需要设置方向和初始值。用户空间的访问路径一般是/sys/class/gpio/gpioX。需要检查是否正确处理了错误情况,比如GPIO已被占用或编号无效。 关于实现部分,gpio_export函数应该在drivers/gpio/gpiolib.c中定义。它主要创建sysfs条目,并设置相应的属性文件,比如direction和value。这里可能会用到内核的sysfs接口,比如device_create和sysfs_create_group。同时,需要确保导出的GPIO没有被其他驱动占用,这涉及到gpio_desc结构的状态管理。 还需要注意权限问题,导出后的GPIO在用户空间的访问可能需要root权限,或者通过udev规则设置合适的权限。另外,GPIO编号的管理,包括动态分配和静态声明,也是需要考虑的。例如,使用gpio_is_valid来检查编号有效性。 可能用户在使用时遇到问题,比如无法导出GPIO,这时候需要检查是否已经申请了该GPIO,或者是否有冲突。此外,导出后的方向设置和读写操作是否正确,也是常见的问题点。 最后,结合引用中的内容,特别是引用[3]提到的gpio_chip结构,可以说明GPIO子系统的层次结构,帮助用户理解gpio_export在整个框架中的位置和作用。需要确保解释清晰,并给出示例代码和注意事项,帮助用户正确使用该函数。</think>### Linux内核中`gpio_export`的用法与实现 #### 1. `gpio_export`的作用 `gpio_export`是Linux内核GPIO子系统提供的接口,用于将GPIO引脚导出到用户空间,允许通过sysfs文件系统(路径`/sys/class/gpio`)进行访问和控制[^3]。导出后,用户可通过读写文件(如`direction`、`value`)操作GPIO的输入/输出方向和电平。 --- #### 2. 使用方法 ##### 内核驱动中的调用流程: 1. **申请GPIO**:使用`gpio_request`分配GPIO资源。 2. **配置方向**:通过`gpio_direction_input`或`gpio_direction_output`设置方向。 3. **导出到用户空间**:调用`gpio_export`导出GPIO。 4. **可选标签**:使用`gpio_export_link`在sysfs中创建符号链接。 示例代码: ```c int gpio_num = 16; // GPIO编号 if (gpio_is_valid(gpio_num)) { gpio_request(gpio_num, "my_gpio"); gpio_direction_output(gpio_num, 0); // 初始输出低电平 gpio_export(gpio_num, false); // 第二个参数控制是否允许方向修改 } ``` --- #### 3. 实现分析 ##### 核心代码位置: - 函数定义:`drivers/gpio/gpiolib.c` - 关键步骤: 1. **有效性检查**:验证GPIO编号是否合法。 2. **分配`gpio_desc`结构**:管理GPIO状态和属性。 3. **创建sysfs接口**:通过`device_create`和`sysfs_create_group`生成`value`、`direction`等文件。 4. **权限设置**:默认权限为0644,用户可读写。 ##### 代码片段: ```c int gpio_export(unsigned gpio, bool direction_may_change) { struct gpio_desc *desc; if (!gpio_is_valid(gpio)) return -EINVAL; desc = gpio_to_desc(gpio); return gpiod_export(desc, direction_may_change); } ``` --- #### 4. 注意事项 - **GPIO编号有效性**:需通过`gpio_is_valid`检查。 - **资源冲突**:避免重复申请已占用的GPIO。 - **用户空间权限**:可能需要root权限或配置udev规则。 - **动态导出**:可通过`echo 16 > /sys/class/gpio/export`手动导出。 --- #### 5. 相关结构体 - `gpio_chip`:抽象GPIO控制器,包含操作函数(如`direction_input`)[^3]。 - `gpio_desc`:描述单个GPIO的状态(申请/释放、方向等)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值