Linux下如何使用X86 CPU的GPIO

1.前言

在arm嵌入式开发中,各个外设具备固定的物理地址,咱们能够直接经过芯片手册来编写驱动配置后使用。可是在x86中有所不一样,全部外设控制器集成在PCH(曾经的南桥)中,每一个外设都是做为一个PCI设备挂在PCH的PCI总线上,PCH再经过DMI与CPU相联。对于标压处理器H/K系列(也就是咱们台式机),南桥还在主板上,对于x86移动处理器(Y/U结尾系列),已将PCH和CPU集成到同一封装中,与现在各种SOC相似,以下(详见datasheet)。html

image-20201031102153079

因为x86中每一个外设是一个PCI设备,因此咱们要使用某个外设就须要为其分配内存空间映射、IRQ和I/O基址,x86中这些资源配置是由BIOS(UEFI)完成的,由于每块主板设计和外设使用不同,就须要不同的配置,因此不一样的主板厂商须要定制本身主板的BIOS 。linux

BIOS配置好主板使用的外设后,一些BIOS(UEFI)经过ACPI(高级配置和电源接口)的DSDT来传递设备信息(相似arm设备树,但功能更强)给操做系统,操做系统解析获取到这些设备信息后咱们才能在驱动配置和使用这个外设,但ACPI对各个操做系统有兼容性问题,这就会出如今Windows设备管理器能看到该设备,到linux下什么就也没有的现象,由于桌面CPU大多都是用的Windows系统,因此大部分X86硬件厂商的BIOS主要兼容Windows为主。BIOS又是咱们普通开发者没法接触修改的东西,不兼容怎么办。shell

本文说的GPIO就是这么个问题,linux下没法使用,因为涉及的东西有点多,因此简单介绍在如何将x86工控机引出的GPIO使用起来的(注意:是CPU的GPIO引脚,不是Super IO的GPIO)。函数

CPU :英特尔7代低压处理器( Kaby Lake) i5-7200U/赛扬3865U工具

linux:linux 4.0以上操作系统

2.linux pinctrl子系统

要使用gpio须要先看一下linux系统PINCTRL子系统,层级以下所示(图片来源蜗窝科技):.net

pinctrl

最底层是硬件控制器,其上是操做这些硬件的相关驱动(pin controller driver),不一样的控制器有不一样底层驱动,通常由芯片厂商BSP完成;pin controller driver初始化的时候会向pin control core模块注册pin control设备(经过pinctrl_register这个bootom level interface)。pin control core模块是一个硬件无关模块,它抽象了全部pin controller的硬件特性,仅仅从用户(各个driver就是pin control subsystem的用户)角度给出了top level的接口函数,这样,各个driver不须要关注pin controller的底层硬件相关的内容,使用时直接向pinctrl子系统申请IO资源便可。关于linux GPIO与pinctrl子系统信息,详见蜗窝科技-GPIO子系统.debug

pin controller driver成功注册到pin control core后,咱们经过pin control core导出到sysfs的文件就能够直接操做一个GPIO,使其输入输出,而不须要专门去写一个驱动模块。设计

3. pin controller driver

搞嵌入式的必定对platform bus很是熟悉,pin controller driver的注册一样离不开platform bus,driver与device必须通过某种匹配后,才能进一步执行probe注册到系统中。3d

pinctrl-bus

结合前言中对x86设备的描述,platform bus可经过如下两种方式来判断driver和device是否匹配。

  • 方式一,由BIOS经过ACPI 中DSDT传递控制器设备节点描述给linux(可类比设备树),linux内核启动过程当中解析处理DSDT信息,自动构造device设备并添加到Platform bus,添加过程当中匹配ACPI_ID,触发执行pin controller driver 的probe()函数。
  • 方式二,linux扫描PCI总线设备建立设备并添加,PCI驱动匹配vendor、device、class后触发执行pin controller driver 的probe()函数。

别忘了前提,启动时BIOS必须为使用的PCI设备分配好设备中断号(中断vector)、映射空间地址等咱们才能用。那对于咱们的GPIO设备linux系统使用的是哪一种方式呢,这须要到源码中来看,首先七代系列CPU linux pinctrl driver源码文件为\drivers\pinctrl\intel\pinctrl-sunrisepoint.c,看以下代码。

static const struct acpi_device_id spt_pinctrl_acpi_match[] = {
	{ "INT344B", (kernel_ulong_t)&sptlp_soc_data },
	{ "INT345D", (kernel_ulong_t)&spth_soc_data },
	{ }
};
MODULE_DEVICE_TABLE(acpi, spt_pinctrl_acpi_match);
.....
static struct platform_driver spt_pinctrl_driver = {
	.probe = spt_pinctrl_probe,
	.driver = {
		.name = "sunrisepoint-pinctrl",
		.acpi_match_table = spt_pinctrl_acpi_match,
		.pm = &spt_pinctrl_pm_ops,
	},
};

