GPIO Subsystem -3- (Device Tree)

 

gpio 데이터를 매핑하여 사용할 수 있는 방법은 다음과 같이 약 3가지 방법이 있다.

  • 디바이스 트리로 정의 (가장 최근 Trend)
    • ACPI 마저도 등록하여 사용할 수 있다.
  • Legacy Board/Machine specific code에서 플랫폼 데이터로 정의
    • 사실 GPIO 매핑을 플랫폼 데이터에 등록하여 사용하는 경우는 많지 않았다. 그냥 핀 번호로 사용하는 경우가 대부분이었다.
  • ACPI 펌웨어 테이블에 정의

 

디바이스 트리를 사용하는 GPIO 매핑

GPIO Controller 노드

디바이스 노드 내부에 “gpio-controller” 속성이 있으면 GPIO Controller 노드를 의미한다.

 

cell 개수

#gpio-cells = <2> 속성은 two-cell 데이터 argument를 사용함을 의미한다.

  • gpio1 controller는 2개의 cell을 사용하여 디바이스 드라이버가 2개의 argument를 받아 처리한다.
  • gpio2 controller는 1개의 cell을 사용하여 디바이스 드라이버가 1개의 argument를 받아 처리한다.
  • 지정되지 않는 경우 2 cell 방식을 사용한다.
	gpio1: gpio1 {
		gpio-controller;
		 #gpio-cells = <2>;
	};
	gpio2: gpio2 {
		gpio-controller;
		 #gpio-cells = <1>;
	};
	[...]

	enable-gpios = <&gpio2 2>;
	data-gpios = <&gpio1 12 0>,
		     <&gpio1 13 0>,
		     <&gpio1 14 0>,
		     <&gpio1 15 0>;

 

Pin control subsystem과의 연동

pin control 서브시스템과의 연동은 gpio controller 노드에서 “gpio-ranges” 속성을 사용하여 한다. 다음 코드를 알아보자.

“gpio-ranges” 속성이 가리키는 phandle은 연계된 pin controller 노드를 가리켜야 한다. 그리고 1~3개의 인자를 사용할 수 있으며, 배열 사용을 지원한다.

  • 1~2개 인자를 사용하는 경우 반드시 “gpio-ranges-group-names” 속성을 사용하여야 한다.
    • 첫 번째 인자: 시작 gpio 번호(offset)
    • 두 번째 인자: “gpio-ranges-group-names” 속성에서 지정한 그룹명에 해당하는 pin 시작 번호와 개수를 사용한다. 따라서 이 항목이 사용되는 경우에는 반드시 0이어야 한다.
	gpio-ranges = <&pinctrl 0 0>; 
        gpio-ranges-group-names = "gpioa"

 

  • 3개 인자를 사용하는 경우 “gpio-ranges-group-names” 속성이 필요 없고, 사용되더라도 빈 문자열이어야 한다.
    • 첫 번째 인자: 시작 gpio 번호(offset)
    • 두 번째 인자: 시작 pin 번호(offset)
    • 세 번째 인자: gpio 개수
	gpio-ranges = <&pinctrl 0 0 16>;

 

다음은 배열 형태로 사용된 경우이다.

        gpio-ranges = <&pinctrl 0 16 3>, <&pinctrl 14 30 2>;

 

다음 사용 사례를 살펴본다.

  • iomux pin controller 노드
    • pctl-gpio-a 노드는 pinmux 노드 2개를 선언하였다.
    • 이 벤더의 디바이스 트리 노드 맵 변환은 고유의 방법을 사용하는데 pins 속성이나 groups 속성이 지정되지 않았음을 알 수 있다. 이러한 경우 consumer 디바이스 측에서 사용하는 compact 노드명을 그대로 그룹명으로 사용한다. 아래 두 consumer 디바이스의 노드명을 보면 “uart@FF100000″과 “gpio@FF140000″이 있는데 compact 노드명을 그룹명으로 사용하므로 이들은 각각 “uart”와 “gpio” 그룹명이 된다.
  • uart (pin control consumer) 노드
    • “default” 스테이트를 사용하여 pinctrl-0 속성에서 지정된 phandle 노드로 pinmux/pinconf 매핑을 등록한다. “uart” 그룹과 pinmux 노드에서 정의한 “uart0” 펑션을 매핑한다.
    • 관련 함수: drivers/pinctrl/pinctrl-tb10x.c – tb10x_dt_node_to_map()
  • gpioa (pin control consumer) 노드
    • “gpio-ranges” 속성을 사용하여 iomux 핀 controller 노드와 연계한다.
    • gpio 0번부터 pin controller에서 구현된 그룹명들 중 “gpio-ranges-group-names” 속성에서 지정한 “gpioa” 그룹명에 해당하는 핀들을 gpio 핀들로 등록하게 한다.
iomux: iomux@FF10601c {
        compatible = "abilis,tb10x-iomux";
        reg = <0xFF10601c 0x4>;
        pctl_gpio_a: pctl-gpio-a {
                abilis,function = "gpioa";
        };
        pctl_uart0: pctl-uart0 {
                abilis,function = "uart0";
        };
};
uart@FF100000 {
        compatible = "snps,dw-apb-uart";
        reg = <0xFF100000 0x100>;
        clock-frequency = <166666666>;
        interrupts = <25 1>;
        reg-shift = <2>;
        reg-io-width = <4>;
        pinctrl-names = "default";
        pinctrl-0 = <&pctl_uart0>;
};
gpioa: gpio@FF140000 {
        compatible = "abilis,tb10x-gpio";
        reg = <0xFF140000 0x1000>;
        gpio-controller;
        #gpio-cells = <2>;
        ngpios = <3>;
        gpio-ranges = <&iomux 0 0>;
        gpio-ranges-group-names = "gpioa";
};

 

line 명 지정

