Linux的GPIO控制“gpiod_”和“gpio_”淺析
- Devicetree
- 擷取GPIO
-
- of函數擷取
- gpiod_函數擷取
- 控制GPIO
-
- gpio_函數控制
- gpiod_函數控制
- 總結
平台:MT6739
Kernel:4.4
新linux核心對于gpio的控制,從以前的舊的“gpio_”開頭的函數,已經變為“gpiod_”開頭的函數,本文簡單對比分析下。
具體文檔可以檢視:
kernel-4.4\Documentation\gpio\consumer.txt
kernel-4.4\Documentation\gpio\gpio-legacy.txt
Devicetree
裝置樹的變化不大,基本還是下面的結構:
gpio1: gpio1 {
gpio-controller
#gpio-cells = <2>;
};
gpio2: gpio2 {
gpio-controller
#gpio-cells = <1>;
};
[...]
//裝置節點内要聲明的gpio屬性示例
enable-gpios = <&gpio2 2>;
data-gpios = <&gpio1 12 0>,
<&gpio1 13 0>,
<&gpio1 14 0>,
<&gpio1 15 0>;
裝置節點内聲明的gpio屬性(gpios property),格式保持沒變,每個list的第一個元素依然是具體IO的controller的句柄,剩下的元素個數,由對應controller的“#gpio-cells”屬性的值決定,常用的是“#gpio-cells = < 2 >;”。一個為IO的number,一個為IO需要配置的狀态,0 = GPIO_ACTIVE_HIGH,具體定義見dts目錄下的 include/dt-bindings/gpio/gpio.h 檔案。
有變化的地方在gpio屬性的命名,就是上面示例的“enable-gpios”和“data-gpios”。之前舊的版本,建議命名方式為“[< name >-]gpios”,其實也可以不用按這種方式命名,在源碼擷取gpio的時候,用比如“of_get_named_gpio”這種可以傳name進去的函數,把自己的gpio屬性名字傳進去,也可以擷取到gpio。例如:
gpio-pin-id = <&gpio 1 GPIO_ACTIVE_HIGH>;
//将gpio屬性的名字“gpio-pin-id”傳入下面函數
id_gpio = of_get_named_gpio(pdev->dev.of_node, "gpio-pin-id", 0);
但是,用新的函數的話,建議按照推薦的命名方式,這樣可以不用“of”函數,直接擷取到需要的gpio,例如:
enable-gpios = <&gpio2 2>;
//将gpio屬性的名字字首“enable”傳入下面函數
enable = gpiod_get(&(pdev->dev), "enable", GPIOD_IN);
以上内容可以檢視文檔:
kernel-4.4\Documentation\devicetree\bindings\gpio\gpio.txt
kernel-4.4\Documentation\gpio\board.txt
擷取GPIO
之前舊的方式從dts擷取gpio,基本都要用of函數,現在新的方式可以直接使用“gpiod_”開頭的函數,例如,有如下3種命名方式的gpio屬性:
gpios = <&gpio1 12 0>;
enable-gpios = <&gpio2 2>;
gpio-pin-id = <&gpio 1 GPIO_ACTIVE_HIGH>;
of函數擷取
頭檔案:kernel-4.4\include\linux\of_gpio.h
int of_get_gpio(struct device_node *np, int index);
int of_get_gpio_flags(struct device_node *np, int index,enum of_gpio_flags *flags);
上面這兩個函數隻能擷取的“gpios”的這個gpio,因為這兩個函數,最終都是調用“of_get_named_gpio_flags”,并預設傳入了“gpios”這個name。
static inline int of_get_gpio(struct device_node *np, int index)
{
return of_get_gpio_flags(np, index, NULL);
}
static inline int of_get_gpio_flags(struct device_node *np, int index,
enum of_gpio_flags *flags)
{
return of_get_named_gpio_flags(np, "gpios", index, flags);
}
int of_get_named_gpio_flags(struct device_node *np, const char *list_name, int index, enum of_gpio_flags *flags);
這個函數可以擷取到以上3種gpio,隻要将對應的name傳入這個函數就行。
gpiod_函數擷取
頭檔案:kernel-4.4\include\linux\gpio\consumer.h
源檔案:kernel-4.4\drivers\gpio\gpiolib.c
使用“gpiod_”函數,隻能直接擷取到“gpios”和“enable-gpios”這兩個gpio,例如函數“gpiod_get_index”:
struct gpio_desc *__must_check gpiod_get_index(struct device *dev,
const char *con_id,
unsigned int idx,
enum gpiod_flags flags)
{
......
if (dev) {
/* Using device tree? */
if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
dev_dbg(dev, "using device tree for GPIO lookup\n");
//注意這個函數“of_find_gpio”
desc = of_find_gpio(dev, con_id, idx, &lookupflags);
} else if (ACPI_COMPANION(dev)) {
dev_dbg(dev, "using ACPI for GPIO lookup\n");
desc = acpi_find_gpio(dev, con_id, idx, &lookupflags);
}
}
......
}
這個函數調用到了下面一個函數“of_find_gpio”:
static struct gpio_desc *of_find_gpio(struct device *dev, const char *con_id,
unsigned int idx,
enum gpio_lookup_flags *flags)
{
......
for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
//這裡處理“gpiod_”函數傳進來的name
if (con_id)
snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id,
gpio_suffixes[i]);
else
snprintf(prop_name, sizeof(prop_name), "%s",
gpio_suffixes[i]);
desc = of_get_named_gpiod_flags(dev->of_node, prop_name, idx,
&of_flags);
if (!IS_ERR(desc) || (PTR_ERR(desc) == -EPROBE_DEFER))
break;
}
......
}
可以看到,這裡将“gpiod_”函數傳進來的name加了字尾,這個字尾就是“gpios”,如果name為空的話,就隻有一個字尾。是以,當“gpiod_”函數傳入的name為“enable”時,得到“enable-gpios”這個gpio,當傳入的name為“”時,得到“gpios”這個gpio。
那麼還有一個“gpio-pin-id”其實也是可以用“gpiod_”函數去控制的,不過先要用舊方式of函數擷取到舊類型的gpio,然後使用函數:
struct gpio_desc *gpio_to_desc(unsigned gpio);
來擷取到新類型的gpio。
控制GPIO
gpio_函數控制
一般的流程是,先請求一個gpio,判斷是否擷取到了:
int gpio_request(unsigned gpio, const char *label);
ret = gpio_request(gpio_num, "gpio");
if (ret) {
pr_err("Could not get GPIO %d", gpio_num);
return ret;
}
然後初始化為輸入或者輸出:
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
if (input) {
if (gpio_direction_input(gpio_num)) {
pr_err("Could not set direction of GPIO %d to input", gpio_num);
goto err;
}
} else {
if (gpio_direction_output(gpio_num, 0)) {
pr_err("Could not set direction of GPIO %d to output", gpio_num);
goto err;
}
}
然後單獨控制輸出改變,或者擷取輸入狀态:
int gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value);
使用完之後,再釋放gpio:
void gpio_free(unsigned gpio);
gpiod_函數控制
依然以函數“gpiod_get_index”為例:
struct gpio_desc *__must_check gpiod_get_index(struct device *dev,
const char *con_id,
unsigned int idx,
enum gpiod_flags flags)
{
struct gpio_desc *desc = NULL;
int status;
enum gpio_lookup_flags lookupflags = 0;
dev_dbg(dev, "GPIO lookup for consumer %s\n", con_id);
if (dev) {
/* Using device tree? */
if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
dev_dbg(dev, "using device tree for GPIO lookup\n");
desc = of_find_gpio(dev, con_id, idx, &lookupflags);
} else if (ACPI_COMPANION(dev)) {
dev_dbg(dev, "using ACPI for GPIO lookup\n");
desc = acpi_find_gpio(dev, con_id, idx, &lookupflags);
}
}
/*
* Either we are not using DT or ACPI, or their lookup did not return
* a result. In that case, use platform lookup as a fallback.
*/
if (!desc || desc == ERR_PTR(-ENOENT)) {
dev_dbg(dev, "using lookup tables for GPIO lookup\n");
desc = gpiod_find(dev, con_id, idx, &lookupflags);
}
if (IS_ERR(desc)) {
dev_dbg(dev, "lookup for GPIO %s failed\n", con_id);
return desc;
}
status = gpiod_request(desc, con_id);
if (status < 0)
return ERR_PTR(status);
status = gpiod_configure_flags(desc, con_id, lookupflags, flags);
if (status < 0) {
dev_dbg(dev, "setup of GPIO %s failed\n", con_id);
gpiod_put(desc);
return ERR_PTR(status);
}
return desc;
}
EXPORT_SYMBOL_GPL(gpiod_get_index);
可以看到,在這個函數裡,已經做了3個動作,分别是函數“of_find_gpio”,“gpiod_request”,“gpiod_configure_flags”;
這3個函數,分别對應舊代碼裡面的“of_get_named_gpio_flags”,“gpio_request”,“gpio_direction_input/gpio_direction_output”;
是以,這一個函數就已經整合了,從devicetree擷取gpio,請求gpio,初始化gpio這3步,非常友善:
struct gpio_desc *enable;
//擷取到裝置樹裡面的“enable-gpios”這個gpio,并初始化為輸出低
enable = gpiod_get_index(&(pdev->dev), "enable", 0, GPIOD_OUT_LOW);
if (IS_ERR(enable ))
printk("Cannot find enable-gpios! \n");
擷取到gpio之後,就可以和以前一樣對gpio單獨操作:
int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);
上面兩個函數依然是不能休眠,要休眠用下面:
int gpiod_get_value_cansleep(const struct gpio_desc *desc);
void gpiod_set_value_cansleep(struct gpio_desc *desc, int value);
gpio使用之後,也要釋放,不過是用:
void gpiod_put(struct gpio_desc *desc);
和“gpio_free”功能類似。
總結
新的“gpiod_”方式,單純從API的使用上來看,主要就是把之前要分幾步去做的事情,整合到了一起,使用起來更友善一些,也更規範了一下;對于更複雜的内部實作的機制,比如,之前擷取到的gpio是一個“int”類型,現在擷取到的是“struct gpio_desc *”,這個後面有機會再深入研究了!