能够看到使用的是ACPI模式,那么驱动的注册逻辑应该以下,

PINCTRL

其中driver把系统中全部的pin描述出来,并将driver注册到platform bus。driver须要对应的device才能工做,可是linux由于ACPI的兼容性问题,linux并无解析DSDT并建立出GPIO 相关的device,因此没有触发执行probe来将pin controller driver注册到pin control core中,pinctrl子系统没有工做固然没法使用。到这里咱们去解决内核对ACPI的解析(或者说兼容性问题)显然是不太现实的(本身太菜(╯﹏╰)),有没有其余办法呢?

先阅读源码看看,probe()执行过程当中须要用到device的哪些resource,只要咱们能获取到这些resource,本身手动构造一个device注册到platform bus不就好了,O(∩_∩)O哈哈~。

int intel_pinctrl_probe(struct platform_device *pdev,
			const struct intel_pinctrl_soc_data *soc_data)
{
    ......
    for (i = 0; i < pctrl->ncommunities; i++) {
    	......
		res = platform_get_resource(pdev, IORESOURCE_MEM,
					    community->barno);//0
		regs = devm_ioremap_resource(&pdev->dev, res);
		......
	}
	......
	irq = platform_get_irq(pdev, 0);
    ......
}

能够看到pin controller driver须要pincontrler 的地址空间和使用的中断号两部分资源,其中地址空间是三个,由于全部GPIO由三个GPIO控制器组成,三个GPIO控制器共享相同的中断线,三个GPIO控制器做为一个PCI设备。如何获取这两个信息呢?

4.手动构造device

上面经过阅读源代码得知,intel-pinctrl须要pincontrler 地址空间、和使用的中断号两部分资源。

地址空间起始地址可经过PCI 设备P2SB Bridge (D31:F1)得到。中断vectorBIOS配置,反编译BIOS给linux传递的ACPI信息,看是否有中断vector相关信息:

在板子上进入/sys/firmware/acpi/tables,将目录下全部文件考出,使用acpi工具iasl对DSDT文件进行反编译:

iasl -d DSDT.dat

获得AML文件 DSDT.dsl,里面包含BIOS开发的各设备节点信息。

打开 DSDT.dsl并找到pin controler设备节点描述,只须要搜索驱动里的"INT344B"或"INT345D"就能定位到。到这里咱们也明白了,为何驱动里的spt_pinctrl_acpi_match[]有两像,原来是一个表明标压处理器(H),一个表明低压处理器(U)。