gpio controller를 등록하기 전에 gpio_chip->ngpios에 gpio 개수를 등록하거나 “ngpios” 속성을 사용하여 gpio 핀 개수를 지정하여 gpio controller가 사용하는 gpio 핀 개수를 먼저 알고 있어야한다. 그 후 “gpio-line-names” 속성을 사용하여 gpio 핀 개수만큼 gpio 디스크립터들의 이름을 지정한다.

  • gpio 디스크립터들의 핀명이 gpio 디바이스  드라이버의 내부 코드로 이미 정해져있는 경우에는 별도로 “gpio-line-names” 속성을 사용할 필요 없다.
gpio-controller@00000000 {
	compatible = "foo";
	reg = <0x00000000 0x1000>;
	gpio-controller;
	#gpio-cells = <2>;
	ngpios = <18>;
	gpio-line-names = "MMC-CD", "MMC-WP", "VDD eth", "RST eth", "LED R",
		"LED G", "LED B", "Col A", "Col B", "Col C", "Col D",
		"Row A", "Row B", "Row C", "Row D", "NMI button",
		"poweroff", "reset";
}

 

hog 처리

gpio 컨트롤러의 하위 노드들에서 “gpio-hog” 속성이 발견되는 경우 지정된 gpio 핀 상태를 즉시 설정한다. 커널 v.4.1-rc1에 추가되었다.

  • “gpios” 속성에 해당하는 argument의 처리는 벤더가 제공하는 gpio 컨트롤러에서 구현되는데, 대부분의 벤더들은 이의 구현을 하지 않고 gpio simple 변환을 사용한다.
    • 2개의 셀을 사용하는 gpio simple 변환은 gpio 시작 번호(offset)를 변환없이 그대로 사용하고, gpio 설정 값에 해당하는 두 번째 인수 역시 그대로 변환 없이 사용한다.
  • 참고: gpio: add GPIO hogging mechanism
	qe_pio_a: gpio-controller@1400 {
		compatible = "fsl,qe-pario-bank-a", "fsl,qe-pario-bank";
		reg = <0x1400 0x18>;
		gpio-controller;
		#gpio-cells = <2>;

		line_b {
			gpio-hog;
			gpios = <6 0>;
			output-low;
			line-name = "foo-bar-gpio";
		};
	};

 

GPIO Client 노드 (for Consumer)

다음과 같이 “X-gpios = <&gpio_controller Y Z>;” 속성 형식을 사용하는 X 이름을 갖는 gpio 매핑은 Y와 Z 두 개의 셀을 데이터 argument를 사용하는데, 첫 번째 argument는 보통 gpio 번호, 두 번째는 상태를 지정한다.

  • led라는 이름으로 0~2번 인덱스 각각에 15~17번 핀을 active_high 상태로 동작하도록 매핑한다.
  • power라는 이름으로 1번 핀을 active_low 상태로 동작하도록 매핑한다.
    • GPIO_ACTIVE_LOW 상태에 대해서는 착각하기 쉬우므로 주의해야 한다.
	foo_device {
		compatible = "acme,foo";
		...
		led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
			    <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
			    <&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */

		power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
	};

 

코드에서 위의 매핑을 불러서 동작 시키고자할 때에는 다음과 같이 호출한다.

  • led 라는 이름의 gpio 0~2번 인덱스의 로지컬 출력을 high로 설정한다.
    • 매핑 시 active_high에서 동작하게 하였으므로 로지컬 출력을 high로 하면 gpio 핀은 물리적 high 상태가 된다. 의미적으로는 led가 점등(on)한다.
  • power라는 이름의 gpio 로지컬 출력을 high로 설정한다.
    • 매핑 시 active_low에서 동작하게 하였으므로 로지컬 출력을 high로 하면 gpio 핀은 물리적 low 상태가 된다. 의미적으로는 파워가 켜진다.(on)
	struct gpio_desc *red, *green, *blue, *power;

	red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
	green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
	blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);

	power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);

 

참고

 

 

ACPI 펌웨어를 사용하는 GPIO 매핑

디바이스 트리를 사용하는 방법과 아주 유사한 방법으로 아래의 ACPI 디스크립션을 사용하는 방법이 있다. ACPI 5.1에서 소개된 _DSD (Device Specific Data)를 참조하기 바란다.

	Device (FOO) {
		Name (_CRS, ResourceTemplate () {
			GpioIo (Exclusive, ..., IoRestrictionOutputOnly,
				"\\_SB.GPI0") {15} // red
			GpioIo (Exclusive, ..., IoRestrictionOutputOnly,
				"\\_SB.GPI0") {16} // green
			GpioIo (Exclusive, ..., IoRestrictionOutputOnly,
				"\\_SB.GPI0") {17} // blue
			GpioIo (Exclusive, ..., IoRestrictionOutputOnly,
				"\\_SB.GPI0") {1} // power
		})

		Name (_DSD, Package () {
			ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
			Package () {
				Package () {
					"led-gpios",
					Package () {
						^FOO, 0, 0, 1,
						^FOO, 1, 0, 1,
						^FOO, 2, 0, 1,
					}
				},
				Package () {
					"power-gpios",
					Package () {^FOO, 3, 0, 0},
				},
			}
		})
	}

 

Platform 데이터에 GPIO 매핑 (deprecated)

일부 시스템에서 다음 매크로 함수 및 API를 사용하여 매핑을 플랫폼 데이터에 저장(bound)한 후 이를 lookup하여 사용하였었는데 지금은 사용하지 않는 방법이다.

	GPIO_LOOKUP(chip_label, chip_hwnum, con_id, flags)
	GPIO_LOOKUP_IDX(chip_label, chip_hwnum, con_id, idx, flags)

 

gpio 룩업 테이블 정의를 한 후

