Pin Control Subsystem -2-

Pin Control 관련 디바이스 트리

최근에는 디바이스 드라이버 내부 코드에서 매핑을 미리 선언하여 준비해두지 않고, 디바이스 트리를 통해 pinmux/pinconf 매핑을 간단히 등록할 수 있게 하였다. pinmux/pinconf 매핑을 기술하는 디바이스 트리 속성들은 각 벤더들마다 작성 방법이 약간씩 상이하지만 대체적으로 generic한 방법을 따르고 있다. 따라서 먼저 generic한 방법으로 pin control 관련 노드들을 작성하는 방법을 알아보기로 한다.

 

먼저 각 노드들을 설명하기 앞서 다음의 항목에 대해서는 그 의미를 알아야한다.

  • pinmux 매핑
    • 핀 리스트 또는 그룹 리스트에 속한 핀들 —–> Function(모드)을 매핑한다.
  • pinconf 매핑
    • 핀 리스트 또는 그릅에 속한 핀들 ——–> Configuration(bias-pull-up, …)을 매핑한다.

 

다음 그림에서 pinmux 매핑과 pinconf 매핑 두 가지 상황을 매핑하였다. 좌측에는 핀 또는 그룹명이고 우측은 pinmux 매핑에 사용하는 Function 명과 pinconf 매핑에 사용하는 Configuration 항목이다.

 

Generic Pin Controller 노드

pin controll 노드는 configuration(pinmux/pinconf) 노드들을 포함할 수 있다. 또한 추가적으로 pin controller 디바이스 드라이버가 초기화 시 Consumer 입장이 되어 pin 들의 초기화를 수행할 수 있다.

  • configuration 노드들은 다음과 같고, 아래에서 설명하는 멀티 스테이트를 사용하는 경우 configuration 노드들은 state 노드들에 포함될 수 있다.
    • pinmux 노드
    • pinconf 노드
    • Mixed pinmux/pinconf 노드
  • 스테이트 별 관리
    • 싱글 스테이트
      • configuration 노드들은 별도의 스테이트 관리를 하지 않고, 클라이언트 디바이스가 한 가지 스테이트만을 사용한다.
    • 멀티 스테이트
      • 클라이언트 디바이스가 두 가지 이상의 스테이트를 런타임에서 요청할 수 있도록 할 수 있다.
  • Consumer 입장
    • 이 노드에서 pinctrl-names 및 pinctrl-0 속성의 사용을 하는 경우이다.

 

다음 그림은 싱글 또는 멀티 스테이트에 대한 사례이다.

 

Generic pinmux 노드

아래와 같이 pinmux 노드는 여러 형태로 구성될 수 있다. pinmux 노드 밖에 state를 구분하기 위한 노드로 감쌀 수도 있다.

  • function
    • 선택할 펑션명이다.
  • groups | pins
    • 그룹 리스트 또는 핀 리스트 둘 중 하나가 지정된다.
state_0_node_a {
        uart0 {
                function = "uart0";
                groups = "u0rxtx", "u0rtscts";
        };
};
state_1_node_a {
        spi0 {
                function = "spi0";
                groups = "spi0pins";
        };
};
state_2_node_a { 
        function = "i2c0";
        pins = "mfio29", "mfio30";
};

 

생성되는 매핑 수
  • pinmux 매핑은 무조건 매핑 대상 수 만큼 만들어진다. pinmux에서 대상은 groups가 될수도 있고 pins가 될 수도 있다.
  • 따라서 위의 코드들의 매핑 수는 순서대로 2개, 1개, 2개이다.

 

Generic pinconf 노드

아래와 같이 pinconf 노드는 여러 형태로 구성될 수 있다. pinconf 노드 밖에 state를 구분하기 위한 노드로 감쌀 수도 있다.

  • pins | group | pinmux
    • 핀 리스트, 하나의 그룹, 핀먹스 셋 중 하나가 지정된다.
    • 핀 먹스가 사용되는 경우 정수 핀 id 들이 지정된다.
  • 핀에 설정할 항목들
    • bias-pull-down, bias-pull-up, input-enable, …
state_0_node_a {
        cts_rxd {
                pins = "GPIO0_AJ5", "GPIO2_AH4"; /* CTS+RXD */
                bias-pull-up;
        };
};
state_1_node_a {
        rts_txd {
                pins = "GPIO1_AJ3", "GPIO3_AH3"; /* RTS+TXD */
                output-high;
        };
};
state_2_node_a {
        foo {
                group = "foo-group";
                bias-pull-up;
        };
};
state_3_node_a {
        mux {
                pinmux = <GPIOx_PINm_MUXn>, <GPIOx_PINj_MUXk)>;
                input-enable;
        };
};

 

생성되는 매핑 수
  • pinconf 매핑은 설정 항목의 수와는 무관하고, pinmux와 같이 무조건 매핑 대상 수 만큼 만들어진다.  pinmux에서 대상은 group, pins 또는 pinmux 로 지정된 대상 수 이다.
  • 따라서 위의 코드들의 매핑 수는 순서대로 2개, 2개, 1개, 2개이다.

 

Mixed pinmux/pinconf 노드

아래와 같이 핀들에 대해 function과 configuraton 항목이 동시에 사용되었음을 알 수 있다. pin controller H/W가 mux를 사용하면서 pin 설정을 동시에 해야 하는 경우가 있다. 이렇게 하나의 노드에서 pinmux와 pinconf 매핑이 동시에 표현되는 경우 커널 내부에서는 하나의 매핑 맵으로 처리하지 않고 pinmux 및 pinconf 매핑 맵을 각각 만들어 관리한다.

arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi

.           mmc0_pins: mmc0-pins {
                pins = "PF0", "PF1", "PF2", "PF3",
                       "PF4", "PF5";
                function = "mmc0";
                drive-strength = <30>;
                bias-pull-up;
            };

 

생성되는 매핑 수

위의 디바이스 트리 노드 예에서 만들어지는 매핑 수를 알아보자.

  • pins 속성이든 groups 속성이든 해당 속성안에 있는 문자열 수를 매핑에 적용할 대상으로 판단한다.
    •  pins = “PF0”, “PF1”, “PF2”, “PF3”, “PF4”, “PF5″인 경우 대상의 수는 6개이다.
    • groups = “gpio_12_13_grp gpio_14_15_grp”인 경우 핀이 4개일지라도 대상의 수는 2개이다.
  • 위의 코드에서 pinmux 매핑은 무조건 매핑 대상 수 만큼 만들어진다. 따라서 6개이다.
  • 위의 코드에서 pinconf 매핑은 drive-strength 및 bias-pull-up 등의 설정 항목 수와 관계 없이 하나의 매핑에 포함된다. 따라서 6개이다.
  • 총 매핑 수 = 6개 매핑 대상 * (pinmux 1개 + pinconf 1개) = 12개

 

Consumer로의 사용

pin controller 디바이스 드라이버가 내부에서 Consumer 입장이 되어 pin 설정을 시도할 수도 있다. 이러한 경우 Client 디바이스에서 처럼 pinctrl-names 및 pinctrl-0 속성을 사용할 수 있다. 만일 pin controller가 초기화될 때 pin들의 초기화 설정이 필요 없으면 즉, Consumer 입장이 아니면 아래 두 속성을 지정하지 않아도 된다.

  • pinctrl-names 속성
    • pin 컨트롤러가 제공하는 state 명을 지정한다. state 구분이 필요 없는 경우 보통 “default” 스테이트 명을 사용한다.
    • 만일 N개의 스테이트명을 지정하면 아래 pinctrl-0 부터 pinctrl-N 까지의 속성을 지정해야 한다.
  • pinctrl-0 속성
    • 이 곳에는 phandle 리스트가 입력된다. 각각의 phandle은 pinmux/pinconf 노드를 가리킨다.
	pinctrl: pinctrl {
                ...
		pinctrl-names = "default";
		pinctrl-0 = <&nand_sel &uart3_rx &sdio0_d4>;
                ...
        }

위의 코드에서 pin controller 노드가 pinctrl-names에 “default”하나만 사용되었으므로 싱글 스테이트로만 사용한다. 이러한 경우 pinctrl-0 속성만을 사용한다.

 

Generic Client 노드

  • pinctrl-names 속성
    • 싱글 스테이트로 사용하는 경우 하나의 스테이트 이름만을 사용한다. 이러한 경우 보통 “default”를 사용한다.
    • 만일 N개의 스테이트명을 지정하면 아래 pinctrl-0 부터 pinctrl-N 까지의 속성을 지정해야 한다.
    • 이 속성은 생략될 수 있으며 그러한 경우 “pinctrl-” 속성 다음에 있는 숫자가 스테이트명이된다.
      • 아래 예에서 두 번째 디바이스의 경우 pinctrl-names = “0”, “1”;으로 지정한 것과 같다.
  • pinctrl-0 ~ pinctrl-N 속성
    • 이 곳에는 phandle 리스트가 입력된다. phandle 각각은 반드시 pin 컨트롤러 노드에 포함된 pinmux/pinconf 노드를 가리켜야 한다.
    • 각 스테이트는 pinctrl-0번부터 시작하여 스테이트 수 만큼 pinctrl-N 까지 순서대로 등록할 수 있다.
    • 이 스테이트 속성값이 블랭크인 경우 state 명에 빈 dummy 매핑만 등록된다.
.       /* For a client device requiring named states */
        device {
                pinctrl-names = "active", "idle";
                pinctrl-0 = <&state_0_node_a>;
                pinctrl-1 = <&state_1_node_a &state_1_node_b>;
        };

        /* For the same device if using state IDs */
        device {
                pinctrl-0 = <&state_0_node_a>;
                pinctrl-1 = <&state_1_node_a &state_1_node_b>;
        };

        /*
         * For an IP block whose binding supports pin configuration,
         * but in use on an SoC that doesn't have any pin control hardware
         */
        device {
                pinctrl-names = "active", "idle";
                pinctrl-0 = <>;
                pinctrl-1 = <>;
        };

 

Broadcom ns2 디바이스 트리 사용 예

ns2 pin controller H/W는 pinmux를 사용할 수 있는 핀들이 있고, pinconf 를 설정할 수 있는 핀들이 분류되어 있다. 따라서 ns2 pin controller 드라이버는 해당 핀들을 그룹으로 묶어서 매치 시킬 수 있는 항목들이 제한되어 있다. 다음 그림을 보고 좌측에 있는 그룹에 소속한 핀들이 우측 Function 또는 Configuration 항목과 매치 가능한 것을 알 수 있다.

 

아래 그림에서 사용한 속성과 노드들에 대해 간단히 설명을 하였다.

  • pinctrl-names 속성
    • “default” 스테이트 명 하나만을 사용하였다.
  • pinctrl-0 속성
    • “default” 스테이트 하나만을 사용하였으므로 state id는 0번만을 사용한다. 이 스테이트에는 3개의 pinmux 노드와 1개의 pinconf 노드를 가리키는 phandle을 사용하였다.
  • nand_sel pinmux 노드
    • “nand” 펑션명으로 “nand_grp” 그룹명을 매치시키는 pinmux 매핑이다.
    • 추후 이 pinmux 매핑이 선택되면 “nand_grp”에 속한 많은 핀들이 “nand” 펑션명으로 설정된다.
  • uart0_sel pinmux 노드
    • “uart0” 펑션명으로 3개의 그룹을 매치시키는 pinmux 매핑이다.
    • 추후 이 pinmux 매핑이 선택되면 “uart0_rts_cts_grp” 그룹명에 속한 2개의 핀, “uart0_in_out_grp” 그룹명에 속한 2개의 핀과 “uart0_modem_grp” 그룹명에 속한 4개의 핀들이 “uart0” 펑션으로 설정된다.
  • gpio_sel pinmux 노드
    • “gpio” 펑션명으로 “gpio_0_1_grp” 그룹명을 매치시키는 pinmux 매핑이다.
    • 추후 이 pinmux 매핑이 선택되면 “gpio_0_1_grp” 그룹명에 속한 2개의 핀이 “gpio” 펑션명으로 설정된다.
  • uart3_rx pinconf 노드
    • “uart3_sin” 핀명에 속한 핀을 bias-pull-up 매치시키는 pinconf 매핑이다.
    • 추후 이 pinconf 매핑이 선택되면 “uart3_sin” 핀의 bias를 pull up 시킨다.
  • uart0 클라이언트 노드
    • pinctrl-0 속성
      • 0번 state의 pinmux인 uart0_sel을 통해 3개의 그룹에 속한 핀들을 uart0 펑션으로 변경한다.
  • uart3 클라이언트 노드
    • pinctrl-0 속성
      • 0번 state의 pinconf인 uart3_rx을 통해 uart3_sin 그룹에 속한 핀의 bias를 pull-up으로 설정한다.

 

디바이스 트리내에서 핀 컨트롤러 노드와 서브 노드에 정의한 4개의 pinmux/pinconf 노드 및 2 개의 클라이언트 노드를 살펴본다.

arch/arm64/boot/dts/broadcom/northstar2/ns2-svk.dtsi

 

Pinctrl 생성 및 디바이스 트리로부터 읽어온 매핑 추가

pin controll을 사용하기 위해(Consumer의 입장) pinctrl 구조체를 생성하고 그 밑으로 디바이스 트리 매핑 맵들 및 사용하려는 스테이트들에 해당하는 각 설정들이 관리된다.

  • pinctrl 구조체 아래에 pinctrl_dt_map과 pinctrl_state 두개로 나뉘어 관리하는 모습을 확인하자.
  • 아래 두 개의 그림 사례와 같이 single state로만 관리될 때의 pinctrl_state 구조체의 차이를 확인하자.

 

multi state로 관리되는 pinctrl에는 pinctrl_state가 디바이스에서 사용하는 스테이트 수 만큼 복수개가 등록됨을 알 수 있다.

 

다음 그림은 여러 개의 디바이스 노드로부터 pinctrl을 생성하는 모습을 보여준다. 기본적으로 pin controller 노드도 consumer 입장에서 호출되었고, 나머지 pin control이 필요한 두 개의 클라이언트 디바이스 노드에서 각각 호출되어 사용된 사례이다.

 

create_pinctrl()

pin control을 사용해야 하는 디바이스 드라이버를 위해 전용 pinctrl을 생성한다.  생성된 pinctrl 구조체 는 크게 다음 두 가지를 관리한다.

  • 디바이스 트리 매핑 맵
    • 디바이스가 요청하는 모든 스테이트에서 요구되는 pinmux/pinconf 노드들을 찾아 파싱한 후 pincmux/pinconf 매핑 맵으로 만들어 등록된다.
  • 스테이트별 설정
    • 디바이스가 요청하는 각각의 스테이트별로 디바이스  트리 매핑 맵으로에서 pinmux/pinconf 설정들이 등록된다.

drivers/pinctrl/core.c – 1/2

static struct pinctrl *create_pinctrl(struct device *dev,
				      struct pinctrl_dev *pctldev)
{
	struct pinctrl *p;
	const char *devname;
	struct pinctrl_maps *maps_node;
	int i;
	const struct pinctrl_map *map;
	int ret;

	/*
	 * create the state cookie holder struct pinctrl for each
	 * mapping, this is what consumers will get when requesting
	 * a pin control handle with pinctrl_get()
	 */
	p = kzalloc(sizeof(*p), GFP_KERNEL);
	if (!p)
		return ERR_PTR(-ENOMEM);
	p->dev = dev;
	INIT_LIST_HEAD(&p->states);
	INIT_LIST_HEAD(&p->dt_maps);

	ret = pinctrl_dt_to_map(p, pctldev);
	if (ret < 0) {
		kfree(p);
		return ERR_PTR(ret);
	}

	devname = dev_name(dev);
  • 코드 라인 16~19에서 첫 번째 인자로 요청한 디바이스를 위해 pinctrl 구조체를 생성하고 pinctrl에서 디바이스를 가리키게 한다.
  • 코드 라인 20~21에서 pinctrl_state 구조체들을 담을 수 있는 리스트와 pinctrl_dt_map을 담을 수 있는 리스트를 초기화한다.
  • 코드 라인 23~27에서 요청 디바이스의 디바이스 트리 노드가 발견된 경우에 한해 pinmux/pinconf 매핑들을 파싱하여 매핑 맵들로 읽어온다.
    • 읽어온 매핑 맵들은 pinctrl_maps와 pinctrl_dt_map 두 구조체에서 관리된다.
  • 코드 라인 29에서 첫 번째 인자로 요청해온 디바이스 이름을 알아온다.
    • 주의: 요청자는 보통 클라이언트 디바이스이지만 핀 컨트롤러 디바이스일 수도 있다.

 

drivers/pinctrl/core.c – 2/2

	mutex_lock(&pinctrl_maps_mutex);
	/* Iterate over the pin control maps to locate the right ones */
	for_each_maps(maps_node, i, map) {
		/* Map must be for this device */
		if (strcmp(map->dev_name, devname))
			continue;
		/*
		 * If pctldev is not null, we are claiming hog for it,
		 * that means, setting that is served by pctldev by itself.
		 *
		 * Thus we must skip map that is for this device but is served
		 * by other device.
		 */
		if (pctldev &&
		    strcmp(dev_name(pctldev->dev), map->ctrl_dev_name))
			continue;

		ret = add_setting(p, pctldev, map);
		/*
		 * At this point the adding of a setting may:
		 *
		 * - Defer, if the pinctrl device is not yet available
		 * - Fail, if the pinctrl device is not yet available,
		 *   AND the setting is a hog. We cannot defer that, since
		 *   the hog will kick in immediately after the device
		 *   is registered.
		 *
		 * If the error returned was not -EPROBE_DEFER then we
		 * accumulate the errors to see if we end up with
		 * an -EPROBE_DEFER later, as that is the worst case.
		 */
		if (ret == -EPROBE_DEFER) {
			pinctrl_free(p, false);
			mutex_unlock(&pinctrl_maps_mutex);
			return ERR_PTR(ret);
		}
	}
	mutex_unlock(&pinctrl_maps_mutex);

	if (ret < 0) {
		/* If some other error than deferral occurred, return here */
		pinctrl_free(p, false);
		return ERR_PTR(ret);
	}

	kref_init(&p->users);

	/* Add the pinctrl handle to the global list */
	mutex_lock(&pinctrl_list_mutex);
	list_add_tail(&p->node, &pinctrl_list);
	mutex_unlock(&pinctrl_list_mutex);

	return p;
}
  • 코드 라인 3에서 전역 pinctrl_maps 리스트에 등록된 pinctrl_maps 구조체들과 그 멤버 maps가 가리키는 pinctrl_map 구조체 배열을 이중 루프 순회하며 pinctrl_map 구조체를 map 변수에 알아온다.
  • 코드 라인 5~6에서 순회 중인 map의 dev_name이 요청한 디바이스와 다른 경우 skip 한다.
  • 코드 라인 14~16에서 순회 중인 map의 ctrl_dev_name이 pin controller 디바이스 명과 다른 경우도 skip 한다.
  • 코드 라인 18~36에서 매핑 설정을 추가한다. 만일 pin controller 디바이스가 아직 준비되지 않은 경우 pinctrl 이하 모두 할당 해제한다.
  • 코드 라인 40~44에서 기타 이유로 매핑 설정 추가에 실패하는 경우도 pinctrl 이하 모두 할당 해제하고 함수를 빠져나간다.
  • 코드 라인 49~51에서 에러가 없는 경우이다. 생성한 pinctrl을 전역 pinctrl_list에 추가한다.

 

디바이스 트리로부터 매핑 생성

다음 그림은 클라이언트 디바이스 노드가 요구하는 매핑 맵을 등록할 때의 함수 호출 과정이다.

  • 클라이언트 디바이스 노드로부터 요청
    • “pinctrl-names=”에 요구된 state 명 수만큼 “pinctrl-0” 부터 “pinctrl-N”에 기재된 phandle에 해당하는 노드들
  • Pin controller 디바이스 노드에서 검색
    • phandle이 가리키는 노드가 Pin controller 서브 노드 또는 그 이하 서브 노드에 있는 pinmux/pinconf 노드의 매핑들을 파싱한 후 1개 이상의 매핑 맵을 생성하여 등록한다.

 

위의 디바이스 트리에서 다음 세 번의 매핑 맵 생성을 요구 받은 경우로 예를 들었다.

  • 첫 번째는 pin controller 디바이스 드라이버로부터 전체 매핑 맵을 만든다.
  • 두 번째 uart0 디바이스 드라이버로부터 pinmux의 펑션 지정을 위해 관련 매핑 맵을 만든다.
  • 세 번째 uart3 디바이스 드라이버로 부터 pinconf 설정을 위해 관련 매핑 맵을 만든다.

 

pin controller 디바이스측만 더 자세히 자료 구조를 살펴보면 다음과 같다.

  • 6개의 매핑 맵이 구성되어 있음을 알 수 있다.
    • 5개의 pinmux 매핑 맵
    • 1개의 pinconf 매핑 맵
  • 스테이트별로 관리하지 않고 디바이스가 요구하는 매핑들이 pinctrl_dt_map에 한꺼번에 등록되어 관리된다.

 

pinctrl_dt_to_map()

요청한 디바이스 트리 노드를 읽어 관련 pinmux/pinconf 노드를 파싱한 후 pinctrl_dt_map 구조체 이하에 pinmux/pinconf 매핑 맵을 등록한다.

  • “pinctrl-names” 속성을 통해 스테이트 명을 알아온다.
  • 스테이트명의 개수 만큼 “pinctrl-0” 번 부터 시작하는 “pinctrl-N” 번까지 phandle 리스트를 읽어온다.
  • phandle에 해당하는 pinmux/pinconf  노드를 찾아 해당 설정을 매핑 맵으로 구성한다.
    • pinmux 매핑 맵은 그룹명과 펑션명의 조합이다.
      • 예) uart0_modem_grp에 속한 핀들을 uart0 펑션으로 선택한다.
    • pinconf 매핑 맵은 그룹명 또는 핀명과 설정 항목들의 조합이다. 설정 항목은 복수가 가능하며 설정 값도 포함된다.
      • 예) uart3_pin에 bias-pull-up 설정하고, drive-strength를 30으로 설정한다.

drivers/pinctrl/devicetree.c – 1/2

int pinctrl_dt_to_map(struct pinctrl *p, struct pinctrl_dev *pctldev)
{
	struct device_node *np = p->dev->of_node;
	int state, ret;
	char *propname;
	struct property *prop;
	const char *statename;
	const __be32 *list;
	int size, config;
	phandle phandle;
	struct device_node *np_config;

	/* CONFIG_OF enabled, p->dev not instantiated from DT */
	if (!np) {
		if (of_have_populated_dt())
			dev_dbg(p->dev,
				"no of_node; not parsing pinctrl DT\n");
		return 0;
	}

	/* We may store pointers to property names within the node */
	of_node_get(np);

	/* For each defined state ID */
	for (state = 0; ; state++) {
		/* Retrieve the pinctrl-* property */
		propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state);
		prop = of_find_property(np, propname, &size);
		kfree(propname);
		if (!prop) {
			if (state == 0) {
				of_node_put(np);
				return -ENODEV;
			}
			break;
		}
		list = prop->value;
		size /= sizeof(*list);

		/* Determine whether pinctrl-names property names the state */
		ret = of_property_read_string_index(np, "pinctrl-names",
						    state, &statename);
		/*
		 * If not, statename is just the integer state ID. But rather
		 * than dynamically allocate it and have to free it later,
		 * just point part way into the property name for the string.
		 */
		if (ret < 0) {
			/* strlen("pinctrl-") == 8 */
			statename = prop->name + 8;
		}
  • 코드 라인 14~19에서 디바이스에 해당하는 디바이스 트리 노드가 없는 경우 그냥 함수를 빠져나간다.
  • 코드 라인 25~38에서 “pinctrl-<state>” 속성명을 찾아 phandle 리스트가 담긴 속성 값의 위치는 list에 담고, size에 phandle 개수를 담는다.
    • 예) “pinctrl-0=<&uart0 &uart3>”인 경우 list=uart0 phandle 값, size=2가 담긴다.
  • 코드 라인 41~51에서 “pinctrl-names” 속성명에서 state명을 읽어온다. 만일 발견하지 못한 경우 <state> 문자열로 사용한다.
    • 예) “pinctrl-0″인 경우 statename=”0″이 된다.

 

drivers/pinctrl/devicetree.c – 2/2

		/* For every referenced pin configuration node in it */
		for (config = 0; config < size; config++) {
			phandle = be32_to_cpup(list++);

			/* Look up the pin configuration node */
			np_config = of_find_node_by_phandle(phandle);
			if (!np_config) {
				dev_err(p->dev,
					"prop %s index %i invalid phandle\n",
					prop->name, config);
				ret = -EINVAL;
				goto err;
			}

			/* Parse the node */
			ret = dt_to_map_one_config(p, pctldev, statename,
						   np_config);
			of_node_put(np_config);
			if (ret < 0)
				goto err;
		}

		/* No entries in DT? Generate a dummy state table entry */
		if (!size) {
			ret = dt_remember_dummy_state(p, statename);
			if (ret < 0)
				goto err;
		}
	}

	return 0;

err:
	pinctrl_dt_free_maps(p);
	return ret;
}
  • 코드 라인 2~13에서 phandle 개수가 담긴 size 수 만큼 루프를 돌며 phandle을 읽어와서 해당 phandle에 해당하는 pinmux/pinconf 설정 노드를 찾아 np_config에 대입한다.
  • 코드 라인 16~21에서 디바이스 트리에서 pinmux/pinconf 설정 노드를 파싱하고 매핑 맵들을 생성한 후 다음 두 개의 자료 구조에 추가하여 관리하게 한다.
    • pinctrl_maps 구조체를 생성하고 파싱한 매핑 맵들을 추가한다.
    • pinctrl_dt_map 구조체를 생성하고 파싱한 매핑 맵들을 추가한다.
  • 코드 라인 24~28에서 phandle이 하나도 없이 비어있는 경우 매핑 맵이 비어있는 dummy용 매핑 맵 하나를 만들어 역시 위의 두개의 자료 구조를 생성하여 그 밑에 추가하여 관리하게 준비한다.
    • pinctrl 구조체 밑으로 자료 구조가 비어 문제가 될 상황을 사전에 없애기 위함이다.
    • 예) “pinctrl-0 = <>;”

 

하나의 phandle에 대응하는 pinmux/pinconf 노드 파싱 후 매핑 등록

dt_to_map_one_config()

drivers/pinctrl/devicetree.c

