1. 程式人生 > >我的核心學習筆記10:Intel GPIO驅動原始碼分析

我的核心學習筆記10:Intel GPIO驅動原始碼分析

本文對Intel e3800的GPIO驅動原始碼進行分析。

一、概述

1.1 核心配置

Intel e3800的GPIO在Linux核心中使用的驅動名為gpio_ich(為了行文方便,將對應的裝置稱為“gpio_ich裝置”)。驅動原始碼位於:drivers/gpio/gpio-ich.c

本文基於linux 3.17.1版本核心進行分析。

核心配置(make menuconfig)資訊如下:

Device Drivers  --->
    -*- GPIO Support  --->
        <M>   Intel ICH GPIO

使用模組形式編譯,在載入該驅動後,就可以使用lsmod檢視。另外,也會生成對應的驅動裝置目錄:/sys/bus/platform/devices/gpio_ich/。

1.2 暫存器介紹

intel e3800的GPIO手冊講的有點複雜。筆者花了好幾天時間搜尋才慢慢有點理解。在這裡僅說一下個人的理解。 1、分類 e3800的GPIO分2類,即SCORE和SSUS(看了baytrail的pingctrl,還有一個叫NCORE的),分別對應GPIO_S0_SC[xxx]和GPIO_S5[xx]。比如,GPIO_S0_SC[032]表示SCORE的第32個引腳。下面根據手冊各處查詢一下對應關係: GPIO_S0_SC[032] -> SD2_CMD -> SDMMC2_CMD -> bank2 pin0 -> 第二組暫存器bit0(手冊是South Core Use Select 2,從1開始。)
經過複雜的對比,終於知道:GPIO_S0_SC[032]就是bank2的pin0。計算公式為:gpio_pin / 32為bank值,gpio_pin % 32是這個bank的第幾個引腳。Linux核心的GPIO子系統也是這樣計算的。 2、SCORE SCORE一共4個Bank。引腳號範圍:GPIO_S0_SC[101:0] 。每個Bank使用6個暫存器控制,但主要的是use select、io select、io level這三個暫存器。下面以bank1為例介紹。 偏移值為0x0的是GPIO複用暫存器(Use Select)。名稱為:South Core Use Select 1 (cfio_ioreg_SC_USE_SEL_31_0_)  偏移值為0x04的是IO輸入輸出選擇暫存器(Io Select)。名稱為:South Core Io Select 1 (cfio_ioreg_SC_IO_SEL_31_0_)  偏移值為0x08的是IO電平暫存器(Gpio Level)(如為輸入則讀取暫存器值)。名稱為:South Core Gpio Level 1 (cfio_ ioreg_SC_GP_LVL_31_0_) 
bank2、bank3、bank4都是類似的,省略。從筆者實踐上看,似乎設定為輸出後,無法讀取Gpio Leve暫存器的值。 操作GPIO一般步驟: A、設定use select暫存器,將某個引腳複用為GPIO。設定為1表示將該引腳複用為GPIO。
B、設定io select暫存器,選擇該GPIO引腳為輸出還是輸入。
C、設定/讀取io level暫存器。設定1表示將該GPIO引腳拉高電平,0為低電平。當為輸入引腳時,讀取即為對應的GPIO電平值。(注:存疑待核實)
在gpio_ich驅動中,這三組暫存器的偏移量使用陣列表示,根據baseaddr索引,從而得到真實IO地址。 3、SSUS SSUS只有2個bank。引腳號範圍:GPIO_S5[43:0]。主要暫存器名稱如下:
Sus Use Select 1 (cfio_ioreg_SUS_USE_SEL_31_0_)
Sus Io Select 1 (cfio_ioreg_SU S_IO_SEL_31_0_)
Sus Io Select 1 (cfio_ioreg_SU S_IO_SEL_31_0_)
另一組省略不寫。 GPIO的介紹在e3800手冊的39章節。從手冊來看,SCORE表示“South Core”,NCORE可能表示“North Core”,SSUS暫找不到。像這裡說的GPIO_S0_SC[xxx],似乎是接到南橋上的引腳,但細節暫沒時間也不想去研究了。

二、gpio_ich設備註冊

gpio_ich裝置的註冊實現過程在檔案drivers/mfd/lpc_ich.c中。在LPC探測函式lpc_ich_probe對GPIO基地址進行賦值,程式碼如下:
        priv->gbase = GPIOBASE_ICH6; // GPIO基地址
        priv->gctrl = GPIOCTRL_ICH6;