struct gpiod_lookup_table gpios_table = {
	.dev_id = "foo.0",
	.table = {
		GPIO_LOOKUP_IDX("gpio.0", 15, "led", 0, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX("gpio.0", 16, "led", 1, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX("gpio.0", 17, "led", 2, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP("gpio.0", 1, "power", GPIO_ACTIVE_LOW),
		{ },
	},
};

 

다음 함수로 등록을 한다.

gpiod_add_lookup_table(&gpios_table);

 

그런 후 아래와 같은 방식으로 호출하여 사용할 수 있다.

	struct gpio_desc *red, *green, *blue, *power;

	red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
	green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
	blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);

	power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);

 

디바이스 트리 소스

gpio controller 노드를 파싱하고 추가할 때의 함수 호출관계이다.

 

of_gpiochip_add()

drivers/gpio/gpiolib-of.c

int of_gpiochip_add(struct gpio_chip *chip)
{
        int status;

        if ((!chip->of_node) && (chip->parent))
                chip->of_node = chip->parent->of_node;

        if (!chip->of_node)
                return 0;

        if (!chip->of_xlate) {
                chip->of_gpio_n_cells = 2;
                chip->of_xlate = of_gpio_simple_xlate;
        }

        if (chip->of_gpio_n_cells > MAX_PHANDLE_ARGS)
                return -EINVAL;

        status = of_gpiochip_add_pin_range(chip);
        if (status)
                return status;

        /* If the chip defines names itself, these take precedence */
        if (!chip->names)
                devprop_gpiochip_set_names(chip);

        of_node_get(chip->of_node);

        return of_gpiochip_scan_gpios(chip);
}

gpio controller 오퍼레이션을 포함한 gpio_chip 구조체를 등록하고 디바이스 트리의 gpio controller 노드에서 각종 속성들을 파싱하여 처리한다.

  • 코드 라인 5~9 인자로 전달받은 gpio_chip 구조체에 디바이스 노드가 지정되지 않은 경우 부모 gpio controller 디바이스의 디바이스 노드를 알아와서 사용한다. 만일 디바이스 노드가 없는 경우 이 함수를 처리하지 않고 에러 없이 빠져나간다.
  • 코드 라인 11~13에서 gpio_chip의 (*of_xlate) 후크 함수가 구현되지 않은 경우 two cell 방식의 기본 함수인 og_gpio_simple_xlate()를 사용한다.
  • 코드 라인 16~17에서 gpio cell 수가 MAX_PHANDLE_ARGS(16)을 초과하는 경우 에러 결과를 반환한다.
  • 코드 라인 19~21에서 pin controller 노드와 연결되어 사용되는 gpio controller인 경우 “gpio-ranges” 속성을 사용하여 연계할 pin controller의 지정된 핀들을 gpio 컨트롤러에 등록한다.
  • 코드 라인 24~25에서 gpio controller 노드에서 gpio 이름이 주어지지 않은 경우 부모 gpio controller 노드의 “gpio-line-names” 속성 값들을 읽어서 gpio 디스크립터들의 name으로 사용한다.
    • gpio  controller 노드에서 “X-gpios ” 속성을 사용하지 않은 경우 “gpio-line-names” 속성에서 사용한 이름들을 gpio 이름으로 가져온다.
  • 코드 라인 29에서 gpio controller 노드의 서브 노드에서 “gpio-hog” 속성을 발견하는 경우 hog 매핑들을 읽어온다.

 

of_gpiochip_add_pin_range()

drivers/gpio/gpiolib-of.c

#ifdef CONFIG_PINCTRL
static int of_gpiochip_add_pin_range(struct gpio_chip *chip)
{
        struct device_node *np = chip->of_node;
        struct of_phandle_args pinspec;
        struct pinctrl_dev *pctldev;
        int index = 0, ret;
        const char *name;
        static const char group_names_propname[] = "gpio-ranges-group-names";
        struct property *group_names;

        if (!np)
                return 0;

        group_names = of_find_property(np, group_names_propname, NULL);

        for (;; index++) {
                ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3,
                                index, &pinspec);
                if (ret)
                        break;

                pctldev = of_pinctrl_get(pinspec.np);
                of_node_put(pinspec.np);
                if (!pctldev)
                        return -EPROBE_DEFER;

                if (pinspec.args[2]) {
                        if (group_names) {
                                of_property_read_string_index(np,
                                                group_names_propname,
                                                index, &name);
                                if (strlen(name)) {
                                        pr_err("%pOF: Group name of numeric GPIO ranges must be the empty string.\n",
                                                np);
                                        break;
                                }
                        }
                        /* npins != 0: linear range */
                        ret = gpiochip_add_pin_range(chip,
                                        pinctrl_dev_get_devname(pctldev),
                                        pinspec.args[0],
                                        pinspec.args[1],
                                        pinspec.args[2]);
                        if (ret)
                                return ret;
                } else {
                        /* npins == 0: special range */
                        if (pinspec.args[1]) {
                                pr_err("%pOF: Illegal gpio-range format.\n",
                                        np);
                                break;
                        }

                        if (!group_names) {
                                pr_err("%pOF: GPIO group range requested but no %s property.\n",
                                        np, group_names_propname);
                                break;
                        }

                        ret = of_property_read_string_index(np,
                                                group_names_propname,
                                                index, &name);
                        if (ret)
                                break;

                        if (!strlen(name)) {
                                pr_err("%pOF: Group name of GPIO group range cannot be the empty string.\n",
                                np);
                                break;
                        }

                        ret = gpiochip_add_pingroup_range(chip, pctldev,
                                                pinspec.args[0], name);
                        if (ret)
                                return ret;
                }
        }

        return 0;
}

#else
static int of_gpiochip_add_pin_range(struct gpio_chip *chip) { return 0; }
#endif