static int dt_to_map_one_config(struct pinctrl *p, const char *statename,
                struct device_node *np_config)
{
    struct device_node *np_pctldev;
    struct pinctrl_dev *pctldev;
    const struct pinctrl_ops *ops;
    int ret;
    struct pinctrl_map *map;
    unsigned num_maps;

    /* Find the pin controller containing np_config */
    np_pctldev = of_node_get(np_config);
    for (;;) {
        np_pctldev = of_get_next_parent(np_pctldev);
        if (!np_pctldev || of_node_is_root(np_pctldev)) {
            dev_info(p->dev, "could not find pctldev for node %s, deferring probe\n",
                np_config->full_name);
            of_node_put(np_pctldev);
            /* OK let's just assume this will appear later then */
            return -EPROBE_DEFER;
        }
        pctldev = get_pinctrl_dev_from_of_node(np_pctldev);
        if (pctldev)
            break;
        /* Do not defer probing of hogs (circular loop) */
        if (np_pctldev == p->dev->of_node) {
            of_node_put(np_pctldev);
            return -ENODEV;
        }
    }
    of_node_put(np_pctldev);

    /*
     * Call pinctrl driver to parse device tree node, and
     * generate mapping table entries
     */
    ops = pctldev->desc->pctlops;
    if (!ops->dt_node_to_map) {
        dev_err(p->dev, "pctldev %s doesn't support DT\n",
            dev_name(pctldev->dev));
        return -ENODEV;
    }
    ret = ops->dt_node_to_map(pctldev, np_config, &map, &num_maps);
    if (ret < 0)
        return ret;

    /* Stash the mapping table chunk away for later use */
    return dt_remember_or_free_map(p, statename, pctldev, map, num_maps);
}

하나의 pinmux/pinconf 설정 노드를 파싱하여 매핑 맵들을 생성한다.

  • 코드 라인 12에서 인자로 주어진 pinconf/pinmux 설정 노드가 담긴 np_config를 사용하기 위해 참조 카운터를 1 증가시킨다.
  • 코드 라인 13~29에서 인자로 주어진 pinconf/pinmux 설정 노드의 부모에 pin controller 노드가 있으므로 루프를 돌며 부모 노드 쪽에서 pinctrl 디바이스 노드를 찾는다.  그런 후 찾은 디바이스 노드에 연결된 pinctrl 디바이스를 알아와서 pctldev에 대입한다.
  • 코드 라인 30에서 인자로 주어진 pinconf/pinmux 설정 노드가 담긴 np_config의 사용이 완료되었으므로 참조 카운터를 1 감소시킨다.
  • 코드 라인 37~45에서 pin controller 드라이버에 구현된 ops 후크 함수를 호출하여 pinmux/pinconf 노드를 파싱하여 만든 매핑 맵 수 만큼 pinctrl_map 구조체들을 생성한 후 map 변수명에 담아온다.
    • ns2의 경우 generic 함수인 pinconf_generic_dt_to_map_pin() 함수를 호출한다.
  • 코드 라인 48에서 다음 두 가지 자료 구조에 위에서 파싱하여 만들어진 매핑 맵들을 추후 사용할 수 있도록 등록한다.
    • pinctrl_dt_maps 구조체를 생성한 후 pinctrl->dt_maps에 추가한다.
    • pinctl_maps 구조체를 생성한 후 전역 pinctrl_maps에 추가한다.

 

Generic pinconf 노드 파싱 및 매핑 등록

pinconf_generic_dt_node_to_map_pin()

include/linux/pinctrl/pinconf-generic.h

static inline int pinconf_generic_dt_node_to_map_pin(
        struct pinctrl_dev *pctldev, struct device_node *np_config,
        struct pinctrl_map **map, unsigned *num_maps)
{
    return pinconf_generic_dt_node_to_map(pctldev, np_config, map, num_maps,
            PIN_MAP_TYPE_CONFIGS_PIN);
}

Generic pinmux/pinconf 노드를 파싱하여 pinmux/pinconf 매핑 맵들을 생성해온다.

  • generic pinconf 노드를 파싱한 경우 대상이 그룹이라하더라도 설정 타입을 pin으로만 사용한다.

 

pinconf_generic_dt_node_to_map()

drivers/pinctrl/pinconf-generic.c

int pinconf_generic_dt_node_to_map(struct pinctrl_dev *pctldev,
        struct device_node *np_config, struct pinctrl_map **map,
        unsigned *num_maps, enum pinctrl_map_type type)
{
    unsigned reserved_maps;
    struct device_node *np;
    int ret;

    reserved_maps = 0;
    *map = NULL;
    *num_maps = 0;

    ret = pinconf_generic_dt_subnode_to_map(pctldev, np_config, map,
                        &reserved_maps, num_maps, type);
    if (ret < 0)
        goto exit;

    for_each_available_child_of_node(np_config, np) {
        ret = pinconf_generic_dt_subnode_to_map(pctldev, np, map,
                    &reserved_maps, num_maps, type);
        if (ret < 0)
            goto exit;
    }
    return 0;

exit:
    pinctrl_utils_free_map(pctldev, *map, *num_maps);
    return ret;
}
EXPORT_SYMBOL_GPL(pinconf_generic_dt_node_to_map);

generic한 pinmux/pinconf 노드를 파싱하여 pinmux/pinconf 매핑 맵들을 생성해온다. pinconf 노드에서 매핑 맵을 만들 때에는 인자로 전달받은 타입으로 매핑 맵들을 만든다. 타입에 invalid 타입이 주어진 경우 “pin” 속성이 사용된 경우 pin 타입을 사용하고, “groups” 속성을 사용한 경우 group 타입이 지정된다. pinmux 노드인 경우에는 인자로 주어진 타입과 상관 없다.

  • 코드 라인 13~16에서 인자로 전달받은 np_config 디바이스 노드를 파싱하여 매핑 맵들을 만든다.
  • 코드 라인 18~23에서 서브 노드가 있는 경우 이 함수를 재귀호출 처리한다. 이렇게 하는 경우 여러 번의 노드를 파싱하여도 최종으로 하나의 할당에 다 포함되게 하여 반환된다.

 

pinconf_generic_dt_subnode_to_map()

drivers/pinctrl/pinconf-generic.c -1/2-

int pinconf_generic_dt_subnode_to_map(struct pinctrl_dev *pctldev,
        struct device_node *np, struct pinctrl_map **map,
        unsigned *reserved_maps, unsigned *num_maps,
        enum pinctrl_map_type type)
{
    int ret;
    const char *function;
    struct device *dev = pctldev->dev;
    unsigned long *configs = NULL;
    unsigned num_configs = 0;
    unsigned reserve, strings_count;
    struct property *prop;
    const char *group;
    const char *subnode_target_type = "pins";

    ret = of_property_count_strings(np, "pins");
    if (ret < 0) {
        ret = of_property_count_strings(np, "groups");
        if (ret < 0)
            /* skip this node; may contain config child nodes */
            return 0;
        if (type == PIN_MAP_TYPE_INVALID)
            type = PIN_MAP_TYPE_CONFIGS_GROUP;
        subnode_target_type = "groups";
    } else {
        if (type == PIN_MAP_TYPE_INVALID)
            type = PIN_MAP_TYPE_CONFIGS_PIN;
    }
    strings_count = ret;

    ret = of_property_read_string(np, "function", &function);
    if (ret < 0) {
        /* EINVAL=missing, which is fine since it's optional */
        if (ret != -EINVAL)
            dev_err(dev, "%pOF: could not parse property function\n",
                np);
        function = NULL;
    }

    ret = pinconf_generic_parse_dt_config(np, pctldev, &configs,
                          &num_configs);
    if (ret < 0) {
        dev_err(dev, "%pOF: could not parse node property\n", np);
        return ret;
    }

    reserve = 0;
    if (function != NULL)
        reserve++;
    if (num_configs)
        reserve++;

    reserve *= strings_count;
  • 코드 라인 16~28에서 노드내에 “pins” 속성이 있으면 pin 설정 타입으로 지정한다. 만일 없으면 “groups” 속성을 찾아 group 설정 타입으로 지정한다. 둘 다 없는 경우 child 노드들에서 검색하도록 성공(0) 결과로 함수를 빠져나간다.
  • 코드 라인 29에서 “pins” 또는 “groups” 속성 값에 있는 항목 수를 카운터 한 값이 strings_count에 대입된다.
    • 예) pins = “mfio_13 mfio_14 mfio_15” -> strings_count = 3
  • 코드 라인 31~38에서 “function” 속성 값을 읽어온다. 옵션이므로 없을 수도 있다.
  • 코드 라인 40~45에서 pinconf 설정을 파싱하여 매핑 맵으로 등록한다. 출력 인자 num_configs에는 설정 항목 수가 반환된다.
    • bias-pull-up;  drive-strength = <30>; -> 2개의 설정 항목
  • 코드 라인 47~51에서 매핑 수 만큼 reserve 카운터를 증가시킨다.
    • pinmux 및 pinconf 항목이 둘 다 있으면 reserve 값은 최대 2가 될 수 있다.
  • 코드 라인 53에서 reserve 값에 strings_count를 곱한다.
    • 예) pins = “mfio_13 mfio_14 mfio_15”; function = “uart”;   bias-pull-up;  drive-strength = <30>;
      • 대상이 3개이고 pinmux가 있고, pinconf도 있으므로 -> reserve = 6
      • bias-pull-up 및 drive-strength 등의 설정 항목 수와 상관 없이 pinconf 매핑은 하나만 만들어진다.

 

drivers/pinctrl/pinconf-generic.c -2/2-

    ret = pinctrl_utils_reserve_map(pctldev, map, reserved_maps,
            num_maps, reserve);
    if (ret < 0)
        goto exit;

    of_property_for_each_string(np, subnode_target_type, prop, group) {
        if (function) {
            ret = pinctrl_utils_add_map_mux(pctldev, map,
                    reserved_maps, num_maps, group,
                    function);
            if (ret < 0)
                goto exit;
        }

        if (num_configs) {
            ret = pinctrl_utils_add_map_configs(pctldev, map,
                    reserved_maps, num_maps, group, configs,
                    num_configs, type);
            if (ret < 0)
                goto exit;
        }
    }
    ret = 0;

exit:
    kfree(configs);
    return ret;
}
EXPORT_SYMBOL_GPL(pinconf_generic_dt_subnode_to_map);
  • 코드 라인 1~4에서 기존 매핑 수가 reserved_maps이고, 새 매핑 수는 num_maps + 위 코드에서 산출한 reserve이다. 새 매핑 수가 기존 매핑 수보다 많은 경우 pinctrl_map 구조체를 새 매핑 수 만큼 다시 생성한다.
  • 코드 라인 6~22에서 “pins” 또는 “groups” 속성에 있는 문자열수 만큼 루프를 돌며 pinmux 매핑이 있는 경우 mux 타입 맵을 추가하고, pinconf 매핑이 있는 경우 pinconf 타입 맵을 추가한다.

 

pinconf_generic_parse_dt_config()

drivers/pinctrl/pinconf-generic.c

/**
 * pinconf_generic_parse_dt_config()
 * parse the config properties into generic pinconfig values.
 * @np: node containing the pinconfig properties
 * @configs: array with nconfigs entries containing the generic pinconf values
 *           must be freed when no longer necessary.
 * @nconfigs: umber of configurations
 */
int pinconf_generic_parse_dt_config(struct device_node *np,
                    struct pinctrl_dev *pctldev,
                    unsigned long **configs,
                    unsigned int *nconfigs)
{
    unsigned long *cfg;
    unsigned int max_cfg, ncfg = 0;
    int ret;

    if (!np)
        return -EINVAL;

    /* allocate a temporary array big enough to hold one of each option */
    max_cfg = ARRAY_SIZE(dt_params);
    if (pctldev)
        max_cfg += pctldev->desc->num_custom_params;
    cfg = kcalloc(max_cfg, sizeof(*cfg), GFP_KERNEL);
    if (!cfg)
        return -ENOMEM;

    parse_dt_cfg(np, dt_params, ARRAY_SIZE(dt_params), cfg, &ncfg);
    if (pctldev && pctldev->desc->num_custom_params &&
        pctldev->desc->custom_params)
        parse_dt_cfg(np, pctldev->desc->custom_params,
                 pctldev->desc->num_custom_params, cfg, &ncfg);

    ret = 0;

    /* no configs found at all */
    if (ncfg == 0) {
        *configs = NULL;
        *nconfigs = 0;
        goto out;
    }

    /*
     * Now limit the number of configs to the real number of
     * found properties.
     */
    *configs = kmemdup(cfg, ncfg * sizeof(unsigned long), GFP_KERNEL);
    if (!*configs) {
        ret = -ENOMEM;
        goto out;
    }

    *nconfigs = ncfg;

out:
    kfree(cfg);
    return ret;
}

pinconf 노드인 경우 configration 항목들을 파싱하여 그 수 만큼 unsigned long 타입 배열로 반환한다.

  • 코드 라인 22~27에서 필요한 최대 설정 항목 수만큼 임시 배열을 생성한다. 최대 설정 항목 수는 다음 두 항목의 합을 사용한다.
    • generic 디바이스 노드가 지원하는 설정 항목(bias, drive, …) 수
    • 해당 컨트롤러가 지원하는 custom 설정 항목 수
  • 코드 라인 29에서 generic 디바이스 노드가 지원하는 설정 항목들을 대상으로 인자로 전달받은 디바이스 노드를 파싱한다.
  • 코드 라인 30~33에서 해당 컨트롤러가 지원하는 custom 설정 항목들을 대상으로 인자로 전달받은 디바이스 노드를 파싱한다.
  • 코드 라인 38~42에서 검색된 설정 항목이 하나도 없는 경우 성공(0)으로 함수를 빠져나간다.
  • 코드 라인 48~58에서 검색된 설정 항목 수 만큼 기존 배열을 일부 복사하여 unsigned long 배열을 만들어 출력 인자 configs에 대입한다. 또한 출력 인자 nconfigs에도 검색된 설정 항목 수를 대입한다. 그런 후 처음에 임시로 만든 배열은 할당 해제한다.

 

다음 그림은 하나의 pinconf 노드를  파싱하여 출력 인자 configs에 unsigned long 타입 배열로 반환하는 모습을 보여준다.

  • 아래 예에서는 노드안에 3건의 설정을 사용하였다.
  • 파싱 항목을 검색하여 비교할 때 generic 파라메터들과 디바이스 드라이버 벤더에서 제공하는 custom 파라메터들을 대상으로 검색한다.
  • 검색된 항목을 대상으로 unsigned long 타입으로 설정을 만들 때 검색된 속성의 인덱스 번호를 우측 8비트에 사용한다. 그리고 좌측은 파라메터 속성 값이 주어지지 않은 경우에 한하여 파라메터의 멤버 default_value 값을 사용한다.

 

parse_dt_cfg()

drivers/pinctrl/pinconf-generic.c

/**
 * parse_dt_cfg() - Parse DT pinconf parameters
 * @np: DT node
 * @params: Array of describing generic parameters
 * @count:  Number of entries in @params
 * @cfg:    Array of parsed config options
 * @ncfg:   Number of entries in @cfg
 *
 * Parse the config options described in @params from @np and puts the result
 * in @cfg. @cfg does not need to be empty, entries are added beginning at
 * @ncfg. @ncfg is updated to reflect the number of entries after parsing. @cfg
 * needs to have enough memory allocated to hold all possible entries.
 */
static void parse_dt_cfg(struct device_node *np,
             const struct pinconf_generic_params *params,
             unsigned int count, unsigned long *cfg,
             unsigned int *ncfg)
{
    int i;

    for (i = 0; i < count; i++) {
        u32 val;
        int ret;
        const struct pinconf_generic_params *par = &params[i];

        ret = of_property_read_u32(np, par->property, &val);

        /* property not found */
        if (ret == -EINVAL)
            continue;

        /* use default value, when no value is specified */
        if (ret)
            val = par->default_value;

        pr_debug("found %s with value %u\n", par->property, val);
        cfg[*ncfg] = pinconf_to_config_packed(par->param, val);
        (*ncfg)++;
    }
}

count 수 만큼 루프를 돌며 디바이스 노드에서 속성을 찾아 출력 인자인 cfg 배열에 찾은 속성의 인덱스 번호를 하나씩 채워 넣는다.

 

pinctrl_utils_reserve_map()

drivers/pinctrl/pinctrl-utils.c

int pinctrl_utils_reserve_map(struct pinctrl_dev *pctldev,
        struct pinctrl_map **map, unsigned *reserved_maps,
        unsigned *num_maps, unsigned reserve)
{
    unsigned old_num = *reserved_maps;
    unsigned new_num = *num_maps + reserve;
    struct pinctrl_map *new_map;

    if (old_num >= new_num)
        return 0;

    new_map = krealloc(*map, sizeof(*new_map) * new_num, GFP_KERNEL);
    if (!new_map) { 
        dev_err(pctldev->dev, "krealloc(map) failed\n");
        return -ENOMEM;
    }

    memset(new_map + old_num, 0, (new_num - old_num) * sizeof(*new_map));

    *map = new_map;
    *reserved_maps = new_num;
    return 0; 
}
EXPORT_SYMBOL_GPL(pinctrl_utils_reserve_map);

필요한 만큼 pinctrl_map 배열을 확장하여 할당한다.

  • 2 단계 이상의 복합 노드로 구성될 때 이 함수가 호출될 때 마다 기존 pinctrl_map 배열보다 점점 커진다. 이 함수에서는 기존 할당된 pinctrl_map 배열을 버리고 더 커진 새 pinctrl_map 배열을 생성하고 기존 데이터를 복제하여 유지한다.

 

  • 코드 라인 5~10에서 기존 맵 수인 reserved_maps와 새로 만들 num_maps + reserve 와 비교하여 새 맵이 더 크지 않으면 그냥 성공(0) 결과로 새로운 맵 할당 없이 함수를 빠져나간다.
  • 코드 라인 12~16에서 새로 만들어야 하는 맵 수만큼 pinctrl_map 배열을 확장한다.
    • krealloc() 함수를 사용 시 새로 할당한 영역에 기존 기존 데이터는 그대로 복제되어 유지된다.
  • 코드 라인 18~22에서 기존 맵 수보다 추가 생성된 맵 수 만큼 생성한 맵의 뒷 부분을 0으로 클리어한다.

 

pinctrl_utils_add_map_mux()

drivers/pinctrl/pinctrl-utils.c

int pinctrl_utils_add_map_mux(struct pinctrl_dev *pctldev,
        struct pinctrl_map **map, unsigned *reserved_maps,
        unsigned *num_maps, const char *group,
        const char *function)
{   
    if (WARN_ON(*num_maps == *reserved_maps))
        return -ENOSPC;

    (*map)[*num_maps].type = PIN_MAP_TYPE_MUX_GROUP;
    (*map)[*num_maps].data.mux.group = group;
    (*map)[*num_maps].data.mux.function = function;
    (*num_maps)++;

    return 0;
}   
EXPORT_SYMBOL_GPL(pinctrl_utils_add_map_mux);

pinmux 매핑 맵의 자료 구조를 채운다.

 

pinctrl_utils_add_map_configs()

drivers/pinctrl/pinctrl-utils.c

int pinctrl_utils_add_map_configs(struct pinctrl_dev *pctldev,
        struct pinctrl_map **map, unsigned *reserved_maps,
        unsigned *num_maps, const char *group,
        unsigned long *configs, unsigned num_configs,
        enum pinctrl_map_type type)
{
    unsigned long *dup_configs;

    if (WARN_ON(*num_maps == *reserved_maps))
        return -ENOSPC;

    dup_configs = kmemdup(configs, num_configs * sizeof(*dup_configs),
                  GFP_KERNEL);
    if (!dup_configs) {
        dev_err(pctldev->dev, "kmemdup(configs) failed\n");
        return -ENOMEM;
    }

    (*map)[*num_maps].type = type;
    (*map)[*num_maps].data.configs.group_or_pin = group;
    (*map)[*num_maps].data.configs.configs = dup_configs;
    (*map)[*num_maps].data.configs.num_configs = num_configs;
    (*num_maps)++;

    return 0;
}
EXPORT_SYMBOL_GPL(pinctrl_utils_add_map_configs);

pinconf 매핑 맵의 자료 구조를 채운다.

  • configs 배열을 복제하여 사용하는 이유는 이 함수를 호출하는 곳에서 원래 configs 배열을 할당 해제한다.

 

추후 사용하기 위해 디바이스 트리 매핑들 등록

dt_remember_or_free_map()

drivers/pinctrl/devicetree.c

static int dt_remember_or_free_map(struct pinctrl *p, const char *statename,
                   struct pinctrl_dev *pctldev,
                   struct pinctrl_map *map, unsigned num_maps)
{
    int i;
    struct pinctrl_dt_map *dt_map;

    /* Initialize common mapping table entry fields */
    for (i = 0; i < num_maps; i++) {
        map[i].dev_name = dev_name(p->dev);
        map[i].name = statename;
        if (pctldev)
            map[i].ctrl_dev_name = dev_name(pctldev->dev);
    }

    /* Remember the converted mapping table entries */
    dt_map = kzalloc(sizeof(*dt_map), GFP_KERNEL);
    if (!dt_map) {
        dev_err(p->dev, "failed to alloc struct pinctrl_dt_map\n");
        dt_free_map(pctldev, map, num_maps);
        return -ENOMEM;
    }

    dt_map->pctldev = pctldev;
    dt_map->map = map;
    dt_map->num_maps = num_maps;
    list_add_tail(&dt_map->node, &p->dt_maps);

    return pinctrl_register_map(map, num_maps, false);
}

추후 사용하기 위해 파싱되어 알아온 매핑 맵들을 다음 두 위치에 추가한다.

  • pinctrl_dt_map 구조체를 생성한 후 매핑 맵들을 추가한다. 생성한 pinctrl_dt_map은 pinctrl->dt_maps 리스트에 추가하여 관리한다.
  • pinctrl_maps 구조체를 생성한 후 매핑 맵들을 추가한다. 생성한 pinctrl_maps 구조체는 전역 pinctrl_maps 리스트에 추가하여 관리한다.

 

  • 코드 라인 9~14에서 파싱하여 알아온 매핑 맵들을 그 수만큼 루프를 돌며 디바이스 명과 스테이트 명을 부여한다. pin control 디바이스도 지정된 경우 pin control 디바이스 명도 부여한다.
  • 코드 라인 17~27에서 pinctrl_dt_map 구조체를 만들고 그 밑으로 파싱하여 알아온 매핑맵들을 추가한다.
  • 코드 라인 29에서 pinctrl_maps 구조체를 만들고 그 밑으로 파싱하여 알아온 매핑맵들을 추가한다.
    • 세 번째 인수에 false를 주어 매핑 맵들의 복제는 하지 않는다.

 

pinctrl_register_map()

이 함수는 pinctrl_maps 구조체를 할당하고 인자로 전달받은 매핑들을 대입한 후 전역 pinctrl_maps 리스트에 추가한다.

drivers/pinctrl/core.c – 1/2

int pinctrl_register_map(const struct pinctrl_map *maps, unsigned num_maps,
			 bool dup)
{
	int i, ret;
	struct pinctrl_maps *maps_node;

	pr_debug("add %u pinctrl maps\n", num_maps);

	/* First sanity check the new mapping */
	for (i = 0; i < num_maps; i++) {
		if (!maps[i].dev_name) {
			pr_err("failed to register map %s (%d): no device given\n",
			       maps[i].name, i);
			return -EINVAL;
		}

		if (!maps[i].name) {
			pr_err("failed to register map %d: no map name given\n",
			       i);
			return -EINVAL;
		}

		if (maps[i].type != PIN_MAP_TYPE_DUMMY_STATE &&
				!maps[i].ctrl_dev_name) {
			pr_err("failed to register map %s (%d): no pin control device given\n",
			       maps[i].name, i);
			return -EINVAL;
		}

		switch (maps[i].type) {
		case PIN_MAP_TYPE_DUMMY_STATE:
			break;
		case PIN_MAP_TYPE_MUX_GROUP:
			ret = pinmux_validate_map(&maps[i], i);
			if (ret < 0)
				return ret;
			break;
		case PIN_MAP_TYPE_CONFIGS_PIN:
		case PIN_MAP_TYPE_CONFIGS_GROUP:
			ret = pinconf_validate_map(&maps[i], i);
			if (ret < 0)
				return ret;
			break;
		default:
			pr_err("failed to register map %s (%d): invalid type given\n",
			       maps[i].name, i);
			return -EINVAL;
		}
	}
  • 코드 라인 10~49에서 인자로 전달받은 maps 배열 수만큼 루프를 돌며 요청 타입에 따른 sanity 체크를 수행한다.

 

drivers/pinctrl/core.c – 2/2

	maps_node = kzalloc(sizeof(*maps_node), GFP_KERNEL);
	if (!maps_node)
		return -ENOMEM;

	maps_node->num_maps = num_maps;
	if (dup) {
		maps_node->maps = kmemdup(maps, sizeof(*maps) * num_maps,
					  GFP_KERNEL);
		if (!maps_node->maps) {
			kfree(maps_node);
			return -ENOMEM;
		}
	} else {
		maps_node->maps = maps;
	}

	mutex_lock(&pinctrl_maps_mutex);
	list_add_tail(&maps_node->node, &pinctrl_maps);
	mutex_unlock(&pinctrl_maps_mutex);

	return 0;
}
  • 코드 라인 1~15에서 pinctrl_maps 구조체를 할당한 후 인자로 전달받은 매핑 배열인 maps를 가리키게한다. 입력 인자 dup에 따라 매핑 배열을 복제할 수도 있다.
  • 코드 라인 17~19에서 할당받은 pinctrl_maps 구조체를 전역 리스트 pinctrl_maps에 추가한다.

 

스테이트별 설정(setting) 관리

디바이스 트리로부터 pinmux/pinconf 노드를 파싱하여 관리되고 있는 매핑 맵들을 스테이트 별로 설정들을 관리하도록 준비한다. 아래 그림은 single 스테이트를 사용한 경우이다.

  • pinmux/pinconf 매핑 맵에 있는 문자열을 setting으로 변환할 때 setting에는 인덱스 숫자로 변환되어 사용되도록 한다.
    • “gpio_0_1_grp” 그룹명은 2번 인덱스로 변경
    • “gpio” 펑션명은 2번 인덱스(selector)로 변경
    • “uart3_sin” 핀명은 67번 인덱스로 변경
    • unsigned long으로 표현된 configs 배열은 그대로 변경없이 사용한다.

 

add_setting()

drivers/pinctrl/core.c

