我的核心學習筆記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設備註冊
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 週三 晚