其定義是:
#define GPIOBASE_ICH6		0x48
#define GPIOCTRL_ICH6		0x4C
前文也提及有0x48地址,所有地址都可以在手冊對應章節中找到。

直接初始化GPIO在函式lpc_ich_init_gpio中。這個函式對ACPI(GPE0)和GPIO都進行初始化。使用的resource結構體gpio_ich_res是一個數組,包含了GPE0和GPIO。這裡只看GPIO的部分,主要程式碼功能描述如下。

1、讀取GPIO基地址值,即通過LPC這個PCI裝置的配置空間偏移值GPIOBASE_ICH6。

	/* Setup GPIO base register */
	pci_read_config_dword(dev, priv->gbase, &base_addr_cfg);
	base_addr = base_addr_cfg & 0x0000ff80;

2、設定resource,即把前面獲取到的base_addr賦值給resource的start成員變數。注意,資源是IORESOURCE_IO型別

	res = &gpio_ich_res[ICH_RES_GPIO];
	res->start = base_addr;
	switch (lpc_chipset_info[priv->chipset].gpio_version) {
	case ICH_V5_GPIO:
	case ICH_V10CORP_GPIO:
		res->end = res->start + 128 - 1;
		break;
	default:
		res->end = res->start + 64 - 1;
		break;
	}
3、檢查GPIO衝突。
ret = lpc_ich_check_conflict_gpio(res);
4、使能GPIO:
lpc_ich_enable_gpio_space(dev);

即使能GPIO地址解碼,

e3800手冊中對GPIO使能解釋如下:

bit 1 Enable (EN): When set, decode of the IO range pointed to by the GBASE is enabled.

5、新增mfd裝置。
	lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_GPIO]);
	ret = mfd_add_devices(&dev->dev, -1, &lpc_ich_cells[LPC_GPIO],
			      1, NULL, 0, NULL);
其中lpc_ich_finalize_cell是設定mfd_cell結構體的成員platform_data。從上文知道,lpc_chipset_info儲存著名稱和一些模組的版本號。這樣,在對應的驅動中就能獲取到這個些值從而進行不同的處理。最後呼叫mfd_add_devices新增lpc_ich_cells[LPC_GPIO]——即GPIO的platform裝置。新增成功之後,就可以呼叫到gpio_ich驅動的probe函數了。

三、gpio_ich驅動

3.1 入口程式碼

gpio_ich是一個platform裝置驅動,其入口程式碼片段如下:
static struct platform_driver ichx_gpio_driver = {
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= DRV_NAME,
	},
	.probe		= ichx_gpio_probe,
	.remove		= ichx_gpio_remove,
};

module_platform_driver(ichx_gpio_driver);
該驅動直接使用module_platform_driver函式,而前面文章介紹的wdt使用module_init形式,其本質是一樣的。

3.2 探測函式

gpio_ich的探測函式為ichx_gpio_probe。下面分步分析其功能。

1、獲取裝置私有資料並初始化GPIO描述表ichx_priv.desc。

    struct lpc_ich_info *ich_info = dev_get_platdata(&pdev->dev);

    switch (ich_info->gpio_version) {
    case ICH_V6_GPIO:
        ichx_priv.desc = &ich6_desc;
        break;
    default:
        return -ENODEV;
    }

gpio_version是GPIO類別,根據lpc_ich驅動指定的值從而配置不同的desc。下文將會提到這個描述表。

2、獲取lpc_ich驅動設定的IO資源,並檢測GPIO是否可用,然後賦值GPIO基地址。

    res_base = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPIO);
    ichx_priv.use_gpio = ich_info->use_gpio;
    err = ichx_gpio_request_regions(res_base, pdev->name,
                    ichx_priv.use_gpio);
    if (err)
        return err;

    ichx_priv.gpio_base = res_base;
    
3、設定gpiolib函式,並新增到gpio子系統。最後列印可用的GPIO引腳號範圍。
    ichx_gpiolib_setup(&ichx_priv.chip);
    err = gpiochip_add(&ichx_priv.chip);
    if (err) {
        pr_err("Failed to register GPIOs\n");
        goto add_err;
    }

    pr_info("GPIO from %d to %d on %s\n", ichx_priv.chip.base,
           ichx_priv.chip.base + ichx_priv.chip.ngpio - 1, DRV_NAME);