“gpio-ranges” 속성을 사용하여 연계할 pin controller의 지정된 핀들을 gpio 컨트롤러에 등록한다.

  • 코드 라인 15에서 “gpio-ranges-group-names” 속성 값을 알아온다.
  • 코드 라인 17~21에서 “gpio-ranges” 속성이 배열로 지정될 수 있으므로 반복하며 읽는다.
  • 코드 라인 23~26에서 phandle 값으로 지정한 pin controller 디바이스를 알아온다.
  • 코드 라인 28~38에서 3개의 argument를 사용한 경우 “gpio-ranges-group-names” 속성 값이 사용된 경우 빈문자열이어야 한다.
  • 코드 라인 40~46에서 gpio chip 정보에 핀 range를 등록한다.
    • 첫 번째 인수부터 gpio 시작 번호(offset), pin 시작 번호(offset), gpio 개수이다.
  • 코드 라인 47~77에서 그 밖의 argument 개수를 사용한 경우 두 번째 인수는 항상 0이어야 한다.  “gpio-ranges-group-names” 속성이 반드시 사용되어야 하며 해당 그룹명을 읽어와서 그 그룹에 해당하는 pin들로 gpio chip 정보에 핀 range를 등록한다.

 

devprop_gpiochip_set_names()

drivers/gpio/gpiolib-devprop.c

/**
 * devprop_gpiochip_set_names - Set GPIO line names using device properties
 * @chip: GPIO chip whose lines should be named, if possible
 *
 * Looks for device property "gpio-line-names" and if it exists assigns
 * GPIO line names for the chip. The memory allocated for the assigned
 * names belong to the underlying firmware node and should not be released
 * by the caller.
 */
void devprop_gpiochip_set_names(struct gpio_chip *chip)
{
        struct gpio_device *gdev = chip->gpiodev;
        const char **names;
        int ret, i;

        if (!chip->parent) {
                dev_warn(&gdev->dev, "GPIO chip parent is NULL\n");
                return;
        }

        ret = device_property_read_string_array(chip->parent, "gpio-line-names",
                                                NULL, 0);
        if (ret < 0)
                return;

        if (ret != gdev->ngpio) {
                dev_warn(chip->parent,
                         "names %d do not match number of GPIOs %d\n", ret,
                         gdev->ngpio);
                return;
        }

        names = kcalloc(gdev->ngpio, sizeof(*names), GFP_KERNEL);
        if (!names)
                return;

        ret = device_property_read_string_array(chip->parent, "gpio-line-names",
                                                names, gdev->ngpio);
        if (ret < 0) {
                dev_warn(chip->parent, "failed to read GPIO line names\n");
                kfree(names);
                return;
        }

        for (i = 0; i < gdev->ngpio; i++)
                gdev->descs[i].name = names[i];

        kfree(names);
}

gpio controller 노드에서 gpio 이름이 주어지지 않은 경우 부모 gpio controller 노드의 “gpio-line-names” 속성 값들을 읽어서 gpio 디스크립터들에서 gpio명으로 사용한다.

  • gpio  controller 노드에서 “X-gpios ” 속성을 사용하지 않은 경우 “gpio-line-names” 속성에서 지정한 이름들을 사용한다.

 

  • 코드 라인 16~19에서 부모 gpio controller가 없는 경우 경고 메시지를 출력 후 함수를 빠져나간다.
  • 코드 라인 21~24에서 부모 gpio controller 노드에서 “gpio-line-names” 속성을 찾아 없으면 함수를 빠져나간다.
  • 코드 라인 26~31에서 gpio 컨트롤러에 등록된 gpio 수와 “gpio-line-names” 속성에서 읽어들인 개수가 다르면 경고 메시지를 출력 후 함수를 빠져나간다.
  • 코드 라인 33~35에서 “gpio-line-names” 속성에서 읽은 라인 명 개수 만큼 문자열 포인터 배열을 준비한다.
  • 코드 라인 37~46에서 “gpio-line-names” 속성에서 문자열을 읽어 names[] 배열에 대입하게 한다. 그 후 그 수 만큼 gpio 디바이스의 디스크립터들 이름으로 지정한다.

 

of_gpiochip_scan_gpios()

drivers/gpio/gpiolib-of.c

/**
 * of_gpiochip_scan_gpios - Scan gpio-controller for gpio definitions
 * @chip:       gpio chip to act on
 *
 * This is only used by of_gpiochip_add to request/set GPIO initial
 * configuration.
 * It returns error if it fails otherwise 0 on success.
 */
static int of_gpiochip_scan_gpios(struct gpio_chip *chip)
{
        struct gpio_desc *desc = NULL;
        struct device_node *np;
        const char *name;
        enum gpio_lookup_flags lflags;
        enum gpiod_flags dflags;
        unsigned int i;
        int ret;

        for_each_available_child_of_node(chip->of_node, np) {
                if (!of_property_read_bool(np, "gpio-hog"))
                        continue;

                for (i = 0;; i++) {
                        desc = of_parse_own_gpio(np, chip, i, &name, &lflags,
                                                 &dflags);
                        if (IS_ERR(desc))
                                break;

                        ret = gpiod_hog(desc, name, lflags, dflags);
                        if (ret < 0) {
                                of_node_put(np);
                                return ret;
                        }
                }
        }

        return 0;
}

gpio controller 노드의 서브 노드들에서 “gpio_hog” 속성이 발견되면 지정된 gpio 핀 상태를 즉시 설정한다.

  • 코드 라인 19~21에서 gpio controller 노드의 서브 노드들에서 “gpio_hog” 속성이 발견되지 않으면 skip 한다.
  • 코드 라인 23~27에서 gpio hog 노드를 파싱하여 이름을 name에 대입하고, 로지컬 플래그 lflags에 대입한다. 그리고 파싱한 디렉션 플래그를 dflags에 대입한다.
  • 코드 라인 29~30에서 gpio controller 디바이스를 통해 위에서 파싱한 정보를 HW에 설정한다.

 