static int add_setting(struct pinctrl *p, struct pinctrl_dev *pctldev,
               const struct pinctrl_map *map)
{
    struct pinctrl_state *state;
    struct pinctrl_setting *setting;
    int ret;

    state = find_state(p, map->name);
    if (!state)
        state = create_state(p, map->name);
    if (IS_ERR(state))
        return PTR_ERR(state);

    if (map->type == PIN_MAP_TYPE_DUMMY_STATE)
        return 0;

    setting = kzalloc(sizeof(*setting), GFP_KERNEL);
    if (!setting)
        return -ENOMEM;

    setting->type = map->type;

    if (pctldev)
        setting->pctldev = pctldev;
    else
        setting->pctldev =
            get_pinctrl_dev_from_devname(map->ctrl_dev_name);
    if (!setting->pctldev) {
        kfree(setting);
        /* Do not defer probing of hogs (circular loop) */
        if (!strcmp(map->ctrl_dev_name, map->dev_name))
            return -ENODEV;
        /*
         * OK let us guess that the driver is not there yet, and
         * let's defer obtaining this pinctrl handle to later...
         */
        dev_info(p->dev, "unknown pinctrl device %s in map entry, deferring probe",
            map->ctrl_dev_name);
        return -EPROBE_DEFER;
    }

    setting->dev_name = map->dev_name;

    switch (map->type) {
    case PIN_MAP_TYPE_MUX_GROUP:
        ret = pinmux_map_to_setting(map, setting);
        break;
    case PIN_MAP_TYPE_CONFIGS_PIN:
    case PIN_MAP_TYPE_CONFIGS_GROUP:
        ret = pinconf_map_to_setting(map, setting);
        break;
    default:
        ret = -EINVAL;
        break;
    }
    if (ret < 0) {
        kfree(setting);
        return ret;
    }

    list_add_tail(&setting->node, &state->settings);

    return 0;
}

인자로 요청한 스테이트명에 해당하는 스테이트 밑으로 pinctrl_state 구조체를 만들고 디바이스 트리 pinmux/pinconf 매핑 맵들을 변환하여 실제 사용하는 인덱스 숫자로 세팅들을 만들어 준비한다.

  • 코드 라인 8~12에서 인자로 주어진 스테이트명으로 스테이트를 검색한 후 처음 사용하는 경우 pinctrl_state 구조체를 생성하고 초기화한다.
  • 코드 라인 14~15에서 dummy 타입 매핑이 발견되면 생략하고 함수를 빠져나간다.
  • 코드 라인 17~42에서 pinctrl_setting 구조체를 할당하고 매핑 맵들의 실제 pinmux/pinconf 매핑 정보를 제외한 일반 정보로 채운다.
  • 코드 라인 44~59에서 pinmux 및 pinconf 매핑 타입에 따라 pinctrl_setting 구조체를 할당하고 매핑 정보에 해당하는 매핑 인덱스 값으로 변환하여 대입한다.
  • 코드 라인 61~63에서 생성한 pinctrl_setting 구조체를 해당 state에 추가하고 성공(0)을 반환한다.

 

pinmux_map_to_setting()

drivers/pinctrl/pinmux.c

int pinmux_map_to_setting(const struct pinctrl_map *map,
              struct pinctrl_setting *setting)
{
    struct pinctrl_dev *pctldev = setting->pctldev;
    const struct pinmux_ops *pmxops = pctldev->desc->pmxops;
    char const * const *groups;
    unsigned num_groups;
    int ret;
    const char *group;

    if (!pmxops) {
        dev_err(pctldev->dev, "does not support mux function\n");
        return -EINVAL;
    }

    ret = pinmux_func_name_to_selector(pctldev, map->data.mux.function);
    if (ret < 0) {
        dev_err(pctldev->dev, "invalid function %s in map table\n",
            map->data.mux.function);
        return ret;
    }
    setting->data.mux.func = ret;
    
    ret = pmxops->get_function_groups(pctldev, setting->data.mux.func,
                      &groups, &num_groups);
    if (ret < 0) {
        dev_err(pctldev->dev, "can't query groups for function %s\n",
            map->data.mux.function);
        return ret;
    }
    if (!num_groups) {
        dev_err(pctldev->dev,
            "function %s can't be selected on any group\n",
            map->data.mux.function);
        return -EINVAL;
    }
    if (map->data.mux.group) {
        group = map->data.mux.group;
        ret = match_string(groups, num_groups, group);
        if (ret < 0) {
            dev_err(pctldev->dev,
                "invalid group \"%s\" for function \"%s\"\n",
                group, map->data.mux.function);
            return ret;
        }
    } else {
        group = groups[0];
    }

    ret = pinctrl_get_group_selector(pctldev, group);
    if (ret < 0) {
        dev_err(pctldev->dev, "invalid group %s in map table\n",
            map->data.mux.group);
        return ret;
    }
    setting->data.mux.group = ret;

    return 0;
}

pinmux 매핑 맵을 setting으로 변환한다. 매핑 맵이 문자열로 구성되어있는데 이들을  setting에 사용할 인덱스 숫자로 바꾼다.

  • 코드 라인 11~14 pinmux 오퍼레이션이 준비되지 않은 pin controller 디바이스인 경우 pinmux 펑션이 지원되지 않는다는 에러 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 16~22에서 function 이름으로 selector 값을 찾아와서 setting의 function 값에 대입한다.
    • ns2 통해 function 명이 “uart0” 인 경우  -> 4
  • 코드 라인 24~36에서 function에서 사용할 수 있는 그룹명들을 groups에 알아온다.
  • 코드 라인 37~45에서 디바이스 트리 pinmux 매핑 맵이 요구하는 그룹명이 위의 groups 배열에서 검색하여 매치되지 않으면 에러를 출력하고 함수를 빠져나간다.
  • 코드 라인 46~48에서 pinmux 매핑 맵에 group 명이 지정되지 않은 경우 pinmux 디바이스 드라이버에서 검색한 그룹의 첫 번째 그룹명을 사용한다.
  • 코드 라인 50~56에서 group명으로 pinmux 디바이스 드라이버가 관리하는 그룹명에서 제공하는group명에 해당하는 selector 인덱스 값을 알아온다.
    • ns2 예) “uart0_modem_grp” 그룹명으로 검색하는 경우 21이 반환된다.

 

pinmux_func_name_to_selector()

drivers/pinctrl/pinmux.c

static int pinmux_func_name_to_selector(struct pinctrl_dev *pctldev,
                                        const char *function)
{
        const struct pinmux_ops *ops = pctldev->desc->pmxops;
        unsigned nfuncs = ops->get_functions_count(pctldev);
        unsigned selector = 0;

        /* See if this pctldev has this function */
        while (selector < nfuncs) {
                const char *fname = ops->get_function_name(pctldev, selector);

                if (!strcmp(function, fname))
                        return selector;

                selector++;
        }

        dev_err(pctldev->dev, "function '%s' not supported\n", function);
        return -EINVAL;
}

function 이름으로 selector 인덱스 값을 찾아온다.

  • 코드 라인 4~5에서 pin controller 디바이스 드라이버의 pinmux 오퍼레이션에 등록된 (*get_functions_count) 후크 함수를 통해 function의 개수를 알아온다.
    • 예) ns2_get_functions_count() 함수를 사용하여 8을 구해온다.
      • ns2 펑션은 nand, nor, gpio, pcie, uart0, uart1, uart2, pwm 까지 8가지이다.
  • 코드 라인 9~16에서 0번부터 시작하여 함수 개수 만큼 루프를 돌며 디바이스 드라이버의 pinmux 오퍼레이션에 등록된 (*get_function_name)을 통해 함수명을 알아온 function 명과 인자로 요청한 function 명이 일치할 때까지 비교한다. 일치하는 경우 selector 인덱스 값을 반환한다.
    • 예) ns2_get_function_name() 함수를 사용하여 0~7 범위에서 구해온다.

 

pinconf_map_to_setting()

drivers/pinctrl/pinconf.c

int pinconf_map_to_setting(const struct pinctrl_map *map,
              struct pinctrl_setting *setting)
{   
    struct pinctrl_dev *pctldev = setting->pctldev;
    int pin;
        
    switch (setting->type) {
    case PIN_MAP_TYPE_CONFIGS_PIN:
        pin = pin_get_from_name(pctldev,
                    map->data.configs.group_or_pin);
        if (pin < 0) {
            dev_err(pctldev->dev, "could not map pin config for \"%s\"",
                map->data.configs.group_or_pin);
            return pin;
        }
        setting->data.configs.group_or_pin = pin;
        break;
    case PIN_MAP_TYPE_CONFIGS_GROUP:
        pin = pinctrl_get_group_selector(pctldev,
                     map->data.configs.group_or_pin);
        if (pin < 0) {
            dev_err(pctldev->dev, "could not map group config for \"%s\"",
                map->data.configs.group_or_pin);
            return pin;
        }
        setting->data.configs.group_or_pin = pin;
        break;
    default:
        return -EINVAL;
    }   

    setting->data.configs.num_configs = map->data.configs.num_configs;
    setting->data.configs.configs = map->data.configs.configs;

    return 0;
}

pinconf 매핑 맵을 setting으로 변환한다. 매핑 맵이 문자열로 구성되어있는데 이들을  setting에 사용할 인덱스 숫자로 바꾼다.

  • 코드 라인 7~17에서 pinconf 매핑이 핀 타입인 경우 pin명 에 해당하는 인덱스 값을 알아와서 setting에 대입한다.
    • ns2 통해 function 명이 “uart0_sin” 인 경우  -> 67
  • 코드 라인 18~27에서 pinconf 매핑이 그룹 타입인 경우 group명 에 해당하는 인덱스 값을 알아와서 setting에 대입한다.
  • 코드 라인 32~33에서 pinconf 매핑에 있는 unsigned long 배열 타입의 configs는 그대로 setting에서도 사용한다.

 

Pin Control 디바이스 드라이버 진입부

Pin Controller + GPIO controller + interrupt controller 드라이버 등록 – for rpi2

rpi2용 드라이버에는 pin controller 뿐만 아니라 gpio controller와 interrupt controller까지 등록하여 사용하는 코드로 구성되어 있다.

  • rpi2는 gpio 핀을 통해 외부 인터럽트를 수신할 수 있다.

drivers/pinctrl/pinctrl-bcm2835.c

static const struct of_device_id bcm2835_pinctrl_match[] = {
        { .compatible = "brcm,bcm2835-gpio" },
        {}
};
MODULE_DEVICE_TABLE(of, bcm2835_pinctrl_match);

static struct platform_driver bcm2835_pinctrl_driver = {
        .probe = bcm2835_pinctrl_probe,
        .remove = bcm2835_pinctrl_remove,
        .driver = {
                .name = MODULE_NAME,
                .owner = THIS_MODULE,
                .of_match_table = bcm2835_pinctrl_match,
        },
};
module_platform_driver(bcm2835_pinctrl_driver);

 

bcm2835_pinctrl_probe()

drivers/pinctrl/bcm/pinctrl-bcm2835.c – 1/3

static int bcm2835_pinctrl_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct device_node *np = dev->of_node;
        struct bcm2835_pinctrl *pc;
        struct resource iomem;
        int err, i;
        BUILD_BUG_ON(ARRAY_SIZE(bcm2835_gpio_pins) != BCM2835_NUM_GPIOS);
        BUILD_BUG_ON(ARRAY_SIZE(bcm2835_gpio_groups) != BCM2835_NUM_GPIOS);

        pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL);
        if (!pc)
                return -ENOMEM;

        platform_set_drvdata(pdev, pc);
        pc->dev = dev;

        err = of_address_to_resource(np, 0, &iomem);
        if (err) {
                dev_err(dev, "could not get IO memory\n");
                return err;
        }

        pc->base = devm_ioremap_resource(dev, &iomem);
        if (IS_ERR(pc->base))
                return PTR_ERR(pc->base);
  • 코드 라인 11~13에서 bcm2835_pinctrl 구조체를 할당받는다.
  • 코드 라인 15~16에서 인자로 전달받은 플랫폼 디바이스와 bcm2835_pinctrl 구조체와 상호 관계를 연결해둔다.
    • &pdev->dev->driver_data = pc
    • pc->dev = &pdev->dev
  • 코드 라인 18~22에서 디바이스 노드의 ranges 속성에서 읽어들인 IO 주소와 크기를 알아와서 리소스 타입인 iomem에 저장한다.
  • 코드 라인 24~26에서 iomem 리소스 정보로 io 매핑을 한다. 반환되는 결과는 매핑한 가상 주소이다.

 

drivers/pinctrl/bcm/pinctrl-bcm2835.c – 2/3

.	pc->gpio_chip = bcm2835_gpio_chip;
	pc->gpio_chip.parent = dev;
	pc->gpio_chip.of_node = np;

	for (i = 0; i < BCM2835_NUM_BANKS; i++) {
		unsigned long events;
		unsigned offset;

		/* clear event detection flags */
		bcm2835_gpio_wr(pc, GPREN0 + i * 4, 0);
		bcm2835_gpio_wr(pc, GPFEN0 + i * 4, 0);
		bcm2835_gpio_wr(pc, GPHEN0 + i * 4, 0);
		bcm2835_gpio_wr(pc, GPLEN0 + i * 4, 0);
		bcm2835_gpio_wr(pc, GPAREN0 + i * 4, 0);
		bcm2835_gpio_wr(pc, GPAFEN0 + i * 4, 0);

		/* clear all the events */
		events = bcm2835_gpio_rd(pc, GPEDS0 + i * 4);
		for_each_set_bit(offset, &events, 32)
			bcm2835_gpio_wr(pc, GPEDS0 + i * 4, BIT(offset));

		spin_lock_init(&pc->irq_lock[i]);
	}

	err = gpiochip_add_data(&pc->gpio_chip, pc);
	if (err) {
		dev_err(dev, "could not add GPIO chip\n");
		return err;
	}

	err = gpiochip_irqchip_add(&pc->gpio_chip, &bcm2835_gpio_irq_chip,
				   0, handle_level_irq, IRQ_TYPE_NONE);
	if (err) {
		dev_info(dev, "could not add irqchip\n");
		return err;
	}

위의 코드들은 gpio 컨트롤러를 등록하는 부분들이다. (여기에서는 설명 생략)

 

drivers/pinctrl/bcm/pinctrl-bcm2835.c – 3/3

	for (i = 0; i < BCM2835_NUM_IRQS; i++) {
		pc->irq[i] = irq_of_parse_and_map(np, i);

		if (pc->irq[i] == 0)
			continue;

		/*
		 * Use the same handler for all groups: this is necessary
		 * since we use one gpiochip to cover all lines - the
		 * irq handler then needs to figure out which group and
		 * bank that was firing the IRQ and look up the per-group
		 * and bank data.
		 */
		gpiochip_set_chained_irqchip(&pc->gpio_chip,
					     &bcm2835_gpio_irq_chip,
					     pc->irq[i],
					     bcm2835_gpio_irq_handler);
	}

	pc->pctl_dev = devm_pinctrl_register(dev, &bcm2835_pinctrl_desc, pc);
	if (IS_ERR(pc->pctl_dev)) {
		gpiochip_remove(&pc->gpio_chip);
		return PTR_ERR(pc->pctl_dev);
	}

	pc->gpio_range = bcm2835_pinctrl_gpio_range;
	pc->gpio_range.base = pc->gpio_chip.base;
	pc->gpio_range.gc = &pc->gpio_chip;
	pinctrl_add_gpio_range(pc->pctl_dev, &pc->gpio_range);

	return 0;
}
  • 코드 라인 1~18에서 이 부분의 코드들은 interrupt controller를 등록하는 부분이다. (여기에서는 설명은 생략)
  • 코드 라인 20~24에서 pinctrl, pinmux 및 pinconf의 오퍼레이션들과 핀 디스크립터들을 포함한 pinctrl 디스크립터 정보로 pin controller를 등록한다.
  • 코드 라인 26~29에서 gpio range를 등록한다. (deprecated)

 

Pin Control 드라이버 등록 – for ns2

rpi2 드라이버와는 달리 오로지 pin controller 드라이버만으로 구성되어 있다.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

static const struct of_device_id ns2_pinmux_of_match[] = {
        {.compatible = "brcm,ns2-pinmux"},
        { }
};

static struct platform_driver ns2_pinmux_driver = {
        .driver = {
                .name = "ns2-pinmux",
                .of_match_table = ns2_pinmux_of_match,
        },
        .probe = ns2_pinmux_probe,
};

static int __init ns2_pinmux_init(void)
{
        return platform_driver_register(&ns2_pinmux_driver);
}
arch_initcall(ns2_pinmux_init);

 

ns2_pinmux_probe()

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

static int ns2_pinmux_probe(struct platform_device *pdev)
{
        struct ns2_pinctrl *pinctrl;
        struct resource *res;
        int i, ret;
        struct pinctrl_pin_desc *pins;
        unsigned int num_pins = ARRAY_SIZE(ns2_pins);

        pinctrl = devm_kzalloc(&pdev->dev, sizeof(*pinctrl), GFP_KERNEL);
        if (!pinctrl)
                return -ENOMEM;

        pinctrl->dev = &pdev->dev; 
        platform_set_drvdata(pdev, pinctrl);
        spin_lock_init(&pinctrl->lock);

        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        pinctrl->base0 = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(pinctrl->base0))
                return PTR_ERR(pinctrl->base0);

        res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
        pinctrl->base1 = devm_ioremap_nocache(&pdev->dev, res->start,
                                        resource_size(res));
        if (!pinctrl->base1) {
                dev_err(&pdev->dev, "unable to map I/O space\n");
                return -ENOMEM;
        }

        res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
        pinctrl->pinconf_base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(pinctrl->pinconf_base))
                return PTR_ERR(pinctrl->pinconf_base);

        ret = ns2_mux_log_init(pinctrl);
        if (ret) {
                dev_err(&pdev->dev, "unable to initialize IOMUX log\n");
                return ret;
        }

        pins = devm_kcalloc(&pdev->dev, num_pins, sizeof(*pins), GFP_KERNEL);
        if (!pins)
                return -ENOMEM;

        for (i = 0; i < num_pins; i++) {
                pins[i].number = ns2_pins[i].pin;
                pins[i].name = ns2_pins[i].name;
                pins[i].drv_data = &ns2_pins[i];
        }

        pinctrl->groups = ns2_pin_groups;
        pinctrl->num_groups = ARRAY_SIZE(ns2_pin_groups);
        pinctrl->functions = ns2_pin_functions;
        pinctrl->num_functions = ARRAY_SIZE(ns2_pin_functions);
        ns2_pinctrl_desc.pins = pins;
        ns2_pinctrl_desc.npins = num_pins;

        pinctrl->pctl = pinctrl_register(&ns2_pinctrl_desc, &pdev->dev,
                        pinctrl);
        if (IS_ERR(pinctrl->pctl)) {
                dev_err(&pdev->dev, "unable to register IOMUX pinctrl\n");
                return PTR_ERR(pinctrl->pctl);
        }
 
        return 0;
}
  • 코드 라인 9~11에서 ns2_pinctrl 구조체를 할당받는다.
  • 코드 라인 13~14에서 인자로 전달받은 플랫폼 디바이스와 bcm2835_pinctrl 구조체와 상호 관계를 연결해둔다.
    • &pdev->dev->driver_data = pc
    • pc->dev = &pdev->dev
  • 코드 라인 17~20에서 플랫폼 디바이스에 저장된 첫 번째 IOMUX 레지스터에 대한 IO 리소스 메모리 정보를 알아온 후 매핑한다. 반환되는 결과는 매핑한 가상 주소이다.
  • 코드 라인 22~28에서 플랫폼 디바이스에 저장된 두 번째 IOMUX 레지스터에 대한 리소스 메모리 정보를 알아온 후 매핑한다. 반환되는 결과는 매핑한 가상 주소이다.
  • 코드 라인 30~33에서 플랫폼 디바이스에 저장된 configuration 레지스터에 대한 리소스 메모리 정보를 알아온 후 매핑한다. 반환되는 결과는 매핑한 가상 주소이다.
  • 코드 라인 35~39에서 최대 NS2_NUM_IOMUX(19)개까지 관리하는 IOMUX 정보를 초기화한다.
  • 코드 라인 41~49에서 pinctrl_pin_desc 구조체를 할당받은 후 컴파일 타임에 구성한 ns2 핀 정보로 대입한다.
  • 코드 라인 51~63에서 pinctrl, pinmux 및 pinconf의 오퍼레이션들과 핀 디스크립터들을 포함한 pinctrl 디스크립터 정보로 pin controller를 등록한다.

 

Pin Controller 등록

pin controller를 등록하면 디바이스 트리를 통해 부트업 시 pinmux 및 pinconf 매핑 항목들을 동작시킬 수 있다.

 

pinctrl_register() 함수를 통해 등록되는 pin controller의 관계는 다음과 같다.

 

pinctrl_register()

이 함수는 pin controller 디바이스를 생성한 후 등록하고 반환한다. 더 자세히 알아보면 다음과 같다.

  • 인자로 전달받은 pinctrl 디스크립터, 디바이스 및 디바이스 데이터를 사용하여 pin controller 디바이스를 생성한다.
  • 내부에서는 pinctrl 디스크립터에서 제공한 pin 디스크립터들을 RADIX 트리에 등록한다.
  • “default” 스테이트를 검색하고 찾은 경우 등록된 pinmux/pinconf 매핑들을 HW에 적용한다.
  • “sleep” 스테이트를 검색하여 알아온다.

drivers/pinctrl/core.c – 1/2

/**
 * pinctrl_register() - register a pin controller device
 * @pctldesc: descriptor for this pin controller
 * @dev: parent device for this pin controller
 * @driver_data: private pin controller data for this pin controller
 */
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
                                    struct device *dev, void *driver_data)
{
        struct pinctrl_dev *pctldev;
        int ret;

        if (!pctldesc)
                return ERR_PTR(-EINVAL);
        if (!pctldesc->name)
                return ERR_PTR(-EINVAL);

        pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);
        if (pctldev == NULL) {
                dev_err(dev, "failed to alloc struct pinctrl_dev\n");
                return ERR_PTR(-ENOMEM);
        }

        /* Initialize pin control device struct */
        pctldev->owner = pctldesc->owner;
        pctldev->desc = pctldesc;
        pctldev->driver_data = driver_data;
        INIT_RADIX_TREE(&pctldev->pin_desc_tree, GFP_KERNEL);
        INIT_LIST_HEAD(&pctldev->gpio_ranges);
        pctldev->dev = dev;
        mutex_init(&pctldev->mutex);

        /* check core ops for sanity */
        ret = pinctrl_check_ops(pctldev);
        if (ret) {
                dev_err(dev, "pinctrl ops lacks necessary functions\n");
                goto out_err;
        }

        /* If we're implementing pinmuxing, check the ops for sanity */
        if (pctldesc->pmxops) {
                ret = pinmux_check_ops(pctldev);
                if (ret)
                        goto out_err;
        }

        /* If we're implementing pinconfig, check the ops for sanity */
        if (pctldesc->confops) {
                ret = pinconf_check_ops(pctldev);
                if (ret)
                        goto out_err;
        }
  • 코드 라인 18~31에서 pinctrl_dev 구조체를 할당받고 초기화한다.
  • 코드 라인 34~52에서 pinctrl, pinmux, pinconf 오퍼레이션에 대한 sanity 체크를 수행한다.

 

drivers/pinctrl/core.c – 2/2

        /* Register all the pins */
        dev_dbg(dev, "try to register %d pins ...\n",  pctldesc->npins);
        ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);
        if (ret) {
                dev_err(dev, "error during pin registration\n");
                pinctrl_free_pindescs(pctldev, pctldesc->pins,
                                      pctldesc->npins);
                goto out_err;
        }

        mutex_lock(&pinctrldev_list_mutex);
        list_add_tail(&pctldev->node, &pinctrldev_list);
        mutex_unlock(&pinctrldev_list_mutex);

        pctldev->p = pinctrl_get(pctldev->dev);

        if (!IS_ERR(pctldev->p)) {
                pctldev->hog_default =
                        pinctrl_lookup_state(pctldev->p, PINCTRL_STATE_DEFAULT);
                if (IS_ERR(pctldev->hog_default)) {
                        dev_dbg(dev, "failed to lookup the default state\n");
                } else {
                        if (pinctrl_select_state(pctldev->p,
                                                pctldev->hog_default))
                                dev_err(dev,
                                        "failed to select default state\n");
                }

                pctldev->hog_sleep =
                        pinctrl_lookup_state(pctldev->p,
                                                    PINCTRL_STATE_SLEEP);
                if (IS_ERR(pctldev->hog_sleep))
                        dev_dbg(dev, "failed to lookup the sleep state\n");
        }

        pinctrl_init_device_debugfs(pctldev);

        return pctldev;

out_err:
        mutex_destroy(&pctldev->mutex);
        kfree(pctldev);
        return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(pinctrl_register);
  • 코드 라인 3~9에서 선언된 모든 핀들을 등록시킨다.
  • 코드 라인 11~13에서 전역 리스트 pinctrldev_list에 할당받아 초기화한 pinctrl_dev 구조체를 추가한다.
  • 코드 라인 15에서 디바이스에 대한 pinctrl 구조체를 찾아 얻어온다. pinctrl이 발견되지 않는 경우 create_pinctrl() 함수를 호출하여 pinctrl을 생성해온다.
    • pinctrl을 생성 시에는 pinctrl_dt_to_map() 함수를 호출하여 디바이스 트리를 파싱하여 pinmux/pinconf 매핑 항목들을 pinctrrl_map 구조체에 담아 등록한다. 그런 후 매핑들 수만큼 add_setting() 함수를 호출하여 pinctrl_map을 사용하여 pinctrl_setting 구조체를 생성해내어 등록한다.
  • 코드 라인 17~27에서 pinctrl을 잘 가져온 경우 “default” 상태를 검색하고 찾은 경우 등록된 pinmux/pinconf 매핑들을 HW에 적용한다.
  • 코드 라인 29~33에서 “sleep” 상태를 검색해온다.
  • 코드 라인 36에서 debugfs 용도로 각종 virtual file들을 생성한다.

 

스테이트 관리

pinctrl_lookup_state()

drivers/pinctrl/core.c

/**
 * pinctrl_lookup_state() - retrieves a state handle from a pinctrl handle
 * @p: the pinctrl handle to retrieve the state from
 * @name: the state name to retrieve
 */
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p,
                                                 const char *name)
{
        struct pinctrl_state *state;

        state = find_state(p, name);
        if (!state) {
                if (pinctrl_dummy_state) {
                        /* create dummy state */
                        dev_dbg(p->dev, "using pinctrl dummy state (%s)\n",
                                name);
                        state = create_state(p, name);
                } else
                        state = ERR_PTR(-ENODEV);
        }

        return state;
}
EXPORT_SYMBOL_GPL(pinctrl_lookup_state);