3.3 gpiolib框架

探測函式最後呼叫gpiochip_add加入核心的gpio子系統。這樣在核心其它地方也能呼叫。其設定gpio_chip的函式如下:

static void ichx_gpiolib_setup(struct gpio_chip *chip)
{
	chip->owner = THIS_MODULE;
	chip->label = DRV_NAME;
	chip->dev = &ichx_priv.dev->dev;

	/* Allow chip-specific overrides of request()/get() */
	chip->request = ichx_priv.desc->request ?
		ichx_priv.desc->request : ichx_gpio_request;
	chip->get = ichx_priv.desc->get ?
		ichx_priv.desc->get : ichx_gpio_get;

	chip->set = ichx_gpio_set;
	chip->direction_input = ichx_gpio_direction_input;
	chip->direction_output = ichx_gpio_direction_output;
	chip->base = modparam_gpiobase;
	chip->ngpio = ichx_priv.desc->ngpio;
	chip->can_sleep = false;
	chip->dbg_show = NULL;
}

注意說明的是,chip->base是指可用的起始引腳號,chip->ngpio是GPIO可用數量。如4個bank,每個32,假如從第0個引腳開始到127均可使用。則chip->base為0,chip->ngpio等於128。

gpio-ich向gpiolib註冊的主要函式有:

申請GPIO:ichx_gpio_request

獲取GPIO:ichx_gpio_get

設定GPIO:ichx_gpio_set

設定GPIO方向:direction_input、direction_output

3.4 GPIO操作結構體

ich的GPIO結構體ichx_desc定義:
struct ichx_desc {
    /* Max GPIO pins the chipset can have */
    uint ngpio; // GPIO數量

    /* chipset registers */
    const u8 (*regs)[3]; // GPIO暫存器(即USE_SEL、IO_SEL、IO_LVL三個暫存器偏移量)
    const u8 *reglen; // 暫存器長度(即每組(bank)暫存器之間間隔)

    /* GPO_BLINK is available on this chipset */
    bool have_blink;

    /* Whether the chipset has GPIO in GPE0_STS in the PM IO region */
    bool uses_gpe0;

    /* USE_SEL is bogus on some chipsets, eg 3100 */
    u32 use_sel_ignore[3];

    /* Some chipsets have quirks, let these use their own request/get */
    int (*request)(struct gpio_chip *chip, unsigned offset);
    int (*get)(struct gpio_chip *chip, unsigned offset);

    /*
     * Some chipsets don't let reading output values on GPIO_LVL register
     * this option allows driver caching written output values
     */
    bool use_outlvl_cache; // 是否緩衝LVL暫存器,因為有的晶片讀不了方向為輸出的IO_LVL
};

看一下ich6_desc的定義:

static struct ichx_desc ich6_desc = {
    /* Bridges using the ICH6 controller need fixups for GPIO 0 - 17 */
    .request = ich6_gpio_request,
    .get = ich6_gpio_get,

    /* GPIO 0-15 are read in the GPE0_STS PM register */
    .uses_gpe0 = true,

    .ngpio = 50,
    .have_blink = true,
    .regs = ichx_regs,
    .reglen = ichx_reglen,
};
由於ich6控制器特殊一點,所以request和get需要另外實現。

它的暫存器偏移值定義如下:

static const u8 ichx_regs[4][3] = {
    {0x00, 0x30, 0x40},    /* USE_SEL[1-3] offsets */
    {0x04, 0x34, 0x44},    /* IO_SEL[1-3] offsets */
    {0x0c, 0x38, 0x48},    /* LVL[1-3] offsets */
    {0x18, 0x18, 0x18},    /* BLINK offset */
};


static const u8 ichx_reglen[3] = {
    0x30, 0x10, 0x10,
};
ichx_reglen指定了每組暫存器的長度間隔。ichx_reglen[0]為0x30,即使用第一組和第二組USE_SEL之間相差0x30的間隔。詳細細節參考手冊。

3.5 GPIO訪問

GPIO訪問使用inl和outl函式。根據GPIO_REG列舉操作不同類別的暫存器。
#define ICHX_WRITE(val, reg, base_res)    outl(val, (reg) + (base_res)->start)
#define ICHX_READ(reg, base_res)    inl((reg) + (base_res)->start)