of_parse_own_gpio()

drivers/gpio/gpiolib-of.c

/**
 * of_parse_own_gpio() - Get a GPIO hog descriptor, names and flags for GPIO API
 * @np:         device node to get GPIO from
 * @chip:       GPIO chip whose hog is parsed
 * @idx:        Index of the GPIO to parse
 * @name:       GPIO line name
 * @lflags:     gpio_lookup_flags - returned from of_find_gpio() or
 *              of_parse_own_gpio()
 * @dflags:     gpiod_flags - optional GPIO initialization flags
 *
 * Returns GPIO descriptor to use with Linux GPIO API, or one of the errno
 * value on the error condition.
 */
static struct gpio_desc *of_parse_own_gpio(struct device_node *np,
                                           struct gpio_chip *chip,
                                           unsigned int idx, const char **name,
                                           enum gpio_lookup_flags *lflags,
                                           enum gpiod_flags *dflags)
{
        struct device_node *chip_np;
        enum of_gpio_flags xlate_flags;
        struct of_phandle_args gpiospec;
        struct gpio_desc *desc;
        unsigned int i;
        u32 tmp;
        int ret;

        chip_np = chip->of_node;
        if (!chip_np)
                return ERR_PTR(-EINVAL);

        xlate_flags = 0;
        *lflags = 0;
        *dflags = 0;

        ret = of_property_read_u32(chip_np, "#gpio-cells", &tmp);
        if (ret)
                return ERR_PTR(ret);

        gpiospec.np = chip_np;
        gpiospec.args_count = tmp;

        for (i = 0; i < tmp; i++) {
                ret = of_property_read_u32_index(np, "gpios", idx * tmp + i,
                                                 &gpiospec.args[i]);
                if (ret)
                        return ERR_PTR(ret);
        }

        desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, &xlate_flags);
        if (IS_ERR(desc))
                return desc;

        if (xlate_flags & OF_GPIO_ACTIVE_LOW)
                *lflags |= GPIO_ACTIVE_LOW;

        if (of_property_read_bool(np, "input"))
                *dflags |= GPIOD_IN;
        else if (of_property_read_bool(np, "output-low"))
                *dflags |= GPIOD_OUT_LOW;
        else if (of_property_read_bool(np, "output-high"))
                *dflags |= GPIOD_OUT_HIGH;
        else {
                pr_warn("GPIO line %d (%s): no hogging state specified, bailing out\n",
                        desc_to_gpio(desc), np->name);
                return ERR_PTR(-EINVAL);
        }

        if (name && of_property_read_string(np, "line-name", name))
                *name = np->name;

        return desc;
}

gpio hog 노드를 파싱하여 출력 인자 3개에 결과를 대입하고 해당 디스크립터를 찾아온다. 자세한 것은 다음과 같다.

  • “line-name” 속성 값을 출력 인자 name에 대입
  • “gpios” 속성값을 파싱하여 디스크립터를 찾아 반환하고, 파싱한 설정 값에 OF_GPIO_ACTIVE_LOW(1) 플래그가 발견되면 로지컬 플래그에 해당하는 출력 인자 lflags에 GPIO_ACTIVE_LOW(1)를 대입
  • “input”, “output-low”, “output-high” 속성 중 하나에 해당하는 플래그 비트를 디렉션 플래그에 해당하는 출력인자 dflags에 대입

 

  • 코드 라인 28~30에서 gpio controller에 해당하는 노드가 없으면 에러로 함수를 빠져나간다.
  • 코드 라인 36~42에서 gpio controller 노드에서 “#gpio-cells” 속성 값을 알아와서 argument 정보를 설정한다.
  • 코드 라인 44~49에서 hog 노드에서 argument 수 만큼 “gpios” 속성 값을 읽어서 이 역시 argument 정보에 설정한다.
  • 코드 라인 51~53에서 hog 노드에서 “gpios”의 argument를 파싱하여 시작 gpio 번호와 파싱된 플래그 값을 xlate_flags에 대입한다.
  • 코드 라인 55~56에서 읽어온 xlate_flags에 ACTIVE_LOW(1) 플래그가 담겨 있으면 lflags에 설정한다.
  • 코드 라인 58~68에서 “input”, “output-low” 그리고 “output-high”와 같은 세가지 속성 중 하나를 찾아 dflags에 대입한다. 발견하지 못하면 경고 메시지를 출력하고 에러로 함수를 빠져나간다.
  • 코드 라인 70~73에서 “line-name” 속성을 찾아 출력 인자 name에 대입한 후 디스크립터를 반환한다.
    • 이 이름은 추후 gpio 디스크립터의 label로 지정된다.

 

gpiod_hog()

drivers/gpio/gpiolib-of.c

/**
 * gpiod_hog - Hog the specified GPIO desc given the provided flags
 * @desc:       gpio whose value will be assigned
 * @name:       gpio line name
 * @lflags:     gpio_lookup_flags - returned from of_find_gpio() or
 *              of_get_gpio_hog()
 * @dflags:     gpiod_flags - optional GPIO initialization flags
 */
