本文原理部分主要参考以下文章(包括图片):
https://blog.csdn.net/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 相关数据结构
- struct gpio_chip (include/linux/gpio/driver.h),此结构是为了抽象 GPIO 的所有操作,同时适配不同芯片的一个 common 的结构。
- struct gpio_desc 结构,包含了一个 gpio_device 的结构和 flag,以及 lable 和 name,gdev 指针指向了这个 gpio_desc 所属的 gpio_device(马上描述),flag 代表了这个 GPIO 的属性状态, gpio_chip 和 gpio_desc 应该是包含关系,但是 Kernel 中并没有直接将其两个结构联系上,而是通过另外一个结构将其联系在一起,这个结构就是 gpio_device。
- 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