enum GPIO_REG {
    GPIO_USE_SEL = 0,
    GPIO_IO_SEL,
    GPIO_LVL,
    GPO_BLINK
};
所有函式最終使用ichx_write_bit和ichx_read_bit。其函式如下:
static int ichx_write_bit(int reg, unsigned nr, int val, int verify)
{
    unsigned long flags;
    u32 data, tmp;
    int reg_nr = nr / 32;
    int bit = nr & 0x1f;
    int ret = 0;

    spin_lock_irqsave(&ichx_priv.lock, flags);

    if (reg == GPIO_LVL && ichx_priv.desc->use_outlvl_cache)
        data = ichx_priv.outlvl_cache[reg_nr];
    else
        data = ICHX_READ(ichx_priv.desc->regs[reg][reg_nr],
                 ichx_priv.gpio_base);

    if (val)
        data |= 1 << bit;
    else
        data &= ~(1 << bit);
    ICHX_WRITE(data, ichx_priv.desc->regs[reg][reg_nr],
             ichx_priv.gpio_base);
    if (reg == GPIO_LVL && ichx_priv.desc->use_outlvl_cache)
        ichx_priv.outlvl_cache[reg_nr] = data;

    tmp = ICHX_READ(ichx_priv.desc->regs[reg][reg_nr],
            ichx_priv.gpio_base);
    if (verify && data != tmp)
        ret = -EPERM;

    spin_unlock_irqrestore(&ichx_priv.lock, flags);

    return ret;
}

static int ichx_read_bit(int reg, unsigned nr)
{
    unsigned long flags;
    u32 data;
    int reg_nr = nr / 32;
    int bit = nr & 0x1f;

    spin_lock_irqsave(&ichx_priv.lock, flags);

    data = ICHX_READ(ichx_priv.desc->regs[reg][reg_nr],
             ichx_priv.gpio_base);

    if (reg == GPIO_LVL && ichx_priv.desc->use_outlvl_cache)
        data = ichx_priv.outlvl_cache[reg_nr] | data;

    spin_unlock_irqrestore(&ichx_priv.lock, flags);

    return data & (1 << bit) ? 1 : 0;
}
如申請GPIO函式ichx_gpio_request,定義如下:
static int ichx_gpio_request(struct gpio_chip *chip, unsigned nr)
{
    if (!ichx_gpio_check_available(chip, nr))
        return -ENXIO;

    /*
     * Note we assume the BIOS properly set a bridge's USE value.  Some
     * chips (eg Intel 3100) have bogus USE values though, so first see if
     * the chipset's USE value can be trusted for this specific bit.
     * If it can't be trusted, assume that the pin can be used as a GPIO.
     */
    if (ichx_priv.desc->use_sel_ignore[nr / 32] & (1 << (nr & 0x1f)))
        return 0;

    return ichx_read_bit(GPIO_USE_SEL, nr) ? 0 : -ENODEV;
}
首先呼叫ichx_gpio_check_available檢測GPIO是否可用,如不可用,返回ENXIO錯誤。然後讀取GPIO_USE_SEL對應的引腳號。如其為1表示該引腳是GPIO功能,返回成功,否則返回ENODEV表示這個引腳已經作他用,無法申請。

其它函式可閱讀gpio-ich.c瞭解更多。

四、免坑指南

1、gpio_ich僅適合於ICH系列的GPIO,有點不合適e3800,所以驅動要做修改,比如GPIO暫存器偏移值與ICH的不同。

2、gpio_ich根據modparam_gpiobase來選擇起始GPIO引腳號。預設為-1,即表示由系統動態分配。因此要根據板子的硬體定義來確定可用範圍,否則使用其它如leds-gpio、i2c-gpio等驅動時會遇到麻煩。

3、檢測GPIO是否可用的函式ichx_gpio_check_available根據lpc_ich驅動傳遞的use_gpio做判斷。use_gpio在lpc_ich_check_conflict_gpio函式賦值,use_gpio的每個位元表示可用的GPIO的bank,如use_gpio為7,表示bank0~bank2可使用,而bank3不可用。

Intel的GPIO的確比較複雜,以上幾點免坑指南也是筆者花了幾天跟蹤程式碼得到的經驗之談。

參考資源:

李遲 2016.12.08 週三 晚