int gpiod_hog(struct gpio_desc *desc, const char *name,
              unsigned long lflags, enum gpiod_flags dflags)
{
        struct gpio_chip *chip;
        struct gpio_desc *local_desc;
        int hwnum;
        int status;

        chip = gpiod_to_chip(desc);
        hwnum = gpio_chip_hwgpio(desc);

        local_desc = gpiochip_request_own_desc(chip, hwnum, name);
        if (IS_ERR(local_desc)) {
                status = PTR_ERR(local_desc);
                pr_err("requesting hog GPIO %s (chip %s, offset %d) failed, %d\n",
                       name, chip->label, hwnum, status);
                return status;
        }

        status = gpiod_configure_flags(desc, name, lflags, dflags);
        if (status < 0) {
                pr_err("setup of hog GPIO %s (chip %s, offset %d) failed, %d\n",
                       name, chip->label, hwnum, status);
                gpiochip_free_own_desc(desc);
                return status;
        }

        /* Mark GPIO as hogged so it can be identified and removed later */
        set_bit(FLAG_IS_HOGGED, &desc->flags);

        pr_info("GPIO line %d (%s) hogged as %s%s\n",
                desc_to_gpio(desc), name,
                (dflags&GPIOD_FLAGS_BIT_DIR_OUT) ? "output" : "input",
                (dflags&GPIOD_FLAGS_BIT_DIR_OUT) ?
                  (dflags&GPIOD_FLAGS_BIT_DIR_VAL) ? "/high" : "/low":"");

        return 0;
}

hog 노드를 파싱하여 얻은 인자들을 gpio conrtroller를 통해 H/W 설정한다.

  • 코드 라인 17~26에서 gpio 디스크립터로 gpio controller와 hwgpio 번호를 알아온다. 그 후 이에 해당하는 로컬 gpio 디스크립터를 알아온다.
  • 코드 라인 28~34에서 룩업 플래그(lflags)에 설정된 플래그들을 gpio 디스크립터에도 추가 반영한다. 또한 디렉션 플래그dflags)에 따라 gpio 입/출력 모드를 HW에 설정한다.
  • 코드 라인37~45에서 gpio 디스크립터에 hog 처리 플래그를 추가하고 hog 정보를 출력한 후 성공(0)을 반환한다.

 

디바이스 트리를 사용한 gpio 디스크립터 검색

of_find_gpio()

drivers/gpio/gpiolib-of.c

struct gpio_desc *of_find_gpio(struct device *dev, const char *con_id,
                   unsigned int idx,
                   enum gpio_lookup_flags *flags)
{
    char prop_name[32]; /* 32 is max size of property name */
    enum of_gpio_flags of_flags;
    struct gpio_desc *desc;
    unsigned int i;

    for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
        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) != -ENOENT))
            break;
    }

    if (IS_ERR(desc))
        return desc;

    if (of_flags & OF_GPIO_ACTIVE_LOW)
        *flags |= GPIO_ACTIVE_LOW;

    if (of_flags & OF_GPIO_SINGLE_ENDED) {
        if (of_flags & OF_GPIO_OPEN_DRAIN)
            *flags |= GPIO_OPEN_DRAIN;
        else
            *flags |= GPIO_OPEN_SOURCE;
    }

    if (of_flags & OF_GPIO_SLEEP_MAY_LOOSE_VALUE)
        *flags |= GPIO_SLEEP_MAY_LOOSE_VALUE;

    return desc;
}

지정한 디바이스 트리 노드에서 인자로 전달 받은 con_id 문자열과 관련된 속성명을 파싱하여 gpio 디스크립터를 찾아온다.

  • 코드 라인 10~25에서 인자로 전달받은 디바이스 노드에서 “<con_id>-gpios” 및 “<con_id>-gpio” 속성명으로 검색한다.
    • <con_id>가 없는 경우 특정 Consumer 디바이스용이 아닌 글로벌 용도 이므로 “gpios” 및 “gpio” 속성명으로 검색한다.
  • 코드 라인 27~28에서 디바이스 트리 노드에서 파싱한 플래그 값이 low active 방식을 사용하는 경우 출력 인자 flags에도 GPIO_ACTIVE_LOW 플래그를 추가한다.
  • 코드 라인 30~35에서 디바이스 트리 노드에서 MOSFET 구성이 push-pull이 아니라 single ended 방식인 경우 파싱한 플래그 값이 open drain 사용 유무에 따라 출력 인자 flags에도 open drain 또는 open source 플래그를 추가한다.
  • 코드 라인 37~38에서 디바이스 트리 노드에서 파싱한 플래그 값이 sleep시 value를 잃어버린다고 설정되어 있는 경우 출력 인자 flags에도 플래그를 추가한다.

 

/**
 * of_get_named_gpiod_flags() - Get a GPIO descriptor and flags for GPIO API
 * @np:     device node to get GPIO from
 * @propname:   property name containing gpio specifier(s)
 * @index:  index of the GPIO
 * @flags:  a flags pointer to fill in
 *
 * Returns GPIO descriptor to use with Linux GPIO API, or one of the errno
 * value on the error condition. If @flags is not NULL the function also fills
 * in flags for the GPIO.
 */
struct gpio_desc *of_get_named_gpiod_flags(struct device_node *np,
             const char *propname, int index, enum of_gpio_flags *flags)
{
    struct of_phandle_args gpiospec;
    struct gpio_chip *chip;
    struct gpio_desc *desc;
    int ret;

    ret = of_parse_phandle_with_args(np, propname, "#gpio-cells", index,
                     &gpiospec);
    if (ret) {
        pr_debug("%s: can't parse '%s' property of node '%pOF[%d]'\n",
            __func__, propname, np, index);
        return ERR_PTR(ret);
    }

    chip = of_find_gpiochip_by_xlate(&gpiospec);
    if (!chip) {
        desc = ERR_PTR(-EPROBE_DEFER);
        goto out;
    }

    desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags);
    if (IS_ERR(desc))
        goto out;

    pr_debug("%s: parsed '%s' property of node '%pOF[%d]' - status (%d)\n",
         __func__, propname, np, index,
         PTR_ERR_OR_ZERO(desc));

out:
    of_node_put(gpiospec.np);

    return desc;
}