인자로 주어진 pinctrl 구조체와 스테이트 이름으로  pinctrl_state 구조체를 찾아온다.

 

find_state()

drivers/pinctrl/core.c

static struct pinctrl_state *find_state(struct pinctrl *p,
					const char *name)
{
	struct pinctrl_state *state;

	list_for_each_entry(state, &p->states, node)
		if (!strcmp(state->name, name))
			return state;

	return NULL;
}

인자로 주어진 pinctrl 구조체의 멤버 states 리스트에서 스테이트 이름으로  pinctrl_state 구조체를 찾아온다. 없으면 null을 반환한다.

 

create_state()

drivers/pinctrl/core.c

static struct pinctrl_state *create_state(struct pinctrl *p,
                                          const char *name)
{
        struct pinctrl_state *state;

        state = kzalloc(sizeof(*state), GFP_KERNEL);
        if (state == NULL) {
                dev_err(p->dev,
                        "failed to alloc struct pinctrl_state\n");
                return ERR_PTR(-ENOMEM);
        }

        state->name = name;
        INIT_LIST_HEAD(&state->settings);

        list_add_tail(&state->node, &p->states);

        return state;
}

pinctrl_state 구조체를 생성한다.

  • 생성한 pinctrl_state 구조체에는 두 번째 인자로 주어진 스테이트 명을 대입한다.
  • 그리고 첫 번째 인자로 받은 pinctrl 구조체에서 멤버 states 리스트에 생성한 구조체를 추가한 후 반환한다.

 

pinctrl_select_state()

state를 선택하고 pinconf/pinmux setting을 HW에 적용한다. 이미 state가 선택된 경우 무시하고 빠져나온다.

drivers/pinctrl/core.c – 1/2

/**
 * pinctrl_select_state() - select/activate/program a pinctrl state to HW
 * @p: the pinctrl handle for the device that requests configuration
 * @state: the state handle to select/activate/program
 */
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
{
	struct pinctrl_setting *setting, *setting2;
	struct pinctrl_state *old_state = p->state;
	int ret;

	if (p->state == state)
		return 0;

	if (p->state) {
		/*
		 * For each pinmux setting in the old state, forget SW's record
		 * of mux owner for that pingroup. Any pingroups which are
		 * still owned by the new state will be re-acquired by the call
		 * to pinmux_enable_setting() in the loop below.
		 */
		list_for_each_entry(setting, &p->state->settings, node) {
			if (setting->type != PIN_MAP_TYPE_MUX_GROUP)
				continue;
			pinmux_disable_setting(setting);
		}
	}

	p->state = NULL;

	/* Apply all the settings for the new state */
	list_for_each_entry(setting, &state->settings, node) {
		switch (setting->type) {
		case PIN_MAP_TYPE_MUX_GROUP:
			ret = pinmux_enable_setting(setting);
			break;
		case PIN_MAP_TYPE_CONFIGS_PIN:
		case PIN_MAP_TYPE_CONFIGS_GROUP:
			ret = pinconf_apply_setting(setting);
			break;
		default:
			ret = -EINVAL;
			break;
		}

		if (ret < 0) {
			goto unapply_new_state;
		}
	}

	p->state = state;

	return 0;
  • 코드 라인 12~13에서 인자로 전달 받은 state가 이미 선택된 경우 아무런 적용 없이 성공리에 함수를 빠져나온다.
  • 코드 라인 15~27에서 기존 state 명으로 적용된 pinmux setting들을 HW에 disable한다.
  • 코드 라인 29에서 임시로 state를 null로 대입한다.
  • 코드 라인 32~49에서 새로운 state 명으로 pinmux/pinconf setting들을 HW에 적용한다.
  • 코드 라인 51~53에서 새 state를 대입하고 성공 결과로 리턴한다.

 

drivers/pinctrl/core.c – 2/2

unapply_new_state:
	dev_err(p->dev, "Error applying setting, reverse things back\n");

	list_for_each_entry(setting2, &state->settings, node) {
		if (&setting2->node == &setting->node)
			break;
		/*
		 * All we can do here is pinmux_disable_setting.
		 * That means that some pins are muxed differently now
		 * than they were before applying the setting (We can't
		 * "unmux a pin"!), but it's not a big deal since the pins
		 * are free to be muxed by another apply_setting.
		 */
		if (setting2->type == PIN_MAP_TYPE_MUX_GROUP)
			pinmux_disable_setting(setting2);
	}

	/* There's no infinite recursive loop here because p->state is NULL */
	if (old_state)
		pinctrl_select_state(p, old_state);

	return ret;
}
EXPORT_SYMBOL_GPL(pinctrl_select_state);

새 상태를 적용하다 에러가 난 경우 모두 취소한다.

 

Pinmux HW 설정

pinmux_enable_setting()

pin controller 디바이스 드라이버를 통해 pinmux 설정이 HW에 적용되도록 요청한다.

drivers/pinctrl/pinmux.c

int pinmux_enable_setting(struct pinctrl_setting const *setting)
{
        struct pinctrl_dev *pctldev = setting->pctldev;
        const struct pinctrl_ops *pctlops = pctldev->desc->pctlops;
        const struct pinmux_ops *ops = pctldev->desc->pmxops;
        int ret = 0;
        const unsigned *pins = NULL;
        unsigned num_pins = 0;
        int i;
        struct pin_desc *desc;

        if (pctlops->get_group_pins)
                ret = pctlops->get_group_pins(pctldev, setting->data.mux.group,
                                              &pins, &num_pins);

        if (ret) {
                const char *gname;

                /* errors only affect debug data, so just warn */
                gname = pctlops->get_group_name(pctldev,
                                                setting->data.mux.group);
                dev_warn(pctldev->dev,
                         "could not get pins for group %s\n",
                         gname);
                num_pins = 0;
        }

        /* Try to allocate all pins in this group, one by one */
        for (i = 0; i < num_pins; i++) {
                ret = pin_request(pctldev, pins[i], setting->dev_name, NULL);
                if (ret) {
                        const char *gname;
                        const char *pname;

                        desc = pin_desc_get(pctldev, pins[i]);
                        pname = desc ? desc->name : "non-existing";
                        gname = pctlops->get_group_name(pctldev,
                                                setting->data.mux.group);
                        dev_err(pctldev->dev,
                                "could not request pin %d (%s) from group %s "
                                " on device %s\n",
                                pins[i], pname, gname,
                                pinctrl_dev_get_name(pctldev));
                        goto err_pin_request;
                }
        }
  • 코드 라인 12~14에서 pinmux setting의 group 인덱스에 해당하는 핀들 번로를 알아온다. 알아온 핀들 번호를 pins 변수에, 그리고 핀 개수를 num_pins에 대입한다.
    • ns2의 경우 pin control 디바이스 드라이버의 pinctrl 오퍼레이션에서 (*get_group_pins) 후크 함수를 호출하면 ns2_get_group_pins() 함수를 호출하게 된다.
    • ns2 예) “uart0_modem_grp” 그룹을 의미하는 21번 인덱스가 주어지면 -> pins = {  39, 40, 41, 42 }, num_pins = 4 가 결과로 주어진다.
  • 코드 라인 16~26에서 핀 컨트롤러 드라이버에서 그룹 인덱스로 pin 번호들을 알아오지 못하면 그룹 명을 알아와서 경고 메시지를 출력한다.
    • ns2의 경우 pin control 디바이스 드라이버의 pinctrl 오퍼레이션에서 (*get_group_name) 후크 함수를 호출하면 ns2_get_group_name() 함수를 호출하게 된다.
    • ns2 예) 21번 인덱스가 주어지면 -> gname = “uart0_modem_grp”라는 그룹명이 결과로 주어진다.
  • 코드 라인 29~46에서 그룹에 속한 핀 개수만큼 루프를 돌며 해당 핀의 할당을 하나 하나 시도한다. 만일 요청이 실패하면 에러 메시지를 출력한 후 루프를 도는 도중 할당한 핀을 모두 해제한 후 함수를 빠져나간다.

 

        /* Now that we have acquired the pins, encode the mux setting */
        for (i = 0; i < num_pins; i++) {
                desc = pin_desc_get(pctldev, pins[i]);
                if (desc == NULL) {
                        dev_warn(pctldev->dev,
                                 "could not get pin desc for pin %d\n",
                                 pins[i]);
                        continue;
                }
                desc->mux_setting = &(setting->data.mux);
        }

        ret = ops->set_mux(pctldev, setting->data.mux.func,
                           setting->data.mux.group);

        if (ret)
                goto err_set_mux;

        return 0;

err_set_mux:
        for (i = 0; i < num_pins; i++) {
                desc = pin_desc_get(pctldev, pins[i]);
                if (desc)
                        desc->mux_setting = NULL;
        }
err_pin_request:
        /* On error release all taken pins */
        while (--i >= 0)
                pin_free(pctldev, pins[i], NULL);

        return ret;
}
  • 코드 라인 2~11에서 그룹에 속한 핀 개수만큼 루프를 돌며 핀 디스크립터를 알아온 후 디스크립터의 mux_setting 값에 pinmux 세팅을 대입한다.
    • desc->mux_setting이 pinctrl_setting_mux 구조체를 가리키게 된다.
  • 코드 라인 13~17에서 group에 속한 핀들에 function을 선택하도록 H/W pin multiplexing을 요청한다.
    • ns2의 경우 pin controller 디바이스 드라이버의 pinmux 오퍼레이션의 (*set_mux) 후크 함수를 호출하면 ns2_pinmux_enable() 함수가 호출된다.

 

Pinconf HW 설정

pinconf_apply_setting()

drivers/pinctrl/pinconf.c

int pinconf_apply_setting(struct pinctrl_setting const *setting)
{                       
        struct pinctrl_dev *pctldev = setting->pctldev;
        const struct pinconf_ops *ops = pctldev->desc->confops;
        int ret;        
                        
        if (!ops) {
                dev_err(pctldev->dev, "missing confops\n");
                return -EINVAL;
        }

        switch (setting->type) {
        case PIN_MAP_TYPE_CONFIGS_PIN:
                if (!ops->pin_config_set) {
                        dev_err(pctldev->dev, "missing pin_config_set op\n");
                        return -EINVAL;
                }
                ret = ops->pin_config_set(pctldev,
                                setting->data.configs.group_or_pin,
                                setting->data.configs.configs,
                                setting->data.configs.num_configs);
                if (ret < 0) {
                        dev_err(pctldev->dev,
                                "pin_config_set op failed for pin %d\n",
                                setting->data.configs.group_or_pin);
                        return ret;
                }
                break;
        case PIN_MAP_TYPE_CONFIGS_GROUP:
                if (!ops->pin_config_group_set) {
                        dev_err(pctldev->dev,
                                "missing pin_config_group_set op\n");
                        return -EINVAL;
                }
                ret = ops->pin_config_group_set(pctldev,
                                setting->data.configs.group_or_pin,
                                setting->data.configs.configs,
                                setting->data.configs.num_configs);
                if (ret < 0) {
                        dev_err(pctldev->dev,
                                "pin_config_group_set op failed for group %d\n",
                                setting->data.configs.group_or_pin);
                        return ret;
                }
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

pin controller 디바이스 드라이버를 통해 pinconf 설정이 HW에 적용되도록 요청한다.

  • 코드 라인 3~10에서 pin controller 디바이스 드라이버의 pinconf 오퍼레이션이 준비되지 않은 경우 에러 메시지를 출력하고 에러 결과로 함수를 그냥 빠져나간다.
  • 코드라인 12~28에서 핀 인덱스에 해당하는 핀을 찾아 주어진 pinconf setting들을 H/W에 적용하게 한다.
    • ns2의 경우 pin control 디바이스 드라이버의 pinconf 오퍼레이션에서 (*pin_config_set) 후크 함수를 호출하면 ns2_pin_config_set() 함수를 호출하게 된다.
    • ns2 예) “uart3_sin” 핀을 의미하는 67번 인덱스와 0x0103 setting이 주어지면 해당 번호의 핀에 pinconf HW 설정 중 하나인 bias-pull-up 설정이된다.
  • 코드라인 29~45에서 그룹 인덱스에 해당하는 핀들을 대상으로 주어진 pinconf setting들을 H/W에 적용하게 한다.
    • ns2의 경우 pin control 디바이스 드라이버의 pinconf 오퍼레이션에서 (*pin_config_group_set) 후크 함수가 없으므로 에러 메시지가 출력되고 함수를 빠져나간다.
    • 즉, ns2의 경우 그룹을 대상으로 pinconf 설정을 하지 못한다.

 

참고

 

Pin Control Subsystem -1-

Pin Control은 각 벤더들의 독자적인 방법으로 pin 컨트롤러(H/W)를 제어하였었다. 그러다 Pin Control 서브시스템이 2011년 후반 커널 v3.2에서 Linus Walleij에 의해 소개되면서 드라이버 제어 및 구현이 표준화되었다. 디바이스 트리를 사용한 드라이버를 통해 더 간단히 설정이 가능해졌다. (샘플 코드들은 커널 v4.14 기준)

 

pin control subsystem을 통해 레지스터들을 조작하여 각 핀의 멀티플렉싱 및 속성을 설정할 수 있다.

  • 핀 멀티플렉싱(PINMUX)
  • 핀 속성 설정(PINCONF)
    • 바이어스 조정(pull down, pull up, open drain)
    • 부하 조정
    • 드라이브 강도(strength)

 

SoC에서 사용되는 수백개의 핀(Pin)들

많은 임베디드 SoC 칩은 다양한 기능들을 한 개의 칩에 포함시켜 제공한다. 먼저 SoC H/W 구조 및 기능들을 Boradcom사의 BCM6862X 칩을 예로 알아본다. 이 칩은 최신 arm64를 사용하는 상용 cpu이며, 제공하는 내부 기능은 다음과 같다. (Northstar 2라는 이름을 사용하며 줄여서 ns2이다.)

  • ARM Cortex-A57(arm64) 2 Ghz x 4 CPU core
  • DDR3 및 DDR4용Memory Controller
  • 내장 SRAM 1M
  • NAND/NOR Flash Controller
  • eMMC Controller
  • 버스: SATA 3 x 2, PCIe Gen2 4 lane x 2, USB3 x 2, USB2 x 1
  • 네트워크 Controller
  • 네트워크 Accelation을 목적으로 사용하는 추가 ARM Cortex R7 700Mhz x 2 core
  • 10G x 2, 2.5G x 2, 1G x 1 이더넷 MAC
  • Security 목적의 Secure Boot, TrustZone, …
  • 그 외 다양한 표준 입출력 장치들

 

아래 그림은 위의 칩이 가지고 있는 기능들을 블록도로 표현하였다.

주의: Broadcom 사의 데이터 시트에 나오는 정보들 중 이미 어느 정도 알려진 public한 정보들만을 일부 발췌하여 설명하기 위한 자료로 사용한다. 그러나 저작권상 보호를 받는 이미지들이므로 일부를 가려 사용하였고, 이 들을 복사하여 사용하지 않도록 주의해야 한다.

 

위에서 보여준 그림과 같이 SoC 내부에 있는 다양한 기능들을 SoC 외부에 있는 회로와 연결하기 위해 많은 핀(pin)들이 사용된다. (아래 그림은 특정 칩을 지칭하지 않음)

 

대부분의 임베디드 SoC(System On a Chip)는 수백개의 핀(pin)들을 사용한다. 이 핀들은 한 가지 기능을 위해 사용되는 것들도 있지만, 여러 가지 기능이 한 개의 핀(pin)에 선택적으로 제공되기도 하는데 바로 아래 그림 파란 박스의 기능들이 MFIO(Multi Function I/O) 핀(pin)에 연결된 기능들이다. 업체마다 다양한 표현을 사용한다. (Pin Multiplexing 또는 Pin Mux라고도 한다.)

 

Pin 디스크립터

아래 그림은 SoC에서 사용하는 핀(pin)들 중 일부(좌측 하단) 1/4 만을 보여준다. 노란색 박스로 되어있는 핀들이 MFIO(Multi Function I/O)에 사용되는 핀들이다. W열의 4번째 위치한 박스가 MFIO16이라는 핀 명칭을 가진 것을 확인할 수 있을 것이다.

 

 

이 칩이 제공하는 MFIO 핀들은 아래 표와 같이 총 63개이다. 아래 표에서 Signal이 각 핀의 이름을 의미하며 Ball은 물리적 핀 배치를 의미한다. 예를 들어 MFIO00 핀은 AP열의 2번째에 위치한다.

 

대부분의 pinctrl 드라이버들은 모든 핀에 명칭을 붙여 준비하지 않고, 기능을 선택하여 사용하는 MFIO(Multi Function I/O) 핀에 대해서만 준비하여 사용한다. 벤더들이 pinctrl 드라이버를 제공하기 위해 PIN에 명칭을 부여할 때 다음과 같은 pinctrl_pin_desc 구조체를 사용한다. number에 0번부터 시작하는 순번이 들어가며, name 필드에는 핀 명이 대입된다.

include/linux/pinctrl/pinctrl.h

/**
 * struct pinctrl_pin_desc - boards/machines provide information on their
 * pins, pads or other muxable units in this struct
 * @number: unique pin number from the global pin number space
 * @name: a name for this pin
 * @drv_data: driver-defined per-pin data. pinctrl core does not touch this
 */
struct pinctrl_pin_desc {
        unsigned number;
        const char *name;
        void *drv_data;
};

Generic 사용 예)

핀들이 복수개이므로 구조체를 보통 컴파일 타임에 배열로 생성하는데 이를 편하게 지정하기 위해 PINCTRL_PIN() 매크로를 사용하여 지정한다. 다음 예에서는 pin 명칭에 XY좌표를 사용하여 간단히 주어진 것을 보여준다.

/* Convenience macro to define a single named or anonymous pin descriptor */
#define PINCTRL_PIN(a, b) { .number = a, .name = b 

const struct pinctrl_pin_desc foo_pins[] = {
      PINCTRL_PIN(0, "A8"),
      PINCTRL_PIN(1, "B8"),
      ...
      PINCTRL_PIN(63, "H1"),
};

 

Broadcom rpi2 소스 코드 사용 예

다음 rpi2 드라이버에서 핀 명칭을 지정하는 방식을 알아보자. 핀 명칭을 간단히 gpio0 ~ gpio53까지 부여한 것을 볼 수 있다.

drivers/pinctrl/bcm/pinctrl-bcm2835.c

/* pins are just named GPIO0..GPIO53 */
#define BCM2835_GPIO_PIN(a) PINCTRL_PIN(a, "gpio" #a)
static struct pinctrl_pin_desc bcm2835_gpio_pins[] = {
        BCM2835_GPIO_PIN(0),
        BCM2835_GPIO_PIN(1),
        BCM2835_GPIO_PIN(2),
        ...
        BCM2835_GPIO_PIN(53),
}

 

Broadcom ns2 소스 코드 사용 예

다음 ns2 드라이버에서 핀 명칭을 약간 변형하여 지정하는 방식을 알아보자. pinctrl_pin_desc 구조체를 직접 사용하거나 PINCTRL_PIN() 매크로를 사용하지 않고, 벤더가 선언한 구조체를 사용하여 그냥 정의한 것을 볼 수 있다. 참고로 일부 벤더들은 PINCTRL_PIN() 매크로를 사용하지 않고, foo_PINCTRL_PIN() 매크로 또는 foo_PIN_DESC() 매크로를 별도로 정의하여 사용하기도한다.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

/*
 * Description of a pin in Northstar2
 *
 * @pin: pin number
 * @name: pin name
 * @pin_conf: pin configuration structure
 */
struct ns2_pin {
        unsigned int pin;
        char *name;
        struct ns2_pinconf pin_conf;
};

#define NS2_PIN_DESC(p, n, b, o, s, i, pu, d)   \
{                                               \
        .pin = p,                               \
        .name = n,                              \
        .pin_conf = {                           \
                .base = b,                      \
                .offset = o,                    \
                .src_shift = s,                 \
                .input_en = i,                  \
                .pull_shift = pu,               \
                .drive_shift = d,               \
        }                                       \
}

/*
 * List of pins in Northstar2
 */
static struct ns2_pin ns2_pins[] = {
        NS2_PIN_DESC(0, "mfio_0", -1, 0, 0, 0, 0, 0),
        NS2_PIN_DESC(1, "mfio_1", -1, 0, 0, 0, 0, 0),
        NS2_PIN_DESC(2, "mfio_2", -1, 0, 0, 0, 0, 0),
        NS2_PIN_DESC(3, "mfio_3", -1, 0, 0, 0, 0, 0),
        ...
        NS2_PIN_DESC(116, "usb2_overcurrent", 2, 0x38, 7, 6, 3, 0),
        NS2_PIN_DESC(117, "sata_led1", 2, 0x3c, 15, 14, 11, 8),
        NS2_PIN_DESC(118, "sata_led0", 2, 0x3c, 7, 6, 3, 0),
};

NS2_PIN_DESC() 매크로를 보면 pin 디스크립터에 pin configuration(pinconf)에서 사용할 정보까지 담아 놓았음을 확인할 수 있다.

 

pinctrl_pin_desc 구조체 및 PINCTRL_PIN() 매크로를 사용하지 않고 임시로 구성한 핀 정보들은 probe 시에 호출되는  foo_probe() 함수에서 pinctrl_pin_desc 구조체 배열을 동적으로 할당 한 후 이 곳에 핀 정보를 복사하여 구성하는 방식을 사용한다. 다음 ns2의 probe 함수 예를 확인하자.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

static int ns2_pinmux_probe(struct platform_device *pdev)
{
        struct ns2_pinctrl *pinctrl;
        struct resource *res;
        int i, ret;
        struct pinctrl_pin_desc *pins;
        unsigned int num_pins = ARRAY_SIZE(ns2_pins);

        pinctrl = devm_kzalloc(&pdev->dev, sizeof(*pinctrl), GFP_KERNEL);
        if (!pinctrl)
                return -ENOMEM;
        ...
        pins = devm_kcalloc(&pdev->dev, num_pins, sizeof(*pins), GFP_KERNEL);
        if (!pins)
                return -ENOMEM;

        for (i = 0; i < num_pins; i++) {
                pins[i].number = ns2_pins[i].pin;
                pins[i].name = ns2_pins[i].name;
                pins[i].drv_data = &ns2_pins[i];
        }
        ...
        pinctrl->pctl = pinctrl_register(&ns2_pinctrl_desc, &pdev->dev, pinctrl);
        ...

 

 

PIN 멀티플렉싱 (pinmux)

이렇게 여러 가지 기능을 하나의 pin에 제공하면 SoC 설계 시 핀 수를 줄여 핀이 차지하는 공간을 줄일 수 있는 장점이 있다. 사용자는 회로 설계 시 필요한 기능을 골라서 선택하여 사용할 수 있다. 한 개의 핀에 연결된 기능들은 한 가지만을 선택하여 사용하여야 하고 동시에 사용할 수 없는 단점이 있다. 다음 그림을 보면 NOR Flash 기능과 Nand Flash 기능을 사용할 때 각 기능에 대해 많은 핀들이 사용된다. 최근에는 NOR Flash와 Nand Flash를 동시에 제공하지 않고 선택 적용하는 케이스들이 많이 있다. 이러한 시스템에서 핀을 선택하기 위해 사용하는 방법이 PIN 멀티 플렉싱을 사용한다. 아래 MFIO(Multi Function I/O) 0번 핀에 두 가지 기능이 선택될 수 있는 것을 알 수 있다.

 

다음 표는 MFIO 각 핀들이 사용할 수 있는 기능들을 보여준다. 예를 들어 MFIO 37번 핀은 3가지의 기능을 사용할 수 있고, 각각 GPIO 12번, PCIE A버스의 2번째 lane 또는 Nor 플래시 중 하나에 사용될 수 있다. 즉 H/W 설계 시 PCIe A버스의 2번째 lane을 사용하여야 하는 경우 GPIO의 12번~13번 핀 및 Nor 플래시 등을 이용할 수 없게됨을 알 수 있다.

 

다음 그림은 위의 표를 참조하여 MFIO37번 핀에 연결된 3개의 기능들이 연결되어 있는 모습을 보여준다.

 

이어서 MFIO37번과 MFIO38번이 PCIe 디바이스에 연결된 모습을 보여준다. 이 경우 GPIO_12번과 GPIO_13번 및 NOR 플래시 디바이스를 연결하여 사용할 수 없다.

 

pinmux 설정

pinmux를 통해 function을 설정하는 방법은 다음과 같은 방법들이 있다.

 

SoC 내부 디폴트 설정

MFIO 핀을 어떠한 용도로 사용할지는 SoC가 각 MFIO 핀 별로 부트업시 default 설정이 지정된다. 물론 각 제조사의 칩 데이터 시트를 참고해야 한다

 

부트 스트랩에 의한 설정

H/W 설계 시 bootstrap 설정에 의해 모드가 지정되는 MFIO 핀들도 있다. 이 경우에는 각 제조사의 데이터 시트를 참고하여 처음 모드가 어떻게 되는지 알아볼 수 있다.

 

S/W 설정

pin control 드라이버를 통해 커널 부트업시 각 MFIO 핀의 모드를 mux 레지스터의 조작을 통해 설정할 수 있다. 최근 커널은 당연히 디바이스 트리를 읽어서 자동으로 설정한다. 부트 후에도 커널에서는 pinctrl에 대한 kernel API를 사용하여 pinmux  및 pinconf 설정이 가능하다. 또한 유저 스페이스에서도 모드를 바꿀 수 있는 시스템 파일 경로를 제공한다.

  • 디바이스 트리를 사용하기 전에는 각 아키텍처로 만들어진 머신(보드)에 대한 초기화 코드에 pinmux 및 pinconf에 대한 설정을 해왔다.
  • 디바이스 트리를 사용하면서부터(64비트 디폴트) 디바이스 트리에서 pinmux 및 pinconf에 대한 설정을 할 수 있게 되었다.

 

Pin Configuration (pinconf)

pin configuration은 대부분 GPIO 서브시스템과 같이 사용된다. GPIO를 사용할 때 pinmux로 선택하는지 아니면 항상 동작하는지에 따라 두 가지 유형으로 나뉜다.

  • case A)와 같은 경우 GPIO를 사용하려면 pinmux에 선택되어 사용되어야 한다. 대부분의 경우 pinmux에서 GPIO는 디폴트로 선택되어 있다.
  • case B)와 같은 경우 GPIO가 항상 동작 가능한 상태이다. 뒷 부분에 있는 pinmux에 의해 특정 장치가 선택되어 사용될 때 동시에 연결되어 사용되고 있는 GPIO가 방해를 하지 않도록 조심해야 한다.

 

