最近處理es8336聲卡問題,最后排查是spk_ctl_gpio和hp_det_gpio這兩個gpio導致的,所以惡補了一下gpio相關的知識,現(xiàn)在總結一下。
源代碼使用的是飛騰的gitee上開源的內(nèi)核:https://gitee.com/phytium_embedded/phytium-linux-kernel.git
1. 概述
-
設備驅動層:定義了與硬件無關的GPIO API,包括GPIO的注冊、卸載和控制等功能,而實現(xiàn)了某個模塊的具體實現(xiàn),比如led燈、按鍵等等。
-
gpiolib抽象層:GPIO框架中的核心抽象層,它的作用是為設備驅動層和控制器層提供一致的接口,該層提供了包括上層設備驅動和下層控制器驅動的API接口。
-
控制器層:GPIO控制器的實現(xiàn)和管理,在該層中實現(xiàn)特定GPIO控制器的底層硬件操作和功能實現(xiàn)包括GPIO控制器的初始化、操作和管理等。負責GPIO寄存器的讀寫操作和GPIO中斷的處理等。
其中gpiolib抽象層是GPIO框架中的核心層,也是linux內(nèi)核自己實現(xiàn)的,一般情況下沒有人會修改這部分的代碼,控制器層一般是芯片廠家BSP工程師實現(xiàn)的,設備驅動層是驅動工程師根據(jù)開發(fā)版的實際情況實現(xiàn)的。優(yōu)秀的BSP工程師和驅動工程師可以把驅動寫得與硬件解耦,把硬件的信息填充到設備樹中,驅動讀取設備樹的信息進行各種操作。
2. 控制器
這里以飛騰e2000為例子看看gpio控制器的驅動是怎么樣的,在文件drivers/gpio/gpio-phytium-platform.c中,但是gpio的操作函數(shù)寫在drivers/gpio/gpio-phytium-core.c和drivers/gpio/gpio-phytium-core.h中。
2.1 設備樹
gpio0: gpio@28034000 {
compatible = "phytium,gpio";
reg = <0x0 0x28034000 0x0 0x1000>;
interrupts = <GIC_SPI 108 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 111 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
#address-cells = <1>;
#size-cells = <0>;
porta {
compatible = "phytium,gpio-port";
reg = <0>;
ngpios = <16>;
};
};
gpio3: gpio@28037000 {
compatible = "phytium,gpio";
reg = <0x0 0x28037000 0x0 0x1000>;
interrupts = <GIC_SPI 156 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
porta {
compatible = "phytium,gpio-port";
reg = <0>;
ngpios = <16>;
};
};
這里是gpio0和gpio3這個兩個gpio模塊,其實e2000有0到5,一共6個gpio模塊,每個模塊都有16個gpio,其中gpio0-2每個gpio都有一個硬件中斷號,而gpio3-5則是這個組共享一個硬件中斷號。當然,我看其他arm64的gpio都有復用功能,所以gpio的設備樹寫在pinctrl里面,現(xiàn)在e2000的gpio沒有復用到其他模塊,所以沒有使用pinctrl。
2.2 驅動的數(shù)據(jù)結構
以我多年的驅動經(jīng)驗來看,所有的gpio控制器驅動做的事情不外乎以下幾種:
- 分配私有數(shù)據(jù)結構體,這個結構體會包含struct gpio_chip結構體
- 讀取設備樹信息,填充gpio_chip結構體
- 把gpio_chip結構體綁定到平臺設備中
實際上gpio的驅動跟中斷和pinctrl放在一起的,初始化的時候會把irq_chip和 struct pinctrl_desc 和pinctrl_dev 一起填充的,這里主要講解gpio,就不多擴展了。我們先看struct gpio_chip結構體:
struct gpio_chip {
const char *label; //gpio控制器的名字
struct gpio_device *gpiodev; //gpio設備描述符
struct device *parent; //gpio的父設備
struct module *owner;
//下面是一系列的操作函數(shù)
int (*get_direction)(struct gpio_chip *gc,unsigned int offset);
int (*direction_input)(struct gpio_chip *gc,unsigned int offset);
int (*direction_output)(struct gpio_chip *gc,unsigned int offset, int value);
int (*get)(struct gpio_chip *gc,unsigned int offset);
void (*set)(struct gpio_chip *gc,unsigned int offset, int value);
int base; //gpio引腳基值
u16 ngpio; //gpio引腳個數(shù)
const char *const *names; //每一個引腳的名字
bool can_sleep; //控制器是否能睡眠
void __iomem *reg_dat; //gpio數(shù)據(jù)寄存器基地址
void __iomem *reg_set; //gpio設置寄存器基地址
void __iomem *reg_clr; //gpio控制寄存器基地址
void __iomem *reg_dir_out; //gpio輸出寄存器基地址
void __iomem *reg_dir_in; //gpio輸入寄存器基地址
};
gpio_chip結構體中包含了對應GPIO端口的硬件基地址、引腳數(shù)、GPIO組數(shù)、GPIO編號和IRQ號等重要信息。同時,它還包括了訪問GPIO寄存器的函數(shù)指針,例如讀取和寫入寄存器等函數(shù),以及描述GPIO的信息和配置的特定標志。其中最重要的是struct gpio_device *gpiodev; 他表示gpio設備描述符。這個結構體存放著gpio控制器的設備信息和每個引腳的描述符結構體,我們來看看struct gpio_device結構體:
struct gpio_device {
int id; //表示這是系統(tǒng)中第幾個GPIO控制器
struct device dev;
struct cdev chrdev;
struct device *mockdev;
struct module *owner;
struct gpio_chip *chip; //gpio管理硬件的結構體,記錄寄存器信息及其操作函數(shù)
struct gpio_desc *descs; //引腳的描述符指針,每一個引腳對應一個gpio_desc結構體
int base; //gpio號碼基值
u16 ngpio; //gpio個數(shù)
const char *label; //gpio控制器的名字
void *data;
struct list_head list;
struct blocking_notifier_head notifier;
};
gpio_device結構體中包含了與GPIO設備(GPIO控制器)相關的重要信息,例如GPIO控制器ID、GPIO號、GPIO個數(shù)、GPIO管理硬件的結構體和gpio引腳描述符結構體。最重要的是GPIO管理硬件的結構體gpio_chip 就是剛剛第一個介紹的數(shù)據(jù)結構,gpio設備是通過gpio_chip 找到對用的硬件信息和操作方法的;其次是gpio_desc結構體,每一個引腳對應一個gpio_desc結構體,他們通過數(shù)組的形式排列,我們看看gpio_desc結構體:
struct gpio_desc {
struct gpio_device *gdev; //屬于哪個GPIO控制器
unsigned long flags; //gpio引腳屬性,比如是否被使用、是否開漏等等
/* Connection label */
const char *label;
/* Name of the GPIO */
const char *name; //引腳名字
#ifdef CONFIG_OF_DYNAMIC
struct device_node *hog;
#endif
#ifdef CONFIG_GPIO_CDEV
/* debounce period in microseconds */
unsigned int debounce_period_us;
#endif
};
gpio_desc結構體主要記錄GPIO引腳的硬件信息和狀態(tài)信息。
這3個結構體的關系如上圖所示,以上這3個結構體就包含了gpio控制器的硬件信息、狀態(tài)信息和操作方法集合,這就我們的設備驅動提供了底層的基礎,我們的驅動就是通過gpiolib抽象層提供的API調(diào)用到這個操作方法的。
3. 設備驅動
在控制器驅動準備好的情況下,我們使用gpio是一件很簡單的事情,就拿es8336這個網(wǎng)卡驅動舉個例子,先看設備樹:
mio14: i2c@28030000 {
...
codec0:es8336@10 {
det-gpios = <&gpio2 5 0>;
sel-gpios = <&gpio2 6 0>;
...
};
};
其中gpio的設備樹就兩行,其他不重要的就忽略了。
驅動文件在sound/soc/codecs/es8336.c:文章來源:http://www.zghlxwxcb.cn/news/detail-646990.html
es8336->spk_ctl_gpio = devm_gpiod_get_index_optional(&i2c->dev, "sel", 0,
GPIOD_OUT_HIGH);
ret = of_property_read_u8(i2c->dev.of_node, "mic-src", &es8336->mic_src);
if (ret != 0) {
dev_dbg(&i2c->dev, "mic1-src return %d", ret);
es8336->mic_src = 0x20;
}
dev_dbg(&i2c->dev, "mic1-src %x", es8336->mic_src);
if (!es8336->spk_ctl_gpio)
dev_info(&i2c->dev, "Can not get spk_ctl_gpio\n");
else
es8336_enable_spk(es8336, false);
es8336->hp_det_gpio = devm_gpiod_get_index_optional(&i2c->dev, "det", 0,
GPIOD_IN);
if (!es8336->hp_det_gpio) {
dev_info(&i2c->dev, "Can not get hp_det_gpio\n");
} else {
INIT_DELAYED_WORK(&es8336->work, hp_work);
hp_irq = gpiod_to_irq(es8336->hp_det_gpio);
ret = devm_request_threaded_irq(&i2c->dev, hp_irq, NULL,
es8336_irq_handler,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING |
IRQF_ONESHOT,
"es8336_interrupt", es8336);
if (ret < 0) {
dev_err(&i2c->dev, "request_irq failed: %d\n", ret);
return ret;
}
}
其實設備驅動都是使用gpiolib提供的API:文章來源地址http://www.zghlxwxcb.cn/news/detail-646990.html
- gpiod_get_indexed:通過索引號獲取GPIO設備引腳。
- gpiod_get_optional:嘗試獲取GPIO設備引腳,如果失敗返回NULL,不會導致注冊失敗。
- gpiod_get_optional_indexed:與gpiod_get_optional類似,但通過索引號獲取GPIO設備引腳。
- gpiod_get_raw:通過GPIO編號獲取GPIO設備引腳,不進行方向和值的配置。
- devm_gpiod_get:這個函數(shù)自動為一個特定的設備申請所需的GPIO,不需要手動釋放,適合臨時使用的GPIO資源。
- devm_gpiod_get_index:這個函數(shù)允許為使用多個GPIO的設備申請多個GPIO引腳,返回一個struct gpiod_hanlde數(shù)組。
- devm_gpiod_get_optional:如果存在,允許驅動程序獲取所需的GPIO,而不會阻止設備與其余的GPIO資源一起初始化。
- devm_gpiod_get_optional_index:類似于devm_gpiod_get_optional,支持索引GPIO。
- gpio_request:向內(nèi)核申請一個GPIO引腳。
- gpio_free:釋放一個已經(jīng)使用的GPIO引腳。
- gpio_direction_input:配置GPIO引腳為輸入模式。
- gpio_direction_output:配置GPIO引腳為輸出模式。
- gpio_get_value:讀取GPIO引腳狀態(tài)。如果 GPIO 引腳已配置為輸出模式,則返回當前輸出值。如果 GPIO 引腳未配置或配置為輸入模式,則返回實際引腳上的輸入值。
- gpio_set_value:設置GPIO引腳狀態(tài)為高或低電平。如果 GPIO 引腳已配置為輸出模式,則設置GPIO引腳狀態(tài)為用戶指定電平;如果 GPIO 引腳是輸入模式,此函數(shù)沒有作用。
- gpio_to_irq:將 GPIO 引腳轉換為專用中斷號。
- gpio_request_one:請求單個GPIO。
- gpio_free_array:釋放一組由gpio_request_array()調(diào)用請求的GPIO。
- gpio_direction_input_array:將一組GPIO方向設置為輸入模式。
- gpio_direction_output_array:將一組GPIO方向設置為輸出模式。
- gpio_get_array:將一組GPIO值讀入緩沖區(qū)中。
- gpio_set_array:將一組GPIO指定的值寫入用戶指定的緩沖區(qū)中。
- gpio_get_value_cansleep:讀取GPIO引腳狀態(tài),如果引腳已配置為輸出模式,則將與其關聯(lián)的電平值復制到調(diào)用函數(shù)的參數(shù)變量中,如果引腳已配置為輸入模式,則等待GPIO中斷或超時發(fā)生后將其值復制到調(diào)用函數(shù)的參數(shù)變量中。
- gpio_set_value_cansleep:設置GPIO端口電平,如果引腳仍被配置為輸入模式,則什么也不做。
到了這里,關于linux驅動-gpio的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!