天天看點

GPIO系列(2)——Linux的GPIO控制“gpiod_”和“gpio_”淺析Devicetree擷取GPIO控制GPIO總結

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 *”,這個後面有機會再深入研究了!

繼續閱讀