아래 그림은 rpi2의 gpio 핀 하나를 대략적으로 보여준다. 여기에서 pad의 위치를 확인해본다.

  • 가장 상위의 삼각형 모습이 pad가 출력 모드로 설정되는 경우 동작하는 회로이다.
  • 그다음 삼각형 모습은 pad가 입력 모드로 설정되는 경우 동작하는 회로이다.
  • 아래 MOS-FET가 있는 곳은 pad에 pull-up 및 pull-down 바이어스를 연결하기 위한 회로이다.

다음에서 설정할 수 있는 속성들을 알아보자. 물론 H/W마다 지원하는 속성이 있다. 각각의 속성 지원 여부는 칩제조사의 데이터시트를 확인해야한다. 기존 칩 제조사의 pin controller의 확인은 Document/devicetree/binding/pinctrl 디렉토리에서 확인할 수 있다.

출력 관련
  • output-low
    • 로우 레벨 출력 모드로 설정한다.
  • output-high
    • 하이 레벨 출력 모드로 설정한다.
  •  drive-push-pull
    • 출력을 push & pull 모드로 설정한다.
  • drive-open-drain
    • 출력을 open drain 모드로 설정한다.
  • drive-open-source
    • 출력을 open source 모드로 설정한다.
  • drive-strength
    • 드라이브 출력을 X mA로 설정한다.

 

입력 관련
  • bias-pull-down
    • 입력 핀에 pull-down 바이어스 전류를 공급한다.
  • bias-pull-up
    • 입력 핀에 pull-up 바이어스 전류를 공급한다.
  • bias-high-impedance
    • pull-up도 아니고 pull-down도 아닌 중간 high-impedance 상태가 유지되도록 입력 핀에 바이어스를 공급한다.
  • bias-bus-hold
    • 핀에 입력된 시그널 상태를 기억하여 다음 신호 입력 전까지 기존 상태를 유지한다.
    • 래치와 같이 동작하도록 입력 핀에 약한 피드백 전류를 공급하여 상태를 유지하게 한다.
  •  bias-disable
    • 핀에 바이어스 전류를 공급하지 않는다.
  • bias-pull-pin-default
    • 핀에 디폴트 상태의 바이어스 전류를 공급한다.
  •  input-enable
    • 입력 핀을 enable 한다.
    • 출력 모드일 때에는 영향이 없다
  •  input-schmitt-enable
    • schmitt 트리거 모드를 enable한다.
  •  input-schmitt-disable
    • schmitt 트리거 모드를 disable한다.
  •  input-debounce
    • 디바운스 모드로 동작시키고 디바운스 타임 X를 설정한다.

 

기타
  • power-source
    • 다른 파워 공급(서플라이)을 선택할 수 있다.
  • low-power-enable
    • 저 전력 모드를 enable 한다.
  • low-power-disable
    • 저 전력 모드를 disable 한다.

 

다음 그림에서 보여주는 바이어스 전류 공급 방법이 가장 많이 사용하는 방법들이다.

 

Pin Control 드라이버 구조

 

아래의 그림은 pin control 드라이버와 관련된 구조를 보여준다.

  • 우측 파란 박스가 pinctrl 드라이버 구현부분이다.
  • Pinctrl 서브시스템 코어는 각 클라이언트 디바이스 드라이버 내부에서 API로 요청하는 pinmux/pinconf 설정을 받아들인다.
  • 좌측 아래 회색 박스의 디바이스 트리를 보면 부트업 타임에 pinmux 및 pinconf에 대한 state 매핑을 등록한다.

 

GPIO 드라이버와의 연계

최근의 SoC는 MFIO와 GPIO 기능이 동시에 제공되므로 이러한 기능을 한 개의 드라이버에서 구현하여 제공하기도 한다.

  • 특정 장치들은 pin controller가 사용하는 모든 pin이 gpio와 1:1로 구성되는 경우도 있다. (예: rpi2)

 

Interrupt Controller 드라이버와의 연계

또한 GPIO 내부에 일부 입력핀들이 외부 인터럽터를 수신할 수 있도록 구성된 Interrupt 멀티플렉서 장치들도 많이 구성된다. 따라서 최근에는 Pin Controller + GPIO Controller + Interrupt Controller가 동시에 제공되는 드라이버를 구성하기도 한다. (예: rpi2, exynos, omap, dra7, am437, sunxi, …)

 

pinctrl 드라이버를 작성할 때 pinctrl 디스크립터를 통해 3개의 오퍼레이션을 구현하고, 추가로 GPIO Controller 및 Interrupt Controller도 여러 가지 구현 경우에 따라 추가될 수 있음을 알 수 있다.

 

Pin Controller

pin controller 디바이스 드라이버는 다음과 같이 3개의 오퍼레이션으로 구현된다.

 

디바이스 드라이버의 pinmux/pinconf 요청

커널 부트업시 디바이스 트리로 구성되어 가동되는 여러 기능의 드라이버들 중 일부가 MFIO핀을 이용하는 경우 pinmux와 같이 자신의 기능을 선택하기위해 요청하는 기능이 있다. 또한 핀의 모드를 설정하기 위해 pinconf 요청을 할 수도 있다.

 

 

pinctrl 디스크립터

pinctrl 디스크립터의 정의는 다음과 같이 pinctrl_desc 구조체를 사용하여 정의된다. pin 디스크립터에서 정의한 pin들을 지정하고 있고, 이 핀들을 대상으로 pinctrl 서브시스템용 API가 구현될 수 있도록 3가지의 operation 구조체 후크를 사용한다. 3가지의 오퍼레이션은 다음의 기능을 지원한다.

  • pinctrl_ops
    • 핀 그룹에 소속된 핀들 정보를 제공한다.
  • pinmux_ops
    • 핀에 연결된 기능을 선택한다.
  • pinconf_ops
    • 핀의 속성(pull up, pull down, open drain, strength, …)을 설정한다.

 

다음 그림은 pinctrl 디스크립터와 구현해야 할 3개의 오퍼레이션들을 보여준다.

 

첫 번째 예) rpi2 드라이버

drivers/pinctrl/bcm/pinctrl-bcm2835.c

static struct pinctrl_desc bcm2835_pinctrl_desc = {
        .name = MODULE_NAME,
        .pins = bcm2835_gpio_pins,
        .npins = ARRAY_SIZE(bcm2835_gpio_pins),
        .pctlops = &bcm2835_pctl_ops,
        .pmxops = &bcm2835_pmx_ops,
        .confops = &bcm2835_pinconf_ops,
        .owner = THIS_MODULE,
};

 

두 번째 예) ns2 드라이버 – pins 필드는 probe 함수에서 동적으로 생성되는 pin 디스크립터 정보들을 대입하므로 컴파일 타임에 지정하지 않는다.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

static struct pinctrl_desc ns2_pinctrl_desc = {
        .name = "ns2-pinmux",
        .pctlops = &ns2_pinctrl_ops,
        .pmxops = &ns2_pinmux_ops,
        .confops = &ns2_pinconf_ops,
};

 

1) Pin 그룹 (for pinctrl & pinconf)

pin control을 사용하는 기능들은 1개 이상의 핀들을 사용한다.  각 기능들이 사용하는 MFIO 핀들을 알아본다. 다음 그림에서 8 개의 핀에 대한 그룹 분류를 알아보자.

Generic 사용 예

예를 들어 UART0를 사용하기 위해서 MFIO_39 ~ MFIO_46번까지 8개의 핀이 사용되고, 이를 용도별로 3개의 그룹으로 나누어 표현하면 다음과 같다.

static const unsigned int uart0_modem_pins[] = {39, 40, 41, 42};
static const unsigned int uart0_rts_cts_pins[] = {43, 44};
static const unsigned int uart0_in_out_pins[] = {45, 46};

 

GPIO는 MFIO_24 ~ MFIO_25, MFIO_27 ~ MFIO_56번까지의 32개의 핀이 사용되는데, UART0와 중복되어 사용되는 핀들의 그룹을 정리하면 다음과 같다.

static const unsigned int gpio_14_17_pins[] = {39, 40, 41, 42};
static const unsigned int gpio_18_19_pins[] = {43, 44};
static const unsigned int gpio_20_21_pins[] = {45, 46};

 

마지막으로 NOR 플래시는 MFIO_0 ~ MFIO_42번까지 43개의 핀이 사용되는데, UART0와 중복되어 사용되는 핀 그룹은 다음과 같다.

static const unsigned int nor_addr_12_15_pins[] = {39, 40, 41, 42};

 

핀 그룹들을 관리하는 방법들은 드라이버 개발자마다 조금씩 다르게 구현하지만 기본 형태는 다음과 같다. 다음 코드에서 8개의 MFIO_39 ~ MFIO_46 핀들에 대한 그룹들의 분류를 알아본다.

#include <linux/pinctrl/pinctrl.h>
struct foo_group {
	const char *name;
	const unsigned int *pins;
	const unsigned num_pins;
};

static const struct foo_group foo_groups[] = {
        ...
	{
		.name = "gpio_14_17_grp",
		.pins = gpio_14_17_pins,
		.num_pins = ARRAY_SIZE(gpio_14_17_pins),
	},
	{
		.name = "uart0_modem_grp",
		.pins = uart0_modem_pins,
		.num_pins = ARRAY_SIZE(uart0_modem_pins),
	},
        { 
                .name = "nor_addr_12_15_grp", 
                .pins = nor_addr_12_15_pins, 
                .num_pins = ARRAY_SIZE(nor_addr_12_15_pins), 
        }, 
	{
		.name = "gpio_18_19_grp",
		.pins = gpio_18_19_pins,
		.num_pins = ARRAY_SIZE(gpio_18_19_pins),
	},
	{
		.name = "uart0_rts_cts_grp",
		.pins = uart0_rts_cts_pins,
		.num_pins = ARRAY_SIZE(uart0_rts_cts_pins),
	},
	{
		.name = "gpio_20_21_grp",
		.pins = gpio_20_21_pins,
		.num_pins = ARRAY_SIZE(gpio_20_21_pins),
	},
	{
		.name = "uart0_in_out_grp",
		.pins = uart0_in_out_pins,
		.num_pins = ARRAY_SIZE(uart0_in_out_pins),
	},

        ...
};

 

Broadcom ns2 소스 코드 사용 예

다음은 8개의 MFIO_39_46 핀들에 대한 broadcom ns2의 실제 소스 코드이다.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

/*
 * Group based IOMUX configuration
 *
 * @name: name of the group
 * @pins: array of pins used by this group
 * @num_pins: total number of pins used by this group
 * @mux: Northstar2 group based IOMUX configuration
 */
struct ns2_pin_group {
        const char *name;
        const unsigned int *pins;
        const unsigned int num_pins;
        const struct ns2_mux mux;
};

generic한 foo_group 구조체 형태에서  ns2_mux 구조체가 하나 더 추가되었다.

 

/*
 * Northstar2 IOMUX register description
 *
 * @base: base address number
 * @offset: register offset for mux configuration of a group
 * @shift: bit shift for mux configuration of a group
 * @mask: mask bits
 * @alt: alternate function to set to
 */
struct ns2_mux {
        unsigned int base;
        unsigned int offset;
        unsigned int shift;
        unsigned int mask;
        unsigned int alt;
};

generic foo_group 구조체 형태에 function을 선택하기 위한 Mux 레지스터 주소 및 컨트롤 정보등을 추가하기 위해 ns2_mux 구조체를 사용하였다.

 

#define NS2_PIN_GROUP(group_name, ba, off, sh, ma, al)  \
{                                                       \
        .name = __stringify(group_name) "_grp",         \
        .pins = group_name ## _pins,                    \
        .num_pins = ARRAY_SIZE(group_name ## _pins),    \
        .mux = {                                        \
                .base = ba,                             \
                .offset = off,                          \
                .shift = sh,                            \
                .mask = ma,                             \
                .alt = al,                              \
        }                                               \
}


/*
 * List of Northstar2 pin groups
 */
static const struct ns2_pin_group ns2_pin_groups[] = {
	...
        NS2_PIN_GROUP(gpio_14_17, 0, 4, 18, 3, 0),
        NS2_PIN_GROUP(uart0_modem, 0, 4, 18, 3, 1),
        NS2_PIN_GROUP(nor_addr_12_15, 0, 4, 18, 3, 2),

        NS2_PIN_GROUP(gpio_18_19, 0, 4, 16, 3, 0),
        NS2_PIN_GROUP(uart0_rts_cts, 0, 4, 16, 3, 1),

        NS2_PIN_GROUP(gpio_20_21, 0, 4, 14, 3, 0),
        NS2_PIN_GROUP(uart0_in_out, 0, 4, 14, 3, 1),
	...
};

generic foo_groups[] 배열 을 구성하는데 <name>_grp, <name>_pins, 핀 수 및 mux 정보(base, offset, shift, mask, alt) 등을 컴파일 타임에 정의한다.

 

Pin 그룹 오퍼레이션 구현

정의된 핀 그룹에 대한 조회등을 할 수 있는 pinctrl 오퍼레이션을 구현하는 방법을 알아보자.

 

Generic 사용 예

static int foo_get_groups_count(struct pinctrl_dev *pctldev)
{
	return ARRAY_SIZE(foo_groups);
}

static const char *foo_get_group_name(struct pinctrl_dev *pctldev,
				       unsigned selector)
{
	return foo_groups[selector].name;
}

static int foo_get_group_pins(struct pinctrl_dev *pctldev, unsigned selector,
			       const unsigned **pins,
			       unsigned *num_pins)
{
	*pins = (unsigned *) foo_groups[selector].pins;
	*num_pins = foo_groups[selector].num_pins;
	return 0;
}

static struct pinctrl_ops foo_pctrl_ops = {
	.get_groups_count = foo_get_groups_count,
	.get_group_name = foo_get_group_name,
	.get_group_pins = foo_get_group_pins,
};

static struct pinctrl_desc foo_desc = {
       ...
       .pctlops = &foo_pctrl_ops,
};

핀 디스크립터에는 3가지의 operations가 지정되는데 첫 번째로 핀 컨트롤러의 핀 그룹 정보를 담당하는 pctlops에 해당하는 operation에 다음 3개의 함수를 구현한다.

  • (*get_groups_count)
    • 등록된 그룹들의 수를 반환한다.
  • (*get_group_name)
    • selector를 인덱스로 그룹명을 반환한다.
  • (*get_group_pins)
    • selector를 인덱스로 그룹을 찾아 출력 인수 pins에 핀 목록. 그리고 출력 인수 num_pins에 핀 수를 반환한다.
    • 예) selector=0 (gpio_pin_14_17을 찾은 것으로 하자)
      • -> pin={ 14, 15, 16, 17 }, num_pins=4

 

Broadcom ns2 소스 코드 사용 예

다음 코드는 broadcom ns2의 실제 소스 코드이다.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

static int ns2_get_groups_count(struct pinctrl_dev *pctrl_dev)
{
        struct ns2_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);

        return pinctrl->num_groups;
}

static const char *ns2_get_group_name(struct pinctrl_dev *pctrl_dev,
                                      unsigned int selector)
{
        struct ns2_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);

        return pinctrl->groups[selector].name;
}

static int ns2_get_group_pins(struct pinctrl_dev *pctrl_dev,
                              unsigned int selector, const unsigned int **pins,
                              unsigned int *num_pins)
{
        struct ns2_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);

        *pins = pinctrl->groups[selector].pins;
        *num_pins = pinctrl->groups[selector].num_pins;

        return 0;
}

static void ns2_pin_dbg_show(struct pinctrl_dev *pctrl_dev,
                             struct seq_file *s, unsigned int offset)
{
        seq_printf(s, " %s", dev_name(pctrl_dev->dev));
}

static const struct pinctrl_ops ns2_pinctrl_ops = {
        .get_groups_count = ns2_get_groups_count,
        .get_group_name = ns2_get_group_name,
        .get_group_pins = ns2_get_group_pins,
        .pin_dbg_show = ns2_pin_dbg_show,
        .dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
        .dt_free_map = pinctrl_utils_free_map,
};

static struct pinctrl_desc ns2_pinctrl_desc = {
        .name = "ns2-pinmux",
        .pctlops = &ns2_pinctrl_ops,
        ...
};

generic 사용 예보다 3개의 함수가 추가로 구현되어 있음을 알 수 있다.

  • (*pin_dbg_show)
    • debugfs(CONFIG_DEBUG_FS 커널 옵션 사용)를 사용하는 경우 pin control subsystem core를 통하여 pin 정보를 출력할 수 있다.
    • 예) $ cat /sys/kernel/debug/pins
    • 호출 경로:
      • &pinctrl_pins_ops.(*open) ->
      • pinctrl_pins_open() ->
      • single_open(file, pinctrl_pins_show, …) ->
      • pinctrl_pins_show() ->
      • pctldev->desc->pctlops->(*pin_dbg_show)
  • (*dt_node_to_map)
    • 디바이스 트리를 통해서 그룹에 pinmux, 또는 핀/그룹에 pinconf 설정을 하도록 매핑한다.
    • 호출 경로:
      • pinctrl_register() ->
      • pinctrl_get() ->
      • create_pinctrl() ->
      • pinctrl_dt_to_map() ->
      • dt_to_map_one_config() ->
      • pctldev->desc->pctlops->(*dt_node_to_map)
  • (*dt_free_map)
    • 디바이스 트리를 통해서 매핑한 설정을 해제한다.
    • 호출 경로:
      • pinctrl_unregister() ->
      • pinctrl_put() ->
      • pinctrl_release() ->
      • pinctrl_free() ->
      • pinctrl_dt_free_maps() ->
      • dt_free_map() ->
      • dt_to_map_one_config() ->
      • pctldev->desc->pctlops->(*dt_node_to_map)

 

2) Pin Function (for pinmux)

그룹화된 핀들을 기능(Function)으로 묶는 것을 알아본다.

 

Generic 사용 예

예를 들어 UART0를 사용하기 위해서는 uart0_modem_grp, uart0_rts_cts_grp, uart0_in_out_grp이 필요하다. 다음 코드 예를 보고 uar0_groups가 어떻게 구성이 되었는지 확인해본다.

static const char * const gpio_groups[] = { "gpio_14_17_grp", "gpio_18_19_grp", "gpio_20_21_grp" };
static const char * const uart0_groups[] = { "uart0_modem_grp", "uart0_rts_cts_grp", "uart0_in_out_grp" };
static const char * const nor_groups[] = { "nor_addr_12_15_grp" };

static const struct foo_pmx_func foo_functions[] = {
        ...
	{
		.name = "gpio",
		.groups = gpio_groups,
		.num_groups = ARRAY_SIZE(gpio_groups),
	},
	{
		.name = "uart0",
		.groups = uart0_groups,
		.num_groups = ARRAY_SIZE(uart0_groups),
	},
	{
		.name = "nor",
		.groups = nor_groups,
		.num_groups = ARRAY_SIZE(nor_groups),
	},
        ...
};

 

Broadcom ns2 소스 코드 사용 예

다음 코드는 broadcom ns2의 실제 코드이다.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

/*
 * List of groups supported by functions
 */

static const char * const nand_grps[] = {"nand_grp"};

static const char * const nor_grps[] = {"nor_data_grp", "nor_adv_grp",
        "nor_addr_0_3_grp", "nor_addr_4_5_grp", "nor_addr_6_7_grp",
        "nor_addr_8_9_grp", "nor_addr_10_11_grp", "nor_addr_12_15_grp"};

static const char * const gpio_grps[] = {"gpio_0_1_grp", "gpio_2_5_grp",
        "gpio_6_7_grp", "gpio_8_9_grp", "gpio_10_11_grp", "gpio_12_13_grp",
        "gpio_14_17_grp", "gpio_18_19_grp", "gpio_20_21_grp", "gpio_22_23_grp",
        "gpio_24_25_grp", "gpio_26_27_grp", "gpio_28_29_grp",
        "gpio_30_31_grp"};

static const char * const pcie_grps[] = {"pcie_ab1_clk_wak_grp",
        "pcie_a3_clk_wak_grp", "pcie_b3_clk_wak_grp", "pcie_b2_clk_wak_grp",
        "pcie_a2_clk_wak_grp"};

static const char * const uart0_grps[] = {"uart0_modem_grp",
        "uart0_rts_cts_grp", "uart0_in_out_grp"};

static const char * const uart1_grps[] = {"uart1_ext_clk_grp",
        "uart1_dcd_dsr_grp", "uart1_ri_dtr_grp", "uart1_rts_cts_grp",
        "uart1_in_out_grp"};

static const char * const uart2_grps[] = {"uart2_rts_cts_grp"};

static const char * const pwm_grps[] = {"pwm_0_grp", "pwm_1_grp",
        "pwm_2_grp", "pwm_3_grp"};

#define NS2_PIN_FUNCTION(func)                          \
{                                                       \
        .name = #func,                                  \
        .groups = func ## _grps,                        \
        .num_groups = ARRAY_SIZE(func ## _grps),        \
}

/*
 * List of supported functions
 */
static const struct ns2_pin_function ns2_pin_functions[] = {
        NS2_PIN_FUNCTION(nand),
        NS2_PIN_FUNCTION(nor),
        NS2_PIN_FUNCTION(gpio),
        NS2_PIN_FUNCTION(pcie),
        NS2_PIN_FUNCTION(uart0),
        NS2_PIN_FUNCTION(uart1),
        NS2_PIN_FUNCTION(uart2),
        NS2_PIN_FUNCTION(pwm),
};

 

 

Pin Mux 오퍼레이션 구현

 

Generic 사용 예

정의된 핀 Mux에 대한 오퍼레이션을 구현하는 방법은 다음과 같다.

static int foo_get_functions_count(struct pinctrl_dev *pctldev)
{
	return ARRAY_SIZE(foo_functions);
}

static const char *foo_get_fname(struct pinctrl_dev *pctldev, unsigned selector)
{
	return foo_functions[selector].name;
}

static int foo_get_groups(struct pinctrl_dev *pctldev, unsigned selector,
			  const char * const **groups,
			  unsigned * const num_groups)
{
	*groups = foo_functions[selector].groups;
	*num_groups = foo_functions[selector].num_groups;
	return 0;
}

static int foo_set_mux(struct pinctrl_dev *pctldev, unsigned selector,
		unsigned group)
{
	u8 regbit = (1 << selector + group);

	writeb((readb(MUX)|regbit), MUX)
	return 0;
}

static struct pinmux_ops foo_pmxops = {
	.get_functions_count = foo_get_functions_count,
	.get_function_name = foo_get_fname,
	.get_function_groups = foo_get_groups,
	.set_mux = foo_set_mux,
	.strict = true,
};

/* Pinmux operations are handled by some pin controller */
static struct pinctrl_desc foo_desc = {
	...
	.pctlops = &foo_pctrl_ops,
	.pmxops = &foo_pmxops,
};

pinmux를 동작시키는데 필요한 pinmux_ops 구조체에 사용하는 다음 함수들을 구현한다.

  • (*get_functions_count)
    • 등록된 펑션들의 수를 반환한다.
  • (*get_function_name)
    • selector를 인덱스로 펑션명을 반환한다.
  • (*get_function_groups)
    • selector를 인덱스로 펑션을 찾아 해당 펑션에 속한 그룹들 목록을 출력 인수 groups에 대입한다. 그리고 그리고 출력 인수 num_groups에는 그룹 수를 반환한다.
    • 예) selector=4 (uart0 펑션을 찾은 것으로 하자)
      • -> groups={ “uart0_modem_grp”, “uart0_rts_cts_grp”, “uart0_in_out_grp” }, num_groups=3
  • (*set_mux)
    • selector를 인덱스로 펑션을 찾고 지정한 그룹을 선택한다.
    • 예) selector=4 (uart0 펑션을 찾은 것으로 하자), group=1
      • -> pinmux 레지스터를 사용하여 “uart0” 펑션에 소속한 “uart0_rts_cts_grp” 핀들을 선택하게 한다.

 

Broadcom ns2 소스 코드 사용 예

다음 코드는 broadcom ns2의 실제 소스 코드이다.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

static int ns2_get_functions_count(struct pinctrl_dev *pctrl_dev)
{
        struct ns2_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);

        return pinctrl->num_functions;
}

static const char *ns2_get_function_name(struct pinctrl_dev *pctrl_dev,
                                         unsigned int selector)
{
        struct ns2_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);

        return pinctrl->functions[selector].name;
}

static int ns2_get_function_groups(struct pinctrl_dev *pctrl_dev,
                                   unsigned int selector,
                                   const char * const **groups,
                                   unsigned int * const num_groups)
{
        struct ns2_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);

        *groups = pinctrl->functions[selector].groups;
        *num_groups = pinctrl->functions[selector].num_groups;

        return 0;
}

static int ns2_pinmux_enable(struct pinctrl_dev *pctrl_dev,
                             unsigned int func_select, unsigned int grp_select)
{
        struct ns2_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);
        const struct ns2_pin_function *func;
        const struct ns2_pin_group *grp;

        if (grp_select > pinctrl->num_groups ||
                func_select > pinctrl->num_functions)
                return -EINVAL;

        func = &pinctrl->functions[func_select];
        grp = &pinctrl->groups[grp_select];

        dev_dbg(pctrl_dev->dev, "func:%u name:%s grp:%u name:%s\n",
                func_select, func->name, grp_select, grp->name);

        dev_dbg(pctrl_dev->dev, "offset:0x%08x shift:%u alt:%u\n",
                grp->mux.offset, grp->mux.shift, grp->mux.alt);

        return ns2_pinmux_set(pinctrl, func, grp, pinctrl->mux_log);
}

static const struct pinmux_ops ns2_pinmux_ops = {
        .get_functions_count = ns2_get_functions_count,
        .get_function_name = ns2_get_function_name,
        .get_function_groups = ns2_get_function_groups,
        .set_mux = ns2_pinmux_enable,
};

static struct pinctrl_desc ns2_pinctrl_desc = {
        .name = "ns2-pinmux",
        .pmxops = &ns2_pinmux_ops,
        ...
};

generic 코드 예와 거의 동일하다.

 

다음 아래 코드에 있는 함수는 ns2_pinmux_enable() 함수에서 호출하여 사용하는 함수로 실제 broadcom ns2의 pin controller(MFIO 레지스터)를 이용하여 pin에 대한 function을 설정한다.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