요청한 디바이스 노드(gpio controller 노드)의 “#gpio-cells” 속성 값의 argument 수 만큼 벤더가 제공하는 변환 함수(*of_xlate)를 사용하여 gpio핀 번호와 플래그를 변환한 후 해당 gpiochip에서 디스크립터를 찾아 반환한다. 출력 인자 flags 값에도 반환시킨다.

  • two cell을 사용하는 simple 변환 사용 예) “abc-gpios” = <&gpio 12 0>
    • 12번 gpio에서 0번 설정 값을 플래그에 대입한다.
    • 참고로 4 bit 플래그 값이 모두 0이므로 -> high-active, push-pull, sleep-maintain-value 특성을 가진다.

 

  • 코드 라인 20~26에서 인자로 요청한 프로퍼티명(X-gpios, X-gpio, gpios, gpio)에서 index 배열의 argument를 읽어서 출력 인자 gpiospec에 대입한다.
    • 예) “abc-gpios” = <&gpio 12 0>, <&gpio 13, 0>에서 propname=”abc-gpios”, index=1로 검색 요청을 한 경우
      • -> index=0을 건너띄고 <&gpio 13, 0> 값을 읽어 gpiospec argument에 대입한다.
  • 코드 라인 28~32에서 첫 번째 argument는 gpio controller 노드를 가리키는 phandle 값이다. 이 phandle로 노드를 검색하여 해당하는  gpio_chip 구조체를 찾아온다.
  • 코드 라인 34~36에서 argument 값을 변환한 값에 해당하는 gpio 디스크립터 및 플래그 값을 알아온다.

 

of_xlate_and_get_gpiod_flags()

drivers/gpio/gpiolib-of.c

static struct gpio_desc *of_xlate_and_get_gpiod_flags(struct gpio_chip *chip,
                    struct of_phandle_args *gpiospec,
                    enum of_gpio_flags *flags)
{
    int ret;

    if (chip->of_gpio_n_cells != gpiospec->args_count)
        return ERR_PTR(-EINVAL);

    ret = chip->of_xlate(chip, gpiospec, flags);
    if (ret < 0)
        return ERR_PTR(ret);

    return gpiochip_get_desc(chip, ret);
}

인자로 전달받은 gpio argument들을 사용하여 GPIO 칩 벤더가 제공하는 (*of_xlate) 변환 함수를 통해 알아온 gpio hw pin 번호와 플래그 값을 사용하여 gpio 디스크립터를 찾아 반환한다.

  • 대부분의gpio controller에서 (*of_xlate) 후크 구현 시 of_gpio_simple_xlate() 함수를 사용한다. 이 함수는 phandle 뒤에 위치하는 두 개의 argument 값을 변환 없이 그대로 gpio 번호와 gpio 플래그 값으로 반환한다.
    • 예) <gpio hw pin: 0~> <flags>
  • 특정 gpio controller는 세 개의 argument를 사용하는 경우도 있으므로 반드시 확인하여 구별해야 한다. 보통 이러한 경우 추가된 인자는 뱅GPIO 뱅크를 가리킨다.
    • 예) <gpio bank: 0~> <gpio hw pin: 0~31> <flags>
  • 코드 라인 7~8에서 gpio controller에 지정된 cell 수와 인자로 전달받은 augument 인자 수가 다른 경우 에러로 함수를 빠져나간다.
  • 코드 라인 10~12에서 gpio controller의 (*of_xlate) 후크에 등록된 함수를 수행한다.
    • 벤더가 후크 함수를 제공하지 않으면 디폴트로 of_gpio_simple_xlate() 함수를 사용한다.
    • phandle 값 뒤에 3개의 argument들을 요구하는 벤더를 제외하면, 2개의 argument들을 요구하는 대부분의 벤더는 별도의 후크 함수를 제공하지 않는다.
  • 코드 라인 14에서 해당 결과 인덱스에 해당하는 gpio 디스크립터를 반환한다.

 

다음 그림은 gpios 속성의 phandle 뒤에 사용된 argument 수에 따라 gpio 칩 벤더가 제공하는 (*xlate) 함수의 사용사례를 보여준다.

 

 

of_gpio_simple_xlate()

drivers/gpio/gpiolib-of.c

/**
 * of_gpio_simple_xlate - translate gpiospec to the GPIO number and flags
 * @gc:         pointer to the gpio_chip structure
 * @gpiospec:   GPIO specifier as found in the device tree
 * @flags:      a flags pointer to fill in
 *
 * This is simple translation function, suitable for the most 1:1 mapped
 * GPIO chips. This function performs only one sanity check: whether GPIO
 * is less than ngpios (that is specified in the gpio_chip).
 */
int of_gpio_simple_xlate(struct gpio_chip *gc,
                         const struct of_phandle_args *gpiospec, u32 *flags)
{
        /*
         * We're discouraging gpio_cells < 2, since that way you'll have to
         * write your own xlate function (that will have to retrieve the GPIO
         * number and the flags from a single gpio cell -- this is possible,
         * but not recommended).
         */
        if (gc->of_gpio_n_cells < 2) {
                WARN_ON(1);
                return -EINVAL;
        }

        if (WARN_ON(gpiospec->args_count < gc->of_gpio_n_cells))
                return -EINVAL;

        if (gpiospec->args[0] >= gc->ngpio)
                return -EINVAL;

        if (flags)
                *flags = gpiospec->args[1];

        return gpiospec->args[0];
}
EXPORT_SYMBOL(of_gpio_simple_xlate);

2개의 셀을 사용하는 goio simple 변환은 gpio 시작 번호(offset)에 해당하는 첫 번째 argument를 그대로 반환한다. 그리고 gpio 설정 값에 해당하는 두 번째 인수 역시 그대로 변환 없이 출력 인자 flags에 대입한다.

  • 코드 라인 20~23에서 셀수가 2개보다 작으면 simple 셀 변환을 할 수 없어서 경고 메시지를 출력하고 에러를 반환한다.
  • 코드 라인 25~26에서 필요한 인자 개수가 모자라면 역시 에러를 반환한다.
  • 코드 라인 28~29에서 gpio 시작 번호(offset)에 해당하는 첫 번째 argument가 gpio 컨트롤러에 등록된 gpio 수를 초과하면 처리할 수 없으므로 에러를 반환한다.
  • 코드 라인 31~32에서 gpio 설정 값에 해당하는 두 번째 인수는 그대로 변환 없이 출력 인자 flags에 대입한다.
  • 코드 라인 34에서 gpio 시작 번호(offset)에 해당하는 첫 번째 argument를 변환 없이 그대로 반환한다.

 

참고

 

 

2 thoughts to “GPIO Subsystem -3- (Device Tree)”

  1. 안녕하세요 글 잘 봤어요.

    근데 궁금한게 있어서 질문 남깁니다.

    of_xlate의 역할이 뭔가요?????
    of_phandle_args변수는 무슨 역할을 하기 위해 있는건가요? ㅠㅠ

    리눅스 모듈 분석하고 있는데 정말 모르겠네요.
    메일로 답변주시면 정말 감사하겠습니다!

  2. 안녕하세요?

    질의하신 부분들은 디바이스 트리에서 곧바로 이해하기 힘든 어려운 부분에 해당합니다.

    먼저 (*of_xlate) 후크는 디바이스 트리의 노드 내에서 읽어 들이는 특정 속성 정보가 드라이버마다 표현이 다른 dynamic 한 구성을 하는 경우에 대해서 사용됩니다.

    gpio 드라이버에서의 사용 예를 들어보겠습니다. gpio 드라이버를 구동하자마자 초깃값으로 gpio 핀에 대해 특정 설정을 하고 싶을 때 디바이스 트리에서 gpio-hog; 속성을 통해 요청할 수 있습니다. 그런데 요청 시 gpio 핀을 의미하는 번호가 각 드라이버 개발자마다 다음과 같이 다른 사용법을 갖는 경우가 있습니다.

    – 2셀(4바이트 2개) 표현을 통해 gpio 핀 번호와 플래그 지정
    - gpios = <GPIO_4 GPIO_ACTIVE_HIGH>;
    – 3셀(4바이트 3개) 표현을 통해 뱅크 번호 x 32 + gpio 핀 번호와 플래그 지정
    - gpios = <BANK_1 GPIO_4 GPIO_ACTIVE_HIGH>;

    실제 각각의 gpio 드라이버에서 위와 같이 gpio 핀을 표현하는 셀의 개수와 gpio에 설정할 플래그를 표현하는 개수를 다르게 할 수 있습니다. 이렇게 여러 개의 셀 표현에 대한 변환을 수행하여 최종 목표인 gpio 디스크립터와 설정할 플래그 값을 알아 오는 것이 목적입니다. 대부분 gpio 드라이버는 argument를 표현할 때 2셀 방법을 사용하는데, 이를 위해 커널은 (*of_xlate) 후크에 사용할 of_gpio_simple_xlate() 함수를 제공합니다. 위의 2 셀과 다른 셀 표현을 사용하실 gpio 드라이버 개발자들은 별도의 자신의 변환 함수를 작성하여 (*of_xlate) 후크에 대입하여 사용하면 됩니다.

    두 번째 of_phandle_args에 대해 설명해 드립니다.

    먼저 알고 있어야 할 항목은 phandle입니다. phandle은 디바이스 트리를 컴파일하면 각 노드에 대해 phandle 속성명이 자동으로 생성되고 이에 대한 값은 일련번호 1부터 노드를 컴파일하는 순서대로 생성됩니다. 만일 특정 노드를 가리킬 일이 있으면 이 일련번호를 직접 사용하는 것이 아니고 노드명에 & 기호를 붙여서 사용하면 해당 노드를 가리키는 phandle 값으로 바뀝니다.

    예를 들어 abc-gpios = < &gpio1 13 0>; 라고 할 때 gpio 노드가 컴파일 시 5번째 순서에 컴파일되어 gpio 노드내에 자동으로 phandle = <5>;가 생성됩니다. 그리고 이 노드를 phandle을 사용하여 가리킨 속성 값은 abc-gpios = <5 13 0>;이 됩니다. 즉 phandle은 노드를 가리키는 유니크한 정수입니다.

    phandle 값 뒤에 작성할 argument들은 phandle 노드에 따라 argument 개수가 달라집니다. 즉 phandle이 가리키는 노드가 원하는 개수만큼의 argument를 대입해서 작성해야 한다는 뜻입니다.

    예를 들어 제 시스템에 gpio 제조사가 다른 칩이 2개 사용되어 gpio1과 gpio2가 사용되고 있다고 가정합니다. 첫 번째 gpio1 드라이버는 2셀 방법을 사용하고, 두 번째 gpio2 드라이버가 3셀 방법을 사용하는 경우 첫 번째 gpio1과 연결된 custom 장치는 abc-gpios = < &gpio1 13 0>;과같이 argument를 2개 사용하여야 하고, 두 번째 gpio2에 연결된 custom 장치는 def-gpios = < &gpio2 1 14 0>;와같이 argument를 3개 사용해야 합니다.

    이렇게 가변 길이의 argument를 가지고 해당 gpio 드라이버의 (*of_xlate) 후크에 지정된 변환 함수를 사용할 때 of_phandle_args 구조체를 사용하여 운반합니다.

    struct of_phandle_args {
    struct device_node *np;
    int args_count;
    uint32_t args[MAX_PHANDLE_ARGS];
    };

    위의 구조체에서 np는 phandle을 통해 가리킨 디바이스 노드이며 두 번째, args_count는 argument 개수입니다. 또한 args에는 변환에 사용될 argument 값들이 들어가게 됩니다.

    이렇게 설명이 복잡하기만 한데, 부디(?) 이해가 되길 바랍니다.

댓글 남기기