Device (GPI0)
        {
            Method (_HID, 0, NotSerialized)  // _HID: Hardware ID
            {
                If ((PCHV () == SPTH))
              {
                    If ((PCHG == 0x02))
                  {
                        Return ("INT3451")
                    }
                    Return ("INT345D")    //表示7代标压处理器
                }
                Return ("INT344B")		//表示7代低压处理器
            Name (LINK, "\\_SB.PCI0.GPI0")
            Method (_CRS, 0, NotSerialized)  // _CRS: Current Resource Settings
            {
                Name (RBUF, ResourceTemplate ()
                {
                    Memory32Fixed (ReadWrite,
                        0x00000000,         // Address Base
                        0x00010000,         // Address Length  地址空间大小
                        _Y2E)
                    Memory32Fixed (ReadWrite,
                        0x00000000,         // Address Base
                        0x00010000,         // Address Length   地址空间大小
                        _Y2F)
                    Memory32Fixed (ReadWrite,
                        0x00000000,         // Address Base
                        0x00010000,         // Address Length   地址空间大小
                        _Y31)
                    Interrupt (ResourceConsumer, Level, ActiveLow, Shared, ,, _Y30)
                    {
                        0x0000000E,		//中断号
                    }
                })
                                CreateDWordField (RBUF, \_SB.PCI0.GPI0._CRS._Y2E._BAS, COM0)  // _BAS: Base Address
              CreateDWordField (RBUF, \_SB.PCI0.GPI0._CRS._Y2F._BAS, COM1)  // _BAS: Base Address
              CreateDWordField (RBUF, \_SB.PCI0.GPI0._CRS._Y30._INT, IRQN)  // _INT: Interrupts
              COM0 = (SBRG + 0x00AF0000)
              COM1 = (SBRG + 0x00AE0000)
              CreateDWordField (RBUF, \_SB.PCI0.GPI0._CRS._Y31._BAS, COM3)  // _BAS: Base Address
              COM3 = (SBRG + 0x00AC0000)
              IRQN = SGIR /* \SGIR */
              Return (RBUF) /* \_SB_.PCI0.GPI0._CRS.RBUF */
                
       }

你可能看不懂上面面的信息,到底哪一个是标压哪一个是低压?不要紧,咱们去pin controller driver中,里面有注释,反推一下就知道INT345D表明的是标压,INT344B表明的是低压。

/* Sunrisepoint-LP */
static const struct pinctrl_pin_desc sptlp_pins[] = {
    ....
}
static const struct intel_pinctrl_soc_data sptlp_soc_data = {
	.pins = sptlp_pins,
    ...
}
.....
/* Sunrisepoint-H */
static const struct pinctrl_pin_desc spth_pins[] = {
    ....
}
static const struct intel_pinctrl_soc_data spth_soc_data = {
	.pins = spth_pins,
    ...
}
static const struct acpi_device_id spt_pinctrl_acpi_match[] = {
	{ "INT344B", (kernel_ulong_t)&sptlp_soc_data },
	{ "INT345D", (kernel_ulong_t)&spth_soc_data },
	{ }
};

回到正题,咱们从 DSDT.dsl获取获得中断号: 0xE,三个地址空间起始地址及大小。构建一个platform_device 以下:

#include <linux/debugfs.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#define P2SB_PORTID_SHIFT 16
#define P2SB_PORT_GPIO3 0xAC
#define P2SB_PORT_GPIO2 0xAD	/*未使用*/
#define P2SB_PORT_GPIO1 0xAE
#define P2SB_PORT_GPIO0 0xAF

#define sbreg_addr 0xfd000000 /*Address Base*/

/*Community 0*/
#define SPT_PINCTRL_COMMUNITY0_OFFSET		sbreg_addr + (P2SB_PORT_GPIO0 << P2SB_PORTID_SHIFT)
#define SPT_PINCTRL_COMMUNITY0_SIZE		0x00010000
/*Community 1*/
#define SPT_PINCTRL_COMMUNITY1_OFFSET		sbreg_addr + (P2SB_PORT_GPIO1 << P2SB_PORTID_SHIFT)
#define SPT_PINCTRL_COMMUNITY1_SIZE		0x00010000 
/*Community 2*/
#define SPT_PINCTRL_COMMUNITY2_OFFSET		sbreg_addr + (P2SB_PORT_GPIO2 << P2SB_PORTID_SHIFT)
#define SPT_PINCTRL_COMMUNITY2_SIZE		0x00010000
/*Community 3*/
#define SPT_PINCTRL_COMMUNITY3_OFFSET		sbreg_addr + (P2SB_PORT_GPIO3 << P2SB_PORTID_SHIFT)
#define SPT_PINCTRL_COMMUNITY3_SIZE		0x00010000


static struct resource intel_pinctrl_dev_resources[] = {
	/* iomem resource */
	DEFINE_RES_MEM_NAMED(SPT_PINCTRL_COMMUNITY0_OFFSET, SPT_PINCTRL_COMMUNITY0_SIZE, NULL),
	DEFINE_RES_MEM_NAMED(SPT_PINCTRL_COMMUNITY1_OFFSET, SPT_PINCTRL_COMMUNITY1_SIZE, NULL),
//	DEFINE_RES_MEM_NAMED(SPT_PINCTRL_COMMUNITY2_OFFSET, SPT_PINCTRL_COMMUNITY2_SIZE, NULL),/*未使用*/
	DEFINE_RES_MEM_NAMED(SPT_PINCTRL_COMMUNITY3_OFFSET, SPT_PINCTRL_COMMUNITY3_SIZE, NULL),
	/* irq resource */
	DEFINE_RES_IRQ(0x0E), /*反编译BIOS DSDT获取*/
};

static struct platform_device intel_pinctrl_device = {
	.name		= "sunrisepoint-pinctrl",
	.id		= -1,
	.resource	= intel_pinctrl_dev_resources,
	.num_resources	= ARRAY_SIZE(intel_pinctrl_dev_resources),
};

static int __init intel_spt_device_init(void)
{
	return platform_device_register(&intel_pinctrl_device);
}
module_init(intel_spt_device_init);

static void __exit intel_spt_device_exit(void)
{
	platform_device_unregister(&intel_pinctrl_device);
}
module_exit(intel_spt_device_exit);

MODULE_AUTHOR("wsg1100");
MODULE_DESCRIPTION("Intel  sunrisepoint pinctrl device");
MODULE_LICENSE("GPL v2");

随内核编译后,加载模块,intel pinctrl子系统正常工做,(^o^)/。
注意,相同平台,不一样BIOS PCI信息可能不一样!文中提供的只是一种方法

版权声明:本文为本文为博主原创文章,转载请注明出处,博客地址:https://www.cnblogs.com/wsg1100/。若有错误,欢迎指正。

相关文章
相关标签/搜索