static int ns2_pinmux_set(struct ns2_pinctrl *pinctrl,
                          const struct ns2_pin_function *func,
                          const struct ns2_pin_group *grp,
                          struct ns2_mux_log *mux_log)
{
        const struct ns2_mux *mux = &grp->mux;
        int i;
        u32 val, mask;
        unsigned long flags;
        void __iomem *base_address;

        for (i = 0; i < NS2_NUM_IOMUX; i++) {
                if ((mux->shift != mux_log[i].mux.shift) ||
                        (mux->base != mux_log[i].mux.base) ||
                        (mux->offset != mux_log[i].mux.offset))
                        continue;

                /* if this is a new configuration, just do it! */
                if (!mux_log[i].is_configured)
                        break;

                /*
                 * IOMUX has been configured previously and one is trying to
                 * configure it to a different function
                 */
                if (mux_log[i].mux.alt != mux->alt) {
                        dev_err(pinctrl->dev,
                                "double configuration error detected!\n");
                        dev_err(pinctrl->dev, "func:%s grp:%s\n",
                                func->name, grp->name);
                        return -EINVAL;
                }

                return 0;
        }
        if (i == NS2_NUM_IOMUX)
                return -EINVAL;

        mask = mux->mask;
        mux_log[i].mux.alt = mux->alt;
        mux_log[i].is_configured = true;

        switch (mux->base) {
        case NS2_PIN_MUX_BASE0:
                base_address = pinctrl->base0;
                break;

        case NS2_PIN_MUX_BASE1:
                base_address = pinctrl->base1;
                break;

        default:
                return -EINVAL;
        }

        spin_lock_irqsave(&pinctrl->lock, flags);
        val = readl(base_address + grp->mux.offset);
        val &= ~(mask << grp->mux.shift);
        val |= grp->mux.alt << grp->mux.shift;
        writel(val, (base_address + grp->mux.offset));
        spin_unlock_irqrestore(&pinctrl->lock, flags);

        return 0;
}

ns2의 pinmux 레지스터를 사용하여 요청한 펑션 인덱스와 그룹 인덱스에 해당하는 핀에 대해  선택하게 한다. (소스 설명 생략)

 

3) Pin Configuration (for pinconf)

 

Pin Configuration 오퍼레이션 구현

핀 또는 핀 그룹에 대한 설정을 하거나 조회할 수 있도록 pinconf 오퍼레이션의 구현에 대해 알아본다.

 

Generic 사용 예

#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinconf.h>
#include "platform_x_pindefs.h"

static int foo_pin_config_get(struct pinctrl_dev *pctldev,
		    unsigned offset,
		    unsigned long *config)
{
	struct my_conftype conf;

	... Find setting for pin @ offset ...

	*config = (unsigned long) conf;
}

static int foo_pin_config_set(struct pinctrl_dev *pctldev,
		    unsigned offset,
		    unsigned long config)
{
	struct my_conftype *conf = (struct my_conftype *) config;

	switch (conf) {
		case PLATFORM_X_PULL_UP:
		...
		}
	}
}

static int foo_pin_config_group_get (struct pinctrl_dev *pctldev,
		    unsigned selector,
		    unsigned long *config)
{
	...
}

static int foo_pin_config_group_set (struct pinctrl_dev *pctldev,
		    unsigned selector,
		    unsigned long config)
{
	...
}

static struct pinconf_ops foo_pconf_ops = {
	.pin_config_get = foo_pin_config_get,
	.pin_config_set = foo_pin_config_set,
	.pin_config_group_get = foo_pin_config_group_get,
	.pin_config_group_set = foo_pin_config_group_set,
};

/* Pin config operations are handled by some pin controller */
static struct pinctrl_desc foo_desc = {
	...
	.confops = &foo_pconf_ops,
};

pinconf를 동작시키는데 필요한 pinconf_ops 구조체에 사용하는 다음 함수들을 구현한다.

  • (*pin_config_get)
    • 핀에 인자로 지정한 pinconf 설정이 있는 경우 그 값을 알아온다.
  • (*pin_config_set)
    • 핀에 pinconf 설정을 한다.
  • (*pin_config_group_get)
    • 그룹에 등록된 pinconf 설정이 있는 경우 그 값을 알아온다.
  • (*pin_config_group_set)
    • 그룹에 속한 핀들에 pinconf 설정을 한다.

 

Broadcom ns2 소스 코드 사용 예

다음 코드는 broadcom ns2의 실제 코드이다. 현재 그룹에 대한 구현은 보이지 않고 핀에 대한 구현만 있음을 알 수 있다.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

static int ns2_pin_config_get(struct pinctrl_dev *pctldev, unsigned int pin,
                              unsigned long *config)
{
        struct ns2_pin *pin_data = pctldev->desc->pins[pin].drv_data;
        enum pin_config_param param = pinconf_to_config_param(*config);
        bool pull_up, pull_down;
        u16 arg = 0;
        int ret;

        if (pin_data->pin_conf.base == -1)
                return -ENOTSUPP;

        switch (param) {
        case PIN_CONFIG_BIAS_DISABLE:
                ns2_pin_get_pull(pctldev, pin, &pull_up, &pull_down);
                if ((pull_up == false) && (pull_down == false))
                        return 0;
                else
                        return -EINVAL;

        case PIN_CONFIG_BIAS_PULL_UP:
             ...

        case PIN_CONFIG_BIAS_PULL_DOWN:
             ...

        case PIN_CONFIG_DRIVE_STRENGTH:
             ...

        case PIN_CONFIG_SLEW_RATE:
             ...

        case PIN_CONFIG_INPUT_ENABLE:
             ...

        default:
                return -ENOTSUPP;
        }
}

static int ns2_pin_config_set(struct pinctrl_dev *pctrldev, unsigned int pin,
                              unsigned long *configs, unsigned int num_configs)
{
        struct ns2_pin *pin_data = pctrldev->desc->pins[pin].drv_data;
        enum pin_config_param param;
        unsigned int i;
        u16 arg;
        int ret = -ENOTSUPP;

        if (pin_data->pin_conf.base == -1)
                return -ENOTSUPP;

        for (i = 0; i < num_configs; i++) {
                param = pinconf_to_config_param(configs[i]);
                arg = pinconf_to_config_argument(configs[i]);

                switch (param) {
                case PIN_CONFIG_BIAS_DISABLE:
                        ret = ns2_pin_set_pull(pctrldev, pin, false, false);
                        if (ret < 0)
                                goto out;
                        break;

                case PIN_CONFIG_BIAS_PULL_UP:
                     ...

                case PIN_CONFIG_BIAS_PULL_DOWN:
                     ...

                case PIN_CONFIG_DRIVE_STRENGTH:
                     ...

                case PIN_CONFIG_SLEW_RATE:
                     ...

                case PIN_CONFIG_INPUT_ENABLE:
                     ...

                default:
                        dev_err(pctrldev->dev, "invalid configuration\n");
                        return -ENOTSUPP;
                }
        }
out:
        return ret;
}

static const struct pinconf_ops ns2_pinconf_ops = {
        .is_generic = true,
        .pin_config_get = ns2_pin_config_get,
        .pin_config_set = ns2_pin_config_set,
};

static struct pinctrl_desc ns2_pinctrl_desc = {
        .name = "ns2-pinmux",
        .confops = &ns2_pinconf_ops,
        ...
};

위 코드의 ns2_pin_config_get() 함수에서 PIN_CONFIG_BIAS_DISABLE 항목이 설정되었는지를 알아보기 위해 pull 상태가 모두 다운인 경우에 한해서만 성공(0)을 반환함을 알 수 있다. 핀에 대해 상태를 알아오기 위해 다음의 명령에 따른 함수 호출 경로를 알아본다.

$ cat /sys/kernel/debug/pinctrl/pinconf-pins

  • &pinconf_pins_ops 파일 오퍼레이션의 (*open) 후크에 등록된 함수 호출 ->
  • pinconf_pins_open() ->
  • single_open()을 통해 pinconf_pins_show() 호출 ->
  • 등록된 핀 수만큼 루프
    • pinconf_dump_pin() 호출 ->
    • pinconf_pins_show() ->
    • pinconf_generic_dump_pins() ->
    • pinconf_generic_dump_one() ->
    • 설정 항목 만큼 루프(PIN_CONFIG_BIAS_BUS_HOLD(0) 부터 ~ PIN_CONFIG_SLEW_RATE 까지)
      • pin_config_get_for_pin() ->
      • pinconf_ops 오퍼레이션의 (*pin_config_get) 호크에 등록된 함수 호출 ->
      • ns2의 경우: ns2_pin_config_get()

 

ns2의 경우 실제 사용할 수 있는 pinconf 항목들은 다음과 같다.

  • bias-pull-disable
  • bias-pull-up
  • bias-pull-down
  • drive-strength
  • slew-rate
  • input-enable

 

다음 아래 코드에 있는 함수는 ns2_pin_config_get() 및 ns2_pin_config_set() 함수에서 호출하여 사용하는 함수로 실제 broadcom ns2의 pin controller(MFIO 레지스터)를 이용하여 pin에 대한 pull 상태를 확인하거나 pull 상태를 설정한다.

drivers/pinctrl/bcm/pinctrl-ns2-mux.c

static void ns2_pin_get_pull(struct pinctrl_dev *pctrldev,
                             unsigned int pin, bool *pull_up,
                             bool *pull_down)
{
        struct ns2_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrldev);
        struct ns2_pin *pin_data = pctrldev->desc->pins[pin].drv_data;
        unsigned long flags;
        u32 val;

        spin_lock_irqsave(&pinctrl->lock, flags);
        val = readl(pinctrl->pinconf_base + pin_data->pin_conf.offset);
        val = (val >> pin_data->pin_conf.pull_shift) & NS2_PIN_PULL_MASK;
        *pull_up = false;
        *pull_down = false;

        if (val == NS2_PIN_PULL_UP)
                *pull_up = true;

        if (val == NS2_PIN_PULL_DOWN)
                *pull_down = true;
        spin_unlock_irqrestore(&pinctrl->lock, flags);
}


static int ns2_pin_set_pull(struct pinctrl_dev *pctrldev, unsigned int pin,
                            bool pull_up, bool pull_down)
{
        struct ns2_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrldev);
        struct ns2_pin *pin_data = pctrldev->desc->pins[pin].drv_data;
        unsigned long flags;
        u32 val;
        void __iomem *base_address;

        base_address = pinctrl->pinconf_base;
        spin_lock_irqsave(&pinctrl->lock, flags);
        val = readl(base_address + pin_data->pin_conf.offset);
        val &= ~(NS2_PIN_PULL_MASK << pin_data->pin_conf.pull_shift);

        if (pull_up == true)
                val |= NS2_PIN_PULL_UP << pin_data->pin_conf.pull_shift;
        if (pull_down == true)
                val |= NS2_PIN_PULL_DOWN << pin_data->pin_conf.pull_shift;
        writel(val, (base_address + pin_data->pin_conf.offset));
        spin_unlock_irqrestore(&pinctrl->lock, flags);

        dev_dbg(pctrldev->dev, "pin:%u set pullup:%d pulldown: %d\n",
                pin, pull_up, pull_down);
        return 0;
}

ns2에서 위의 pull 상태 설정 이외에도 다음과 같은 설정 함수가 사용되고 있다.

  • ns2_pin_set_strength()
  • ns2_pin_set_slew()
  • ns2_pin_set_enable()

 

Pinmux & Pinconf 매핑

pinmux 및 pinconf 등을 사용하기 위해 매핑 배열에 선언한 후 등록할 수 있다. 다음 두 개의 예에서는 “default” 이름을 가진 단일 state 만을 사용하였다.

 

Generic 사용 예 (deprecated)

매핑을 다음과 같이 매핑 배열에 선언한다.

  • name에 “default”를 사용하는 경우 1개의 default state 만으로 구성한다.
  • name에 여러 가지 이름을 사용하는 경우 등록한 이름으로 찾아서 수행시킬 수 있다.
#include <linux/pinctrl/machine.h>

static const struct pinctrl_map mapping[] __initconst = {
	{
		.dev_name = "foo-spi.0",
		.name = PINCTRL_STATE_DEFAULT,
		.type = PIN_MAP_TYPE_MUX_GROUP,
		.ctrl_dev_name = "pinctrl-foo",
		.data.mux.function = "spi0",
	},
	{
		.dev_name = "foo-i2c.0",
		.name = PINCTRL_STATE_DEFAULT,
		.type = PIN_MAP_TYPE_MUX_GROUP,
		.ctrl_dev_name = "pinctrl-foo",
		.data.mux.function = "i2c0",
	},
	{
		.dev_name = "foo-mmc.0",
		.name = PINCTRL_STATE_DEFAULT,
		.type = PIN_MAP_TYPE_MUX_GROUP,
		.ctrl_dev_name = "pinctrl-foo",
		.data.mux.function = "mmc0",
	},
};

 

또는 다음과 같이 PIN_MAP_MUX_GROUP() 매크로 함수를 사용하여 더 간단히 매핑을 만들 수도 있다.

#define PIN_MAP_MUX_GROUP(dev, state, pinctrl, grp, func)      \
        {                                                      \
                .dev_name = dev,                               \
                .name = state,                                 \
                .type = PIN_MAP_TYPE_MUX_GROUP,                \
                .ctrl_dev_name = pinctrl,                      \
                .data.mux = {                                  \
                .group = grp,                                  \
                .function = func,                              \
         },                                                    \
 }

static struct pinctrl_map mapping[] __initdata = {
	PIN_MAP_MUX_GROUP("foo-i2c.o", PINCTRL_STATE_DEFAULT, "pinctrl-foo", NULL, "i2c0"),
};

 

static unsigned long i2c_grp_configs[] = {
	FOO_PIN_DRIVEN,
	FOO_PIN_PULLUP,
};

static unsigned long i2c_pin_configs[] = {
	FOO_OPEN_COLLECTOR,
	FOO_SLEW_RATE_SLOW,
};

#define PIN_MAP_CONFIGS_GROUP(dev, state, pinctrl, grp, cfgs)           \
        {                                                               \
                .dev_name = dev,                                        \
                .name = state,                                          \
                .type = PIN_MAP_TYPE_CONFIGS_GROUP,                     \
                .ctrl_dev_name = pinctrl,                               \
                .data.configs = {                                       \
                        .group_or_pin = grp,                            \
                        .configs = cfgs,                                \
                        .num_configs = ARRAY_SIZE(cfgs),                \
                },                                                      \
        }

#define PIN_MAP_CONFIGS_PIN(dev, state, pinctrl, pin, cfgs)             \
        {                                                               \
                .dev_name = dev,                                        \
                .name = state,                                          \
                .type = PIN_MAP_TYPE_CONFIGS_PIN,                       \
                .ctrl_dev_name = pinctrl,                               \
                .data.configs = {                                       \
                        .group_or_pin = pin,                            \
                        .configs = cfgs,                                \
                        .num_configs = ARRAY_SIZE(cfgs),                \
                },                                                      \
        }

static struct pinctrl_map mapping[] __initdata = {
	PIN_MAP_MUX_GROUP("foo-i2c.0", PINCTRL_STATE_DEFAULT, "pinctrl-foo", "i2c0", "i2c0"),
	PIN_MAP_CONFIGS_GROUP("foo-i2c.0", PINCTRL_STATE_DEFAULT, "pinctrl-foo", "i2c0", i2c_grp_configs),
	PIN_MAP_CONFIGS_PIN("foo-i2c.0", PINCTRL_STATE_DEFAULT, "pinctrl-foo", "i2c0scl", i2c_pin_configs),
	PIN_MAP_CONFIGS_PIN("foo-i2c.0", PINCTRL_STATE_DEFAULT, "pinctrl-foo", "i2c0sda", i2c_pin_configs),
};

 

위에서 정의한 스테이트 별 pinmux/pinconf 매핑들을 등록할 때 다음 API를 사용한다.

.       ret = pinctrl_register_mappings(mapping, ARRAY_SIZE(mapping));

 

“default” 스테이트에 등록된 pinmux/pinconf 매핑을 찾아서 수행하려면 다음과 같이한다.

.       p = devm_pinctrl_get(dev);
	s = pinctrl_lookup_state(p, PINCTRL_STATE_DEFAULT);
	ret = pinctrl_select_state(p, s);

 

한 명령으로 더 간단히 수행하는 방법은 다음과 같다.

.	p = devm_pinctrl_get_select(dev, PINCTRL_STATE_DEFAULT);

 

디바이스 트리

최근에는 디바이스 드라이버 내부에서는 매핑을 미리 만들지 않고 디바이스 트리를 통해 매핑을 간단히 등록할 수 있게 하였다.

 

 

Pin Control APIs

  • pinctrl_get()
    • 요청한 디바이스에 해당하는 pinctrl 구조체를 알아오고자 할 때 사용한다.
    • 최근에는 디바이스 리소스 매니지먼트를 위해 devm_pinctrl_get() 함수를 사용한다.
  • pinctrl_put()
    • pinctrl_get()과 페어로 해제 시 사용한다.
    • 최근에는 디바이스 리소스 매니지먼트를 위해 devm_pinctrl_put() 함수를 사용한다.
  • pinctrl_lookup_state()
    • 클라이언트 디바이스를 위해 요청한 pinctrl과 state 이름으로 pinctrl_state를 찾아온다.
  • pinctrl_select_state()
    • state에 등록된 pinmux 또는 pinconf 매핑들을 HW에 적용한다.
  • gpio_request()
    • 클라이언트 디바이스 등에서 지정한 핀 번호의 gpio를 사용할 수 있다.
    • 최근에는 디바이스 리소스 매니지먼트를 위해 devm_gpio_request() 함수를 사용한다.

 

Debugfs 활용

CONFIG_DEBUG_FS 커널 옵션을 사용하는 경우 debugfs 가상 파일 시스템을 마운트하여 사용할 수 있다.

  • 보통 /sys/kernel/debug에 루트 계정으로 접근하여 사용할 수 있다.

 

rpi2 예)

“pinctrl-bcm28356″라는 이름의 pin controller 드라이버가 pinmux와 pinconf 둘 다 지원함을 알 수 있다.

# cd /sys/fs/debug/pinctrl
# ls -la
total 0
drwxr-xr-x  3 root root 0 Jan  1  1970 .
drwx------ 21 root root 0 Jan  1  1970 ..
drwxr-xr-x  2 root root 0 Jan  1  1970 3f200000.gpio
-r--r--r--  1 root root 0 Jan  1  1970 pinctrl-devices
-r--r--r--  1 root root 0 Jan  1  1970 pinctrl-handles
-r--r--r--  1 root root 0 Jan  1  1970 pinctrl-maps
# cat pinctrl-devices 
name [pinmux] [pinconf]
pinctrl-bcm2835 yes yes

 

다음 명령을 통해 매핑들을 리스트 형태로 조회한다.

  • 3f202000.sdhost 라는 이름의 클라이언트 디바이스
    • default state에 등록한 6개의 매핑들
      • pinmux: 48 ~ 54번 핀이 alt0 펑션을 사용
# cat pinctrl-handles 
Requested pin control handlers their pinmux maps:
device: 3f200000.gpio current state: none
device: 3f202000.sdhost current state: default
 state: default
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio48 (48) function: alt0 (4)
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio49 (49) function: alt0 (4)
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio50 (50) function: alt0 (4)
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio51 (51) function: alt0 (4)
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio52 (52) function: alt0 (4)
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio53 (53) function: alt0 (4)
device: 3f300000.mmc current state: default
 state: default
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio34 (34) function: alt3 (7)
 type: CONFIGS_PIN controller pinctrl-bcm2835 pin gpio34 (34)config 00000000
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio35 (35) function: alt3 (7)
 type: CONFIGS_PIN controller pinctrl-bcm2835 pin gpio35 (35)config 00000002
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio36 (36) function: alt3 (7)
 type: CONFIGS_PIN controller pinctrl-bcm2835 pin gpio36 (36)config 00000002
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio37 (37) function: alt3 (7)
 type: CONFIGS_PIN controller pinctrl-bcm2835 pin gpio37 (37)config 00000002
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio38 (38) function: alt3 (7)
 type: CONFIGS_PIN controller pinctrl-bcm2835 pin gpio38 (38)config 00000002
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio39 (39) function: alt3 (7)
 type: CONFIGS_PIN controller pinctrl-bcm2835 pin gpio39 (39)config 00000002
device: 3f201000.uart current state: default
 state: default
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio32 (32) function: alt3 (7)
 type: CONFIGS_PIN controller pinctrl-bcm2835 pin gpio32 (32)config 00000000
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio33 (33) function: alt3 (7)
 type: CONFIGS_PIN controller pinctrl-bcm2835 pin gpio33 (33)config 00000002
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio43 (43) function: alt0 (4)
 type: CONFIGS_PIN controller pinctrl-bcm2835 pin gpio43 (43)config 00000000
device: 3f804000.i2c current state: default
 state: default
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio2 (2) function: alt0 (4)
 type: MUX_GROUP controller pinctrl-bcm2835 group: gpio3 (3) function: alt0 (4)

 

다음 명령을 통해 매핑들을 더욱 자세하게 조회한다.

# cat pinctrl-maps 
Pinctrl maps:
Pinctrl maps:
device 3f202000.sdhost
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio48
function alt0

device 3f202000.sdhost
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio49
function alt0

device 3f202000.sdhost
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio50
function alt0

device 3f202000.sdhost
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio51
function alt0

device 3f202000.sdhost
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio52
function alt0

device 3f202000.sdhost
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio53
function alt0

device 3f300000.mmc
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio34
function alt3

device 3f300000.mmc
state default
type CONFIGS_PIN (3)
controlling device 3f200000.gpio
pin gpio34
config 00000000

device 3f300000.mmc
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio35
function alt3

device 3f300000.mmc
state default
type CONFIGS_PIN (3)
controlling device 3f200000.gpio
pin gpio35
config 00000002

device 3f300000.mmc
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio36
function alt3

device 3f300000.mmc
state default
type CONFIGS_PIN (3)
controlling device 3f200000.gpio
pin gpio36
config 00000002

device 3f300000.mmc
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio37
function alt3

device 3f300000.mmc
state default
type CONFIGS_PIN (3)
controlling device 3f200000.gpio
pin gpio37
config 00000002

device 3f300000.mmc
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio38
function alt3

device 3f300000.mmc
state default
type CONFIGS_PIN (3)
controlling device 3f200000.gpio
pin gpio38
config 00000002

device 3f300000.mmc
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio39
function alt3

device 3f300000.mmc
state default
type CONFIGS_PIN (3)
controlling device 3f200000.gpio
pin gpio39
config 00000002

device 3f201000.uart
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio32
function alt3

device 3f201000.uart
state default
type CONFIGS_PIN (3)
controlling device 3f200000.gpio
pin gpio32
config 00000000

device 3f201000.uart
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio33
function alt3

device 3f201000.uart
state default
type CONFIGS_PIN (3)
controlling device 3f200000.gpio
pin gpio33
config 00000002

device 3f201000.uart
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio43
function alt0

device 3f201000.uart
state default
type CONFIGS_PIN (3)
controlling device 3f200000.gpio
pin gpio43
config 00000000

device 3f804000.i2c
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio2
function alt0

device 3f804000.i2c
state default
type MUX_GROUP (2)
controlling device 3f200000.gpio
group gpio3
function alt0

 

“3f200000.gpio” 컨트롤러 디버그 정보

# cd 3f200000.gpio/
# ls -la
total 0
drwxr-xr-x 2 root root 0 Jan  1  1970 .
drwxr-xr-x 3 root root 0 Jan  1  1970 ..
-r--r--r-- 1 root root 0 Jan  1  1970 gpio-ranges
-rw-rw-r-- 1 root root 0 Jan  1  1970 pinconf-config
-r--r--r-- 1 root root 0 Jan  1  1970 pinconf-groups
-r--r--r-- 1 root root 0 Jan  1  1970 pinconf-pins
-r--r--r-- 1 root root 0 Jan  1  1970 pingroups
-r--r--r-- 1 root root 0 Jan  1  1970 pinmux-functions
-r--r--r-- 1 root root 0 Jan  1  1970 pinmux-pins
-r--r--r-- 1 root root 0 Jan  1  1970 pins

rpi2의 경우 pin controller가 다루는 0 ~ 53번이 모두 gpio 핀과 1:1로 대응하는 것을 알 수 있다. 커널에서 gpio를 사용하려면 반드시 gpio-ranges에 등록되어 있어야 한다.

# cat gpio-ranges 
GPIO ranges handled:
0: pinctrl-bcm2835 GPIOS [0 - 53] PINS [0 - 53]

 

다음과 같이 0~53번 핀에 해당하는 그룹명이 괄호안에 표시되고, 추가 설정들이 콜론 뒤에 보여진다.

  • rpi2의 경우 모든 핀에 추가 설정이 없음을 알 수 있다.

 

pin과 그룹 디버그 정보

핀에 대한 상태와 연결된 인터럽트 번호를 보여준다.

# cat pins 
registered pins: 54
pin 0 (gpio0) function gpio_in in hi; irq 166 (none)
pin 1 (gpio1) function gpio_in in hi; irq 167 (none)
pin 2 (gpio2) function alt0 in hi; irq 168 (none)
pin 3 (gpio3) function alt0 in hi; irq 169 (none)
pin 4 (gpio4) function gpio_in in hi; irq 170 (none)
pin 5 (gpio5) function gpio_in in hi; irq 171 (none)
...
pin 30 (gpio30) function gpio_in in lo; irq 196 (none)
pin 31 (gpio31) function gpio_in in lo; irq 197 (none)
pin 32 (gpio32) function alt3 in hi; irq 198 (none)
...
pin 41 (gpio41) function alt0 in hi; irq 207 (none)
pin 42 (gpio42) function alt0 in lo; irq 208 (none)
pin 43 (gpio43) function alt0 in hi; irq 209 (none)
pin 44 (gpio44) function gpio_in in hi; irq 210 (none)
pin 45 (gpio45) function gpio_in in hi; irq 211 (none)
pin 46 (gpio46) function gpio_in in hi; irq 212 (none)
pin 47 (gpio47) function gpio_in in hi; irq 213 (none)
pin 48 (gpio48) function alt0 in lo; irq 214 (none)
pin 49 (gpio49) function alt0 in hi; irq 215 (none)
...

 

pinmux 디버그 정보

현재 펑션별로 설정된 그룹들을 보여준다.

