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/。若有錯誤,歡迎指正。

相關文章
相關標籤/搜索