# cat pinmux-functions 
function: gpio_in, groups = [ gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7 gpio8 gpio9 gpio10 gpio11 gpio12 gpio13 gpio14 gpio15 gpio16 gpio17 gpio18 gpio19 gpio20 gpio21 gpio22 gpio23 gpio24 gpio25 gpio26 gpio27 gpio28 gpio29 gpio30 gpio31 gpio32 gpio33 gpio34 gpio35 gpio36 gpio37 gpio38 gpio39 gpio40 gpio41 gpio42 gpio43 gpio44 gpio45 gpio46 gpio47 gpio48 gpio49 gpio50 gpio51 gpio52 gpio53 ]
function: gpio_out, groups = [ gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7 gpio8 gpio9 gpio10 gpio11 gpio12 gpio13 gpio14 gpio15 gpio16 gpio17 gpio18 gpio19 gpio20 gpio21 gpio22 gpio23 gpio24 gpio25 gpio26 gpio27 gpio28 gpio29 gpio30 gpio31 gpio32 gpio33 gpio34 gpio35 gpio36 gpio37 gpio38 gpio39 gpio40 gpio41 gpio42 gpio43 gpio44 gpio45 gpio46 gpio47 gpio48 gpio49 gpio50 gpio51 gpio52 gpio53 ]
function: alt5, groups = [ gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7 gpio8 gpio9 gpio10 gpio11 gpio12 gpio13 gpio14 gpio15 gpio16 gpio17 gpio18 gpio19 gpio20 gpio21 gpio22 gpio23 gpio24 gpio25 gpio26 gpio27 gpio28 gpio29 gpio30 gpio31 gpio32 gpio33 gpio34 gpio35 gpio36 gpio37 gpio38 gpio39 gpio40 gpio41 gpio42 gpio43 gpio44 gpio45 gpio46 gpio47 gpio48 gpio49 gpio50 gpio51 gpio52 gpio53 ]
function: alt4, groups = [ gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7 gpio8 gpio9 gpio10 gpio11 gpio12 gpio13 gpio14 gpio15 gpio16 gpio17 gpio18 gpio19 gpio20 gpio21 gpio22 gpio23 gpio24 gpio25 gpio26 gpio27 gpio28 gpio29 gpio30 gpio31 gpio32 gpio33 gpio34 gpio35 gpio36 gpio37 gpio38 gpio39 gpio40 gpio41 gpio42 gpio43 gpio44 gpio45 gpio46 gpio47 gpio48 gpio49 gpio50 gpio51 gpio52 gpio53 ]
function: alt0, groups = [ gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7 gpio8 gpio9 gpio10 gpio11 gpio12 gpio13 gpio14 gpio15 gpio16 gpio17 gpio18 gpio19 gpio20 gpio21 gpio22 gpio23 gpio24 gpio25 gpio26 gpio27 gpio28 gpio29 gpio30 gpio31 gpio32 gpio33 gpio34 gpio35 gpio36 gpio37 gpio38 gpio39 gpio40 gpio41 gpio42 gpio43 gpio44 gpio45 gpio46 gpio47 gpio48 gpio49 gpio50 gpio51 gpio52 gpio53 ]
function: alt1, groups = [ gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7 gpio8 gpio9 gpio10 gpio11 gpio12 gpio13 gpio14 gpio15 gpio16 gpio17 gpio18 gpio19 gpio20 gpio21 gpio22 gpio23 gpio24 gpio25 gpio26 gpio27 gpio28 gpio29 gpio30 gpio31 gpio32 gpio33 gpio34 gpio35 gpio36 gpio37 gpio38 gpio39 gpio40 gpio41 gpio42 gpio43 gpio44 gpio45 gpio46 gpio47 gpio48 gpio49 gpio50 gpio51 gpio52 gpio53 ]
function: alt2, groups = [ gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7 gpio8 gpio9 gpio10 gpio11 gpio12 gpio13 gpio14 gpio15 gpio16 gpio17 gpio18 gpio19 gpio20 gpio21 gpio22 gpio23 gpio24 gpio25 gpio26 gpio27 gpio28 gpio29 gpio30 gpio31 gpio32 gpio33 gpio34 gpio35 gpio36 gpio37 gpio38 gpio39 gpio40 gpio41 gpio42 gpio43 gpio44 gpio45 gpio46 gpio47 gpio48 gpio49 gpio50 gpio51 gpio52 gpio53 ]
function: alt3, groups = [ gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7 gpio8 gpio9 gpio10 gpio11 gpio12 gpio13 gpio14 gpio15 gpio16 gpio17 gpio18 gpio19 gpio20 gpio21 gpio22 gpio23 gpio24 gpio25 gpio26 gpio27 gpio28 gpio29 gpio30 gpio31 gpio32 gpio33 gpio34 gpio35 gpio36 gpio37 gpio38 gpio39 gpio40 gpio41 gpio42 gpio43 gpio44 gpio45 gpio46 gpio47 gpio48 gpio49 gpio50 gpio51 gpio52 gpio53 ]

 

핀별로 설정된 펑션을 보여준다. 각 pin 별로 그룹 멤버십, pinmux 오너, gpio 오너등을 알 수 있다.

  • 예: pin 2번과 3번은 i2c를 사용하기 위해 alt0 기능을 사용한다.
# cat pinmux-pins 
Pinmux settings per pin
Format: pin (name): mux_owner gpio_owner hog?
pin 0 (gpio0): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 1 (gpio1): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 2 (gpio2): 3f804000.i2c (GPIO UNCLAIMED) function alt0 group gpio2
pin 3 (gpio3): 3f804000.i2c (GPIO UNCLAIMED) function alt0 group gpio3
pin 4 (gpio4): (MUX UNCLAIMED) (GPIO UNCLAIMED)
...
pin 31 (gpio31): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 32 (gpio32): 3f201000.uart (GPIO UNCLAIMED) function alt3 group gpio32
pin 33 (gpio33): 3f201000.uart (GPIO UNCLAIMED) function alt3 group gpio33
pin 34 (gpio34): 3f300000.mmc (GPIO UNCLAIMED) function alt3 group gpio34
pin 35 (gpio35): 3f300000.mmc (GPIO UNCLAIMED) function alt3 group gpio35
pin 36 (gpio36): 3f300000.mmc (GPIO UNCLAIMED) function alt3 group gpio36
pin 37 (gpio37): 3f300000.mmc (GPIO UNCLAIMED) function alt3 group gpio37
pin 38 (gpio38): 3f300000.mmc (GPIO UNCLAIMED) function alt3 group gpio38
pin 39 (gpio39): 3f300000.mmc (GPIO UNCLAIMED) function alt3 group gpio39
pin 40 (gpio40): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 41 (gpio41): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 42 (gpio42): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 43 (gpio43): 3f201000.uart (GPIO UNCLAIMED) function alt0 group gpio43
pin 44 (gpio44): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 45 (gpio45): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 46 (gpio46): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 47 (gpio47): (MUX UNCLAIMED) (GPIO UNCLAIMED)
pin 48 (gpio48): 3f202000.sdhost (GPIO UNCLAIMED) function alt0 group gpio48
pin 49 (gpio49): 3f202000.sdhost (GPIO UNCLAIMED) function alt0 group gpio49
pin 50 (gpio50): 3f202000.sdhost (GPIO UNCLAIMED) function alt0 group gpio50
pin 51 (gpio51): 3f202000.sdhost (GPIO UNCLAIMED) function alt0 group gpio51
pin 52 (gpio52): 3f202000.sdhost (GPIO UNCLAIMED) function alt0 group gpio52
pin 53 (gpio53): 3f202000.sdhost (GPIO UNCLAIMED) function alt0 group gpio53

 

pinconf 디버그 정보

그룹별로 설정을 보여준다.

  • rpi2의 경우 모든 핀에 대해 하나의 핀만 소유하는 개별 그룹을 만들었다.
# cat pinconf-groups 
Pin config settings per pin group
Format: group (name): configs
0 (gpio0):
1 (gpio1):
...
52 (gpio52):
53 (gpio53):

 

0~53번 핀에 대한 핀 명과 설정들을 리스트로 보여준다.

  • 주의: 핀명과 핀 그룹명이 동일하다.
# cat pinconf-pins 
Pin config settings per pin
Format: pin (name): configs
pin 0 (gpio0):
pin 1 (gpio1):
...
pin 52 (gpio52):
pin 53 (gpio53):

 

최근에 변경된 핀 설정(pinconf)이 있는 경우 보여준다.

# cat pinconf-config 
No config found for dev/state/pin, expected:
Searched dev:
Searched state:
Searched pin:
Use: modify config_pin <devname> <state> <pinname> <value>

 

Virtual Pinctrl 드라이버 매핑 예)

pinctrl 드라이버가 다음과 같이 8개의 핀들을 관리한다고 가정한다.

  • pinmux
    • 처음 6개의 핀을 gpio 기능을 선택하였다. 그리고 마지막 2개의 핀은 i2c 기능으로 선택하였다.
  • pinconf
    • 처음 2개의 핀을 bias-pull-up, input-enable 설정하였다. 그리고 다음 2개의 핀을 output-enable 설정하였다.

 

디바이스 트리 설정

다음과 같이 “foo,foo-pinctrl” 드라이버 노드에 pinmux/pinconf 노드들을 구성하였다.

  • pinctrl 드라이버가 로드되자마자 2개의 pinmux 노드와 2개의 pinconf 노드를 동작시킨다.
foo-pinctrl {
	compatible = "foo,foo-pinctrl";
	reg = <0x0 0xf000e000 0x0 0x100>;

	pinctrl-names = "default";
	pinctrl-0 = <&gpio_0_5_sel &i2c_sel
		     &mfio_0_1_conf &mfio_2_3_conf>;

	gpio_sel: gpio_sel {
		function = "gpio";
		groups = "gpio_0_3_grp",
			 "gpio_4_5_grp",
			 "gpio_6_7_grp";
	};

	gpio_0_5_sel: gpio_0_5_sel {
		function = "gpio";
		groups = "gpio_0_3_grp",
			 "gpio_4_5_grp";
	};

	nand_sel: nand_sel {
		function = "nand";
		groups = "nand_0_3_grp",
			 "nand_4_5_grp",
			 "nand_6_7_grp";
	};

	uart_sel: uart_sel {
		function = "uart";
		groups = "uart_0_3_grp",
			 "uart_4_5_grp",
			 "uart_6_7_grp";
	};

	i2c_sel: i2c_sel {
		function = "i2c";
		groups = "i2c_6_7_grp";
	};

	mfio_0_1_conf: mfio_0_1_conf {
		pins = "mfio_0",
		       "mfio_1";
		input-enable;
		bias-pull-up;
	};

	mfio_2_3_conf: mfio_2_3_conf {
		pins = "mfio_2",
		       "mfio_3";
		output-enable;
	};
};

 

가상 “foo,foo-pinctrl” 드라이버 로드

드라이버가 로드될 때 동작할 pinmux/pinconf 노드를 파싱하여 동작시킨다.

  • 디바이스 트리의 설정 3개의 그룹에 pinmux 설정이 적용되고, 6개의 pinconf 설정이 적용되었다.
$ insmod foo-pinctrl.ko
[  223.767526] foo_pinctrl: loading out-of-tree module taints kernel.
[  223.820087] foo-pinctrl f000e000.foo-pinctrl: func:0 name:gpio grp:0 name:gpio_0_3_grp
[  223.820546] foo-pinctrl f000e000.foo-pinctrl: func:0 name:gpio grp:3 name:gpio_4_5_grp
[  223.820757] foo-pinctrl f000e000.foo-pinctrl: func:3 name:i2c grp:9 name:i2c_6_7_grp
[  223.821018] foo-pinctrl f000e000.foo-pinctrl: pin:0 set bias-pull-up
[  223.821182] foo-pinctrl f000e000.foo-pinctrl: pin:0 set input-enable
[  223.821373] foo-pinctrl f000e000.foo-pinctrl: pin:1 set bias-pull-up
[  223.871234] foo-pinctrl f000e000.foo-pinctrl: pin:1 set input-enable
[  223.871659] foo-pinctrl f000e000.foo-pinctrl: pin:2 set output-enable
[  223.871891] foo-pinctrl f000e000.foo-pinctrl: pin:3 set output-enable

 

디버그 출력

$ cd /sys/kernel/debug/pinctrl
$ ls -la
total 0
drwxr-xr-x  3 root root 0 Jan  1  1970 .
drwx------ 22 root root 0 Jan  1  1970 ..
drwxr-xr-x  2 root root 0 Jul  9 06:59 f000e000.foo-pinctrl
-r--r--r--  1 root root 0 Jan  1  1970 pinctrl-devices
-r--r--r--  1 root root 0 Jan  1  1970 pinctrl-handles
-r--r--r--  1 root root 0 Jan  1  1970 pinctrl-maps

 

$ cat pinctrl-devices 
name [pinmux] [pinconf]
foo-pinmux yes yes

 

$ cat pinctrl-handles 
Requested pin control handlers their pinmux maps:
device: f000e000.foo-pinctrl current state: default
  state: default
    type: MUX_GROUP controller foo-pinmux group: gpio_0_3_grp (0) function: gpio (0)
    type: MUX_GROUP controller foo-pinmux group: gpio_4_5_grp (3) function: gpio (0)
    type: MUX_GROUP controller foo-pinmux group: i2c_6_7_grp (9) function: i2c (3)
    type: CONFIGS_PIN controller foo-pinmux pin mfio_0 (0)config 00000105
config 0000010b
    type: CONFIGS_PIN controller foo-pinmux pin mfio_1 (1)config 00000105
config 0000010b
    type: CONFIGS_PIN controller foo-pinmux pin mfio_2 (2)config 0000010f
    type: CONFIGS_PIN controller foo-pinmux pin mfio_3 (3)config 0000010f

 

$ cat pinctrl-maps 
Pinctrl maps:
device f000e000.foo-pinctrl
state default
type MUX_GROUP (2)
controlling device f000e000.foo-pinctrl
group gpio_0_3_grp
function gpio

device f000e000.foo-pinctrl
state default
type MUX_GROUP (2)
controlling device f000e000.foo-pinctrl
group gpio_4_5_grp
function gpio

device f000e000.foo-pinctrl
state default
type MUX_GROUP (2)
controlling device f000e000.foo-pinctrl
group i2c_6_7_grp
function i2c

device f000e000.foo-pinctrl
state default
type CONFIGS_PIN (3)
controlling device f000e000.foo-pinctrl
pin mfio_0
config 00000105
config 0000010b

device f000e000.foo-pinctrl
state default
type CONFIGS_PIN (3)
controlling device f000e000.foo-pinctrl
pin mfio_1
config 00000105
config 0000010b

device f000e000.foo-pinctrl
state default
type CONFIGS_PIN (3)
controlling device f000e000.foo-pinctrl
pin mfio_2
config 0000010f

device f000e000.foo-pinctrl
state default
type CONFIGS_PIN (3)
controlling device f000e000.foo-pinctrl
pin mfio_3
config 0000010f

 

“f000e000.foo-pinctrl” 드라이버 정보

$ cd f000e000.foo-pinctrl
$ ls -la
total 0
drwxr-xr-x 2 root root 0 Jul  9 06:59 .
drwxr-xr-x 3 root root 0 Jan  1  1970 ..
-r--r--r-- 1 root root 0 Jul  9 06:59 gpio-ranges
-rw-rw-r-- 1 root root 0 Jul  9 06:59 pinconf-config
-r--r--r-- 1 root root 0 Jul  9 06:59 pinconf-groups
-r--r--r-- 1 root root 0 Jul  9 06:59 pinconf-pins
-r--r--r-- 1 root root 0 Jul  9 06:59 pingroups
-r--r--r-- 1 root root 0 Jul  9 06:59 pinmux-functions
-r--r--r-- 1 root root 0 Jul  9 06:59 pinmux-pins
-r--r--r-- 1 root root 0 Jul  9 06:59 pins

 

pin과 그룹 디버그 정보
$ cat pins 
registered pins: 8
pin 0 (mfio_0)  f000e000.foo-pinctrl
pin 1 (mfio_1)  f000e000.foo-pinctrl
pin 2 (mfio_2)  f000e000.foo-pinctrl
pin 3 (mfio_3)  f000e000.foo-pinctrl
pin 4 (mfio_4)  f000e000.foo-pinctrl
pin 5 (mfio_5)  f000e000.foo-pinctrl
pin 6 (mfio_6)  f000e000.foo-pinctrl
pin 7 (mfio_7)  f000e000.foo-pinctrl

 

$ cat pingroups 
registered pin groups:
group: gpio_0_3_grp
pin 0 (mfio_0)
pin 1 (mfio_1)
pin 2 (mfio_2)
pin 3 (mfio_3)

group: nand_0_3_grp
pin 0 (mfio_0)
pin 1 (mfio_1)
pin 2 (mfio_2)
pin 3 (mfio_3)

group: uart_0_3_grp
pin 0 (mfio_0)
pin 1 (mfio_1)
pin 2 (mfio_2)
pin 3 (mfio_3)

group: gpio_4_5_grp
pin 4 (mfio_4)
pin 5 (mfio_5)

group: nand_4_5_grp
pin 4 (mfio_4)
pin 5 (mfio_5)

group: uart_4_5_grp
pin 4 (mfio_4)
pin 5 (mfio_5)

group: gpio_6_7_grp
pin 6 (mfio_6)
pin 7 (mfio_7)

group: nand_6_7_grp
pin 6 (mfio_6)
pin 7 (mfio_7)

group: uart_6_7_grp
pin 6 (mfio_6)
pin 7 (mfio_7)

group: i2c_6_7_grp
pin 6 (mfio_6)
pin 7 (mfio_7)

 

pinmux 디버그 정보
$ cat pinmux-functions 
function: gpio, groups = [ gpio_0_3_grp gpio_4_5_grp gpio_6_7_grp ]
function: nand, groups = [ nand_0_3_grp nand_4_5_grp nand_6_7_grp ]
function: uart, groups = [ uart_0_3_grp uart_4_5_grp uart_6_7_grp ]
function: i2c, groups = [ i2c_6_7_grp ]

 

$ cat pinmux-pins 
Pinmux settings per pin
Format: pin (name): mux_owner gpio_owner hog?
pin 0 (mfio_0): f000e000.foo-pinctrl (GPIO UNCLAIMED) function gpio group gpio_0_3_grp
pin 1 (mfio_1): f000e000.foo-pinctrl (GPIO UNCLAIMED) function gpio group gpio_0_3_grp
pin 2 (mfio_2): f000e000.foo-pinctrl (GPIO UNCLAIMED) function gpio group gpio_0_3_grp
pin 3 (mfio_3): f000e000.foo-pinctrl (GPIO UNCLAIMED) function gpio group gpio_0_3_grp
pin 4 (mfio_4): f000e000.foo-pinctrl (GPIO UNCLAIMED) function gpio group gpio_4_5_grp
pin 5 (mfio_5): f000e000.foo-pinctrl (GPIO UNCLAIMED) function gpio group gpio_4_5_grp
pin 6 (mfio_6): f000e000.foo-pinctrl (GPIO UNCLAIMED) function i2c group i2c_6_7_grp
pin 7 (mfio_7): f000e000.foo-pinctrl (GPIO UNCLAIMED) function i2c group i2c_6_7_grp

 

pinconf 디버그 정보
$ cat pinconf-groups 
Pin config settings per pin group
Format: group (name): configs
0 (gpio_0_3_grp): 
1 (nand_0_3_grp): 
2 (uart_0_3_grp): 
3 (gpio_4_5_grp): 
4 (nand_4_5_grp): 
5 (uart_4_5_grp): 
6 (gpio_6_7_grp): 
7 (nand_6_7_grp): 
8 (uart_6_7_grp):

 

$ cat pinconf-pins 
[  713.351594] foo-pinctrl f000e000.foo-pinctrl: pin=0, config=bias-high-impedance:0
[  713.354329] foo-pinctrl f000e000.foo-pinctrl: pin=0, config=bias-pull-down:1
[  713.364921] foo-pinctrl f000e000.foo-pinctrl: pin=0, config=bias-pull-up:1
[  713.367024] foo-pinctrl f000e000.foo-pinctrl: pin=0, config=input-enable:0
[  713.381472] foo-pinctrl f000e000.foo-pinctrl: pin=0, config=output-enable:1
[  713.382058] foo-pinctrl f000e000.foo-pinctrl: pin=1, config=bias-high-impedance:0
[  713.382344] foo-pinctrl f000e000.foo-pinctrl: pin=1, config=bias-pull-down:1
[  713.382673] foo-pinctrl f000e000.foo-pinctrl: pin=1, config=bias-pull-up:1
[  713.383044] foo-pinctrl f000e000.foo-pinctrl: pin=1, config=input-enable:0
[  713.385809] foo-pinctrl f000e000.foo-pinctrl: pin=1, config=output-enable:1
[  713.402990] foo-pinctrl f000e000.foo-pinctrl: pin=2, config=bias-high-impedance:1
[  713.403291] foo-pinctrl f000e000.foo-pinctrl: pin=2, config=bias-pull-down:0
[  713.405833] foo-pinctrl f000e000.foo-pinctrl: pin=2, config=bias-pull-up:0
[  713.406044] foo-pinctrl f000e000.foo-pinctrl: pin=2, config=input-enable:1
[  713.406221] foo-pinctrl f000e000.foo-pinctrl: pin=2, config=output-enable:0
[  713.406366] foo-pinctrl f000e000.foo-pinctrl: pin=3, config=bias-high-impedance:1
[  713.406553] foo-pinctrl f000e000.foo-pinctrl: pin=3, config=bias-pull-down:0
[  713.406667] foo-pinctrl f000e000.foo-pinctrl: pin=3, config=bias-pull-up:0
[  713.406827] foo-pinctrl f000e000.foo-pinctrl: pin=3, config=input-enable:1
[  713.406950] foo-pinctrl f000e000.foo-pinctrl: pin=3, config=output-enable:0
[  713.407103] foo-pinctrl f000e000.foo-pinctrl: pin=4, config=bias-high-impedance:1
[  713.407224] foo-pinctrl f000e000.foo-pinctrl: pin=4, config=bias-pull-down:0
[  713.420170] foo-pinctrl f000e000.foo-pinctrl: pin=4, config=bias-pull-up:0
[  713.420738] foo-pinctrl f000e000.foo-pinctrl: pin=4, config=input-enable:0
[  713.421027] foo-pinctrl f000e000.foo-pinctrl: pin=4, config=output-enable:1
[  713.421257] foo-pinctrl f000e000.foo-pinctrl: pin=5, config=bias-high-impedance:1
[  713.421490] foo-pinctrl f000e000.foo-pinctrl: pin=5, config=bias-pull-down:0
[  713.421755] foo-pinctrl f000e000.foo-pinctrl: pin=5, config=bias-pull-up:0
[  713.421944] foo-pinctrl f000e000.foo-pinctrl: pin=5, config=input-enable:0
[  713.422117] foo-pinctrl f000e000.foo-pinctrl: pin=5, config=output-enable:1
[  713.422226] foo-pinctrl f000e000.foo-pinctrl: pin=6, config=bias-high-impedance:1
[  713.422300] foo-pinctrl f000e000.foo-pinctrl: pin=6, config=bias-pull-down:0
[  713.422469] foo-pinctrl f000e000.foo-pinctrl: pin=6, config=bias-pull-up:0
[  713.422555] foo-pinctrl f000e000.foo-pinctrl: pin=6, config=input-enable:0
[  713.422726] foo-pinctrl f000e000.foo-pinctrl: pin=6, config=output-enable:1
[  713.422844] foo-pinctrl f000e000.foo-pinctrl: pin=7, config=bias-high-impedance:1
[  713.422969] foo-pinctrl f000e000.foo-pinctrl: pin=7, config=bias-pull-down:0
[  713.423114] foo-pinctrl f000e000.foo-pinctrl: pin=7, config=bias-pull-up:0
[  713.423229] foo-pinctrl f000e000.foo-pinctrl: pin=7, config=input-enable:0
[  713.425177] foo-pinctrl f000e000.foo-pinctrl: pin=7, config=output-enable:1
Pin config settings per pin
Format: pin (name): configs
pin 0 (mfio_0): input bias high impedance, input bias pull down, input bias pull up, input enabled, output enabled
pin 1 (mfio_1): input bias high impedance, input bias pull down, input bias pull up, input enabled, output enabled
pin 2 (mfio_2): input bias high impedance, input bias pull down, input bias pull up, input enabled, output enabled
pin 3 (mfio_3): input bias high impedance, input bias pull down, input bias pull up, input enabled, output enabled
pin 4 (mfio_4): input bias high impedance, input bias pull down, input bias pull up, input enabled, output enabled
pin 5 (mfio_5): input bias high impedance, input bias pull down, input bias pull up, input enabled, output enabled
pin 6 (mfio_6): input bias high impedance, input bias pull down, input bias pull up, input enabled, output enabled
pin 7 (mfio_7): input bias high impedance, input bias pull down, input bias pull up, input enabled, output enabled

 

참고: GPIO 유저 스페이스에서 활용

# cd /sys/class/gpio/
# ls
root@raspberrypi:/sys/class/gpio# ls
export	gpiochip0  unexport
# echo 23 > export
# cd  gpio23
# ls
active_low  device  direction  edge  subsystem	uevent	value
# # cat active_low 
0
# cat direction 
in
# cat edge 
none
# cat value 
0
# cd ..
# echo 23 > unexport

 

참고

 

proc interface & seq_file

 

proc 인터페이스 (procfs 또는 /proc)

proc 인터페이스는 다음과 같은 특징을 가지고 있다.

  • 커널 코어 및 디바이스 드라이버 개발자들이 내부 커널 정보 및 디바이스 정보를 유저 스페이스에 파일 형태로 제공한다.
  • 커널 내부에 procfs 라고 불리는 가상 파일 시스템을 제공한다. 사용자는 루트 파일 시스템에 마운트하여 사용할 수 있다.
  • procfs는 커널 메모리에서만 구성되므로 빠른 접근이 가능하다.
  • 대부분의 proc 파일 정보는 사용자가 쉽게 읽을 수 있도록 readable 텍스트 형태로 출력한다. (일부는 binary로만 제공하는 정보도 있다.)

 

 

seq_file 인터페이스

  • seq_file 인터페이스는 개발자 편의를 위해 proc 인터페이스에서 파일 출력의 iteration을 간편히 제공할 목적으로 설계되었다.

 

커널 옵션

  • CONFIG_PROC_FS
    • proc 파일 시스템을 사용하기 위해서는 이 옵션이 필요한다.
    • 만들어진 proc 파일 시스템(procfs)은 마운트하여 사용한다.
  • CONFIG_PROC_KCORE
    • ELF 포맷을 지원한다. 이 파일은 gdb나 ELF 툴을 사용하여 읽어낼 수 있다.
  • CONFIG_PROC_VMCORE
    • 크래시 커널 덤프 이미지를 elf 포맷으로 출력한다.
  • CONFIG_PROC_SYSCTL
    • /proc/sys을 통해 sysctl 인터페이스를 지원한다.
  • CONFIG_PROC_PAGE_MONITOR
    • 프로세스의 메모리 사용에 대한 모니터링을 할 수 있게 한다.
    • /proc/<pid>/
      • smaps
      • clear_refs
      • pagemap
    • /proc/kpagecount
    • /proc/kpageflags

 

 

proc 인터페이스 코어

proc 파일 시스템의 코어는 아래 그림과 같이 3개의 파일을 통해 구현되어 있다.

 

샘플 proc 인터페이스 개발

자신이 개발한 디바이스 드라이버에 proc 인터페이스를 추가하여 관련 정보를 간단히 출력할 수 있는 방법을 알아보자.

  • 커널 v3.10 버전부터 proc 인터페이스가 리팩토링되었다.
  • 커널 v3.10 이전 버전에서는 proc_create() 대신 create_proc_entry() 함수를 사용하였었는데 그 방법은 생략한다.

 

 

Makefile 준비

먼저 테스트용 디렉토리를 만들고 Makefile을 준비한다.  실전에서 사용할 수 있도록 다음과 같이 두 개의 소스를 구분하였다.

  • proc 인터페이스와 관계없이 모듈의 데이터 부분을 다루는 foo.c
  • proc 인터페이스를 만들고 출력하는 proc.c

 

모듈 프로그래밍을 자주해본 경험이 있다면 다음과 같이 make -C 플래그 옵션과 -M 플래그 옵션이 어떠한 일을 하는지 알 수 있다.

  • -C: 커널 위치(include 파일들과 버전이 서로 같은지 확인해서 진행해야 하므로 모듈 컴파일 시 반드시 필요하다)
  • -M: 현재 모듈 소스를 가리키기 위해 보통 현재 위치를 가리키는 $(PWD)를 사용한다.

 

$ mkdir foo-proc
$ cd foo-proc
$ cat Makefile
obj-m := foo-proc.o
foo-proc-objs := foo.o proc.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

 

소스-1(foo.c, foo.h) 준비

foo.c 함수는 모듈에 대한 라이센스 및 개발자 정보를 기록하는 부분이 있고 그 후 다음과 같이 두 개의 파트로 구분되어 있다.

  • 첫 번째 파트:
    • 샘플 데이터 생성 및 삭제를 다룬 함수들이다.
    • 가상의 구조체 데이터 2개를 만들고 리스트에 넣는 과정이다.
  • 두 번째 파트:
    • 모듈 초기화 및 종료를 다룬 함수들이다.

 

다음 그림은 모듈 로딩시에 foo_info 구조체 2개에 샘플 데이터를 담아 foo_list에 추가한 과정을 보여준다.

 

foo-proc/foo.c – 1/2

#include <linux/slab.h>
#include <linux/module.h>       /* for module programming */
#include "foo.h"
#include "proc.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Youngil, Moon <jake9999@dreamwiz.com>");
MODULE_DESCRIPTION("A sample driver");
MODULE_LICENSE("Dual BSD/GPL");

/*--------------------------------------------------------*/
/* 1) Generate sample data                                */
/*--------------------------------------------------------*/

DEFINE_MUTEX(foo_lock);
LIST_HEAD(foo_list);

static int add_data(int a, int b)
{
        struct foo_info *info;

        printk(KERN_INFO "%s %d, %d\n", __func__, a, b);

        info = kzalloc(sizeof(*info), GFP_KERNEL);
        if (!info)
                return -ENOMEM;

        INIT_LIST_HEAD(&info->list);
        info->a = a;
        info->b = b;

        mutex_lock(&foo_lock);
        list_add(&info->list, &foo_list);
        mutex_unlock(&foo_lock);

        return 0;
}

static int add_sample_data(void)
{
        if (add_data(10, 20))
                return -ENOMEM;
        if (add_data(30, 40))
                return -ENOMEM;
        return 0;
}

static int remove_sample_data(void)
{
        struct foo_info *tmp;
        struct list_head *node, *q;

        list_for_each_safe(node, q, &foo_list){
                tmp = list_entry(node, struct foo_info, list);
                list_del(node);
                kfree(tmp);
        }

        return 0;
}

 

아래 __init 및 __exit 부분에서 컴파일 에러가 발생하면 제거하고 사용해도 된다. (커널 옵션에 따라 결정된다)

foo-proc/foo.c – 2/2

/*--------------------------------------------------------*/
/* 2) Module part                                         */
/*--------------------------------------------------------*/

static int __init foo_init(void)
{
        if (add_sample_data())
        {
                printk(KERN_INFO "add_sample_data() failed.\n");
                return -ENOMEM;
        }

        return foo_proc_init();
}

static void __exit foo_exit(void)
{
        remove_sample_data();
        foo_proc_exit();

        return;
}

module_init(foo_init);
module_exit(foo_exit);

 

foo.h 헤더 파일에서는 2 개의 정수를 담고 foo_list라는 이름의 리스트로 연결될 수 있도록 최대한 간단히 표현하였다. foo_lock 뮤텍스는 리스트에 추가하거나 제거할 때 사용하기 위한 동기화 함수이다. (아래 예제에서는 실제 데이터 접근에 대해 동기화 할 필요 없는 상황이라 뮤텍스 코드는 제거해도 된다.  그냥 실전과 같이 리스트를 보호하기 위해 습관적으로 사용하였다.

foo-proc/foo.h

#ifndef _FOO_H_
#define _FOO_H_

#include <linux/mutex.h>
#include <linux/list.h>

struct foo_info {
        int a;
        int b;
        struct list_head list;
};

extern struct mutex foo_lock;
extern struct list_head foo_list;

#endif /* _FOO_H_ */

 

소스-2(proc.c, proc.h) 준비

proc 인터페이스를 구현하기 위해서는  파일 인터페이스(file 구조체를 사용하고, 오페레이션 구현을 위해 file_operations 구조체 사용)를 사용해야 한다. 랜덤(llseek)하게 데이터에 접근하여 데이터를 출력할 필요가 없고, 순차 처리된 데이터에만 접근하여 처리해도 되는 경우 파일 인터페이스에 끼워 간단하게 규격화하여 처리할 수 있는 새로운 방법이 있다. 여기에서는 이 새로운 시퀀스 파일 인터페이스(seq_file 구조체를 사용하고, 오퍼레이션 구현을 위해 seq_operations 구조체 사용)를 소개한다.  아울러 시퀀스 파일 인터페이스를 사용하지만 시퀀스 데이터에 대한 이동에 전혀 관여하지 않고, 단순하게 한 번의 함수 호출에서 데이터를 처리하길 바란다면 시퀀스 오퍼레이션을 구현하지 않을 수도 있다. 이 방법은 single_open() 함수를 사용하는 방법으로 proc 인터페이스 구현 중 가장 단순한 방법이다.

 

proc 인터페이스를 구현하는 방법은 다음 3가지로 구분할 수 있다. (사용자들은 구현이 간편한 시퀀스 파일 인터페이스를 가장 많이 사용한다. 따라서 샘플 코드는 B 방법과 C 방법만을 소개한다)

  • A) file_operations를 구현하여 사용하는 방법
    • 랜덤하게 데이터에 접근하여 데이터를 반복 처리할 수 있도록 iteration을 지원한다.
    • 시작(open) -> llseek -> show -> llseek -> show -> llseek -> show -> 끝(close)
  • B) file_operations 에 단순히 시퀀스 파일(seq_file) 인터페이스를 연결하고 seq_operations를 구현하여 구현하여 사용하는 방법
    • 단방향 순서대로만 데이터에 접근하여 반복 처리할 수 있는 iteration만을 지원한다.
    • 시작(open) -> next -> show -> next -> show -> next -> show -> 끝(close)
  • C) file_operations에 단순히 시퀀스 파일(seq_file) 인터페이스를  연결하고 seq_operatons는 사용하지 않는 single_open()을 사용하여 처리하는 방법
    • 반복 처리할 수 있는 iteration을 지원하지 않고, 단순히 한 번의 출력 함수 호출을 통해서 처리하므로 구현이 가장 간단하다.
    • 시작(open) -> show -> 끝(close)

 

다음 그림은 시퀀스 operations를 사용한 방법과 single_open() 방식을 사용한 방법의 처리 차이를 보여준다.

 

다음 코드는 시퀀스 오퍼레이션 처리에 관련된 코드들이다. 만일 simple_open() 방식을 사용하고자하면 #define USE_SINGLE_OPEN을 선언하여 seq_file에 대한 seq_operations 구현을 사용하지 않게 한다.

foo-proc/proc.c – 1/3.

// #define USE_SINGLE_OPEN

/*--------------------------------------------------------*/
/* 1) seq_file operations part                            */
/*--------------------------------------------------------*/
#ifdef USE_SINGLE_OPEN
#else
static void *foo_seq_start(struct seq_file *s, loff_t *pos)
{
        printk(KERN_INFO "%s", __func__);
        mutex_lock(&foo_lock);
        s->private = "";

        return seq_list_start(&foo_list, *pos);
}

static void *foo_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
        printk(KERN_INFO "%s", __func__);
        s->private = "\n";

        return seq_list_next(v, &foo_list, pos);
}

static void foo_seq_stop(struct seq_file *s, void *v)
{
        mutex_unlock(&foo_lock);
        printk(KERN_INFO "%s", __func__);
}

static int foo_seq_show(struct seq_file *m, void *v)
{
        struct foo_info *info = list_entry(v, struct foo_info, list);

        printk(KERN_INFO "%s", __func__);
        seq_printf(m, "%d + %d = %d\n", info->a, info->b, info->a + info->b);

        return 0;
}

static const struct seq_operations foo_seq_ops = {
        .start  = foo_seq_start,
        .next   = foo_seq_next,
        .stop   = foo_seq_stop,
        .show   = foo_seq_show
};
#endif

 

file_opeations를 구현에 seq_file 인터페이스를 사용하였다. 이 코드에서도 USE_SINGLE_OPEN 선언을 사용하는 경우 시퀀스 오퍼레이션을 사용하지 않음을 알 수 있다.

foo-proc/proc.c – 2/3

/*--------------------------------------------------------*/
/* 2) proc operations part                                */
/*--------------------------------------------------------*/

#ifdef USE_SINGLE_OPEN
static int foo_simple_show(struct seq_file *s, void *unused)
{
        struct foo_info *info;

        list_for_each_entry(info, &foo_list, list)
                seq_printf(s, "%d + %d = %d\n", info->a, info->b, info->a + info->b);

        return 0;
}
#endif

static int foo_proc_open(struct inode *inode, struct file *file)
{
#ifdef USE_SINGLE_OPEN
        return single_open(file, foo_simple_show, NULL);
#else
        return seq_open(file, &foo_seq_ops);
#endif
}


static const struct file_operations foo_proc_ops = {
        .owner          = THIS_MODULE,
        .open           = foo_proc_open,
        .read           = seq_read,
        .llseek         = seq_lseek,
        .release        = seq_release,
};

 

아래 파트는 /proc 디렉토리에 하위 디렉토리 및 이 모듈에 연결된 파일을 생성하는 과정을 보여준다.

foo-proc/proc.c – 3/3

/*--------------------------------------------------------*/
/* 3) proc interface part  (/proc/foo-dir/foo)            */
/*--------------------------------------------------------*/

#define FOO_DIR "foo-dir"
#define FOO_FILE "foo"

static struct proc_dir_entry *foo_proc_dir = NULL;
static struct proc_dir_entry *foo_proc_file = NULL;

int foo_proc_init(void)
{
        foo_proc_dir = proc_mkdir(FOO_DIR, NULL);
        if (foo_proc_dir == NULL)
        {
                printk("Unable to create /proc/%s\n", FOO_DIR);
                return -1;
        }

        foo_proc_file = proc_create(FOO_FILE, 0, foo_proc_dir, &foo_proc_ops); /* S_IRUGO */
        if (foo_proc_file == NULL)
        {
                printk("Unable to create /proc/%s/%s\n", FOO_DIR, FOO_FILE);
                remove_proc_entry(FOO_DIR, NULL);
                return -1;
        }

        printk(KERN_INFO "Created /proc/%s/%s\n", FOO_DIR, FOO_FILE);
        return 0;
}

void foo_proc_exit(void)
{
        /* remove directory and file from procfs */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0)
        remove_proc_entry(FOO_FILE, foo_proc_dir);
        remove_proc_entry(FOO_DIR, NULL);
#else
        remove_proc_subtree(FOO_DIR, NULL);
#endif

        /* remove proc_dir_entry instance */
        proc_remove(foo_proc_file);
        proc_remove(foo_proc_dir);

        printk(KERN_INFO "Removed /proc/%s/%s\n", FOO_DIR, FOO_FILE);
}

 

foo-proc/proc.h – 3/3

#ifndef _PROC_H_
#define _PROC_H_

int foo_proc_init(void);
void foo_proc_exit(void);

#endif /* _PROC_H */

 

빌드

먼저 커널 v3.10 및 4.10 버전을 사용하는 시스템에서 빌드를 하고 테스트를 해보았다.

$ ls
Makefile  foo.c  foo.h  proc.c  proc.h

$ make
make -C /lib/modules/4.10.0-42-generic/build M=/home/jake/workspace/test/foo-proc modules
make[1]: Entering directory `/usr/src/linux-headers-4.10.0-42-generic'
  CC [M]  /home/jake/workspace/test/foo-proc/foo.o
  CC [M]  /home/jake/workspace/test/foo-proc/proc.o
  LD [M]  /home/jake/workspace/test/foo-proc/foo-proc.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/jake/workspace/test/foo-proc/foo-proc.mod.o
  LD [M]  /home/jake/workspace/test/foo-proc/foo-proc.ko
make[1]: Leaving directory `/usr/src/linux-headers-4.10.0-42-generic'

$ ls
Makefile  Module.symvers  foo-proc.ko  foo-proc.mod.c  foo-proc.mod.o  foo-proc.o  foo.c  foo.h  foo.o  modules.order  proc.c  proc.h  proc.o

 

테스트

빌드가 끝났 후 생성된 foo-proc.ko 커널 모듈을 로딩해보고 생성된 /proc/foo-dir/foo 파일을 출력해본다. 마지막으로 언로딩한다.

$ sudo insmod foo-proc.ko
$ cat /proc/foo-dir/foo
30 + 40 = 70
10 + 20 = 30
$ sudo rmmod foo-proc.ko

 

먼저 보여줄 다음은 시퀀스 오퍼레이션을 처리한 방법이다.(proc.c 파일에서 USE_SINGLE_OPEN 미사용) 커널 로그를 보고 호출되는 두 건의 데이터를 통해 foo_seq_show() 함수가 두 번 호출됨을 알 수 있다.

$ dmesg
add_data 10, 20
add_data 30, 40
Created /proc/foo-dir/foo
foo_seq_start
foo_seq_show
foo_seq_next
foo_seq_show
foo_seq_next
foo_seq_stop
foo_seq_start
foo_seq_stop
Removed /proc/foo-dir/foo

 

simple_open() 사용 방법으로 테스트

이 번 방법은 single_open() 방식이다. 먼저 proc.c 에서 USE_SINGILE_OPEN 선언을 사용하는 것으로 수정한 후, 다시 빌드한다. 그 후 생성된 모듈을 로딩한 후 테스트 해보자.  다음 커널 로그를 보는 것과 같이 foo_simple_show() 함수가 한 번만 호출된 것을 알 수 있다.

$ dmesg
add_data 10, 20
add_data 30, 40
Created /proc/foo-dir/foo
foo_simple_show
Removed /proc/foo-dir/foo

 

참고

proc_root_init()

 

proc 파일 시스템 준비

다음 그림은 proc 파일 시스템의 초기화 및 마운트에 대한 함수 처리 흐름이다.

 

proc_root_init()

fs/proc/root.c

void __init proc_root_init(void)
{
        int err;

        proc_init_inodecache();
        err = register_filesystem(&proc_fs_type);
        if (err)
                return;

        proc_self_init();
        proc_thread_self_init();
        proc_symlink("mounts", NULL, "self/mounts");

        proc_net_init();

#ifdef CONFIG_SYSVIPC
        proc_mkdir("sysvipc", NULL);
#endif
        proc_mkdir("fs", NULL);
        proc_mkdir("driver", NULL);
        proc_mkdir("fs/nfsd", NULL); /* somewhere for the nfsd filesystem to be mounted */
#if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE)
        /* just give it a mountpoint */
        proc_mkdir("openprom", NULL);
#endif
        proc_tty_init();
        proc_mkdir("bus", NULL);
        proc_sys_init();
}

마운트하여 사용할 수 있도록 커널 내부에 proc 루트 파일 시스템을 생성하고 초기화한다.

  • 코드 라인 5에서 proc_inode 구조체를 자주 사용하므로 이의 kmem 캐시를 준비한다.
  • 코드 라인 6~7에서 “proc” 파일 시스템을 등록한다. 파일 시스템들은 name을 유니크 키로 사용하는 전역 file_systems(next로 연결된다)에 단방향으로 등록순으로 연결된다.
  • 코드 라인 9에서 inode용 정수 id를 발급받아 self_inum에 대입한다.
    • inode용 IDA는 전역 proc_inum_ida를 통해 관리된다.
  • 코드 라인 10에서 inode용 정수 id를 발급받아 thread_self_inum에 대입한다.
  • 코드 라인 11에서 “self/mounts”를 가리키도록 mounts라는 이름으로 스태틱 링크를 /proc에 생성한다.
    • 예) # ls /proc/mounts -la
      lrwxrwxrwx 1 root root 11 Apr 16 16:55 /proc/mounts -> self/mounts
  • 코드 라인 13에서 “self/net”를 가리키도록 net라는 이름으로 스태틱 링크를 /proc에 생성한다. 그런 후 네트워크 네임스페이스 서브시스템에 등록한다.
    • 예) # ls /proc/net -la
      lrwxrwxrwx 1 root root 8 Apr 16 16:56 /proc/net -> self/net
  • 코드 라인 15~17에서 /proc/sysvipc 디렉토리를 생성한다.
  • 코드 라인 18에서 /proc/fs 디렉토리를 생성한다.
  • 코드 라인 19에서 /proc/driver 디렉토리를 생성한다.
  • 코드 라인 20에서 /proc/nfsd 디렉토리를 생성한다.
  • 코드 라인 21~24에서 /proc/openprom 디렉토리를 생성한다.
  • 코드 라인 25에서 /proc/tty 디렉토리를 생성하고 그 하위 디렉토리에 다음을 추가로 생성한다.
    • /proc/tty/ldisc 및 /proc/tty/driver 디렉토리를 생성한다.
    • /proc/drivers 및 /proc/ldiscs 라는 이름으로 파일을 생성하고 proc 동작이 되도록 파일 오퍼레이션을 연결한다.
  • 코드 라인 26에서 /proc/bus 디렉토리를 생성한다.
  • 코드 라인 27에서 /proc/sys 디렉토리를 생성하고 proc 디렉토리 오페레이션이 되도록 연결한다. 그런 후 그 하위 디렉토리에 다음 디렉토리들을 추가로 생성하고 해당 디렉토리 내에 관련 proc 파일들을 연결한다.
    • /proc/sys/kernel 디렉토리 및 하위 proc 파일들
    • /proc/sys/vm 디렉토리 및 하위 proc 파일들
    • /proc/sys/fs 디렉토리 및 하위 proc 파일들
    • /proc/sys/debug 디렉토리 및 하위 proc 파일들
    • /proc/sys/dev 디렉토리 및 하위 proc 파일들

 

register_filesystem()

fs/filesystems.c

/**
 *      register_filesystem - register a new filesystem
 *      @fs: the file system structure
 *
 *      Adds the file system passed to the list of file systems the kernel
 *      is aware of for mount and other syscalls. Returns 0 on success,
 *      or a negative errno code on an error.
 *
 *      The &struct file_system_type that is passed is linked into the kernel 
 *      structures and must not be freed until the file system has been
 *      unregistered.
 */

int register_filesystem(struct file_system_type * fs)
{
        int res = 0;
        struct file_system_type ** p;

        BUG_ON(strchr(fs->name, '.'));
        if (fs->next)
                return -EBUSY;
        write_lock(&file_systems_lock);
        p = find_filesystem(fs->name, strlen(fs->name));
        if (*p)
                res = -EBUSY;
        else
                *p = fs;
        write_unlock(&file_systems_lock);
        return res;
}

EXPORT_SYMBOL(register_filesystem);

file_system_type 구조체를 전역 &file_systems의 마지막에 추가한다. name(이름)에 “.”이 있거나 이미 사용한 이름이 있는 경우 에러가 반환된다.

 

아래 그림은 rpi2 시스템에 등록되는 파일 시스템들을 보여준다. 그 외에 arm64에서 사용되는 hgetlbfs 및 임베디드 시스템의 플래시 파일 시스템으로 자주 사용되는 jffs2 파일 시스템도 별도로 표기하였다. (그 외의 파일 시스템은 자주 사용하지 않으므로 생략)

 

proc 파일 시스템 마운트

proc_mount()

fs/proc/root.c

static struct dentry *proc_mount(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *data)
{
        int err;
        struct super_block *sb;
        struct pid_namespace *ns;
        char *options;

        if (flags & MS_KERNMOUNT) {
                ns = (struct pid_namespace *)data;
                options = NULL;
        } else {
                ns = task_active_pid_ns(current);
                options = data;

                if (!capable(CAP_SYS_ADMIN) && !fs_fully_visible(fs_type))
                        return ERR_PTR(-EPERM);

                /* Does the mounter have privilege over the pid namespace? */
                if (!ns_capable(ns->user_ns, CAP_SYS_ADMIN))
                        return ERR_PTR(-EPERM);
        }

        sb = sget(fs_type, proc_test_super, proc_set_super, flags, ns);
        if (IS_ERR(sb))
                return ERR_CAST(sb);

        if (!proc_parse_options(options, ns)) {
                deactivate_locked_super(sb);
                return ERR_PTR(-EINVAL);
        }

        if (!sb->s_root) {
                err = proc_fill_super(sb);
                if (err) {
                        deactivate_locked_super(sb);
                        return ERR_PTR(err);
                }

                sb->s_flags |= MS_ACTIVE;
        }

        return dget(sb->s_root);
}

커널에 등록된 proc 파일 시스템을 마운트한다.

  • 코드 라인 9~22에서 다음 커널 경로를 통해 호출되어 MS_KERNMOUNT 플래그가 붙은 경우 인자로 전달 받은 ns(네임 스페이스)를 알아온다. 그 외의 경우 태스크에서 사용되는 ns를 알아온다.
    • mq_init_ns(), pid_ns_prepare_proc() 및 init_hugetlbfs_fs() 함수를 사용하는 경우 다음 경로를 통해 ns를 포함하여 커널 마운트 플래그가 사용된다.
      • ns 사용: kern_mount_data() -> vfs_kern_mount() -> mount_fs()
    • simple_pin_fs() 함수를 사용하는 경우 다음 커널 경로를 통해 ns에 null이 대입된 상태로 커널 마운트 플래그가 사용된다.
      • ns=null: vfs_kern_mount() -> mount_fs()
  • 코드 라인 24~26에서 인자로 요청한 동일한 파일 시스템 타입의 수퍼 블럭들이 proc_test_super() 함수의 결과와 동일하면 proc_set_super() 함수를 호출한다.
    • proc_test_super() 함수를 통해 ns와 동일한 수퍼블럭인지 체크
    • proc_set_super() 함수를 통해 해당 수퍼 블럭을 가상 파일 시스템에 사용하는 anon 타입으로 할당하고 수퍼 블럭의 s_fs_info에 pid 값을 지정한다.
  • 코드 라인 28~31에서 커널 마운트가 아닌 경우 인자로 전달 받은 옵션 문자열을 파싱한다. proc 파일 시스템은 다음 두 개의 옵션을 파싱하여 사용한다.
    • “hidepid=<0~2>”
      • 0: 모든 유저에게 /proc/<pid>/*이 다 보이고 읽을 수 있는 커널 디폴트 설정이다.
      • 1: 모든 유저에게 /proc/<pid>/*이 다 보이지만 owner 유저만 읽을 수 있는 설정이다.
      • 2: owner 유저만 /proc/<pid>/*이 보이고 읽을 수 있는 설정이다.
    • “gid=XXX”
      • hidepid=0을 사용하면서 지정된 그룹이 모든 프로세스 정보를 수집할 수 있게 한다.
    • 참고:
  • 코드 라인 33~41에서 수퍼 블록에서 루트가 지정되지 않은 루트 inode를 생성하고 초기화한 후 지정한다.
  • 코드 라인 43에서 루트 inode에 대한 참조 카운터를 증가시키고 리턴한다.

 

다음 그림은 proc_mount() 함수에 도달하는 과정과 그 이후의 처리를 보여준다.

 

참고

[리눅스 인사이드] 2018년 5월 스터디 모집공고입니다.

안녕하세요? 문c 블로그의 문영일입니다.

 

저랑 같이 arm64 커널 및 디바이스 드라이버 등을 스터디할 멤버를 충원합니다. 아래 사이트를 클릭하시면 스터디 공고를 확인할 수 있습니다.

 

스터디 요약

  • 참여하실 분들은 최소한 arm 커널에 대한 일부 지식이 필요합니다.
  • 처음 커널을 스터디하고자 하시는 분들은 iamroot 사이트에서 지원하는 스터디를 이용하시기 바랍니다.
  • 스터디 참여 접수 기간: 현재 ~ 2018년 5월 5일까지
  • 스터디 시작: 2018년  5월 12일 부터 (매주 토요일 오후 3시 ~ 오후 10시까지)
  • 스터디 공간: 강남 지역 사설 스터디룸을 이용하므로 매주 약 1만원 이내의 비용 발생

 

주최 사이트

  • 사이트명: 리눅스 인사이드 (Linux Inside)
  • 사이트 URL: www.linuxinside.kr
  • 대표 관리자: 윤창호
  • arm64 커널 스터디 진행: 문c 블로그의 문영일

 

참여하실 분은 아래를 클릭하셔서 참여방법을 확인하세요.

 

감사합니다.