ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스 커널] pinctrl - raspberry pi 3 overview
    Linux/kernel 2023. 8. 4. 20:30

    글의 참고

    1 https://www.amazon.com/Linux-Device-Drivers-Development-customized-ebook/dp/B073V4LKWN

    2. https://www.amazon.com/Linux-Driver-Development-Embedded-Processors/dp/1729321828

    3. https://raspberrypi.stackexchange.com/questions/77803/how-do-i-use-devicetree-to-init-gpio-to-a-set-value

    4. https://yohda.tistory.com/entry/LINUXKERNELDT-devicetree-overlay%EB%A5%BC-%ED%86%B5%ED%95%B4-%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%88%98%EC%A0%95-%EB%82%B4%EC%97%AD-%EB%B0%98%EC%98%81%ED%95%98%EA%B8%B0


    글의 전제

    - 그림 출처는 항시 그림 아래에 표시했다.

    - 이 글의 내용은 라즈베리파이에만 테스트를 진행했다. 다른 보드에서는 테스트해 보지 않았다.


    글의 내용

    " 먼저 기본 개념을 알고 가야한다. 리눅스 `핀 서브시스템`은 아래의 2가지 기능을 제공한다.

    " Pin Multiplexing - 하나의 핀이 여러 가지 기능을 가질 수 있다. 즉, 하나의 핀으로 UART TX Pin, GPIO line, I2C SDA 등 여러 목적으로 사용될 수 있다.
    " Pin Configuration - 전기적 특성을 설정한다. 예를 들어, 풀업, 풀다운, 디바운스 주기등

     

     

    " 예제로 bcm283x.dtsi의 gpio 노드를 확인해보자.

    gpio: gpio@7e200000 {
    			compatible = "brcm,bcm2835-gpio";
    			reg = <0x7e200000 0xb4>;
    			/*
    			 * The GPIO IP block is designed for 3 banks of GPIOs.
    			 * Each bank has a GPIO interrupt for itself.
    			 * There is an overall "any bank" interrupt.
    			 * In order, these are GIC interrupts 17, 18, 19, 20.
    			 * Since the BCM2835 only has 2 banks, the 2nd bank
    			 * interrupt output appears to be mirrored onto the
    			 * 3rd bank's interrupt signal.
    			 * So, a bank0 interrupt shows up on 17, 20, and
    			 * a bank1 interrupt shows up on 18, 19, 20!
    			 */
    			interrupts = <2 17>, <2 18>, <2 19>, <2 20>;
    
    			gpio-controller;
    			#gpio-cells = <2>;
    
    			interrupt-controller;
    			#interrupt-cells = <2>;
    
    			gpio-ranges = <&gpio 0 0 54>;
    
    			/* Defines common pin muxing groups
    			 *
    			 * While each pin can have its mux selected
    			 * for various functions individually, some
    			 * groups only make sense to switch to a
    			 * particular function together.
    			 */
                ...
                ...
    			emmc_gpio34: emmc_gpio34 {
    				brcm,pins = <34 35 36 37 38 39>;
    				brcm,function = <BCM2835_FSEL_ALT3>;
    				brcm,pull = <BCM2835_PUD_OFF
    					     BCM2835_PUD_UP
    					     BCM2835_PUD_UP
    					     BCM2835_PUD_UP
    					     BCM2835_PUD_UP
    					     BCM2835_PUD_UP>;
    			};
                ...
                ...
    		};

     

     

    " `emmc_gpio34` 노드를 보면, 아래와 같은 내용을 확인할 수 있다.

    - brcm,pins  = 핀 번호
    - brcm,function = 핀의 기능
    - brcm,pull = no-pull/풀업/풀다운 설정 가능.

     

     

    " 위의 매크로 상수들은 다음과 같다.

    /include/dt-bindings/pinctrl/bcm2835.h
    
    /* SPDX-License-Identifier: GPL-2.0 */                                                               
    /*
     * Header providing constants for bcm2835 pinctrl bindings.
     *
     * Copyright (C) 2015 Stefan Wahren <stefan.wahren@i2se.com>
     */
    
    #ifndef __DT_BINDINGS_PINCTRL_BCM2835_H__
    #define __DT_BINDINGS_PINCTRL_BCM2835_H__
    
    /* brcm,function property */
    #define BCM2835_FSEL_GPIO_IN    0
    #define BCM2835_FSEL_GPIO_OUT   1
    #define BCM2835_FSEL_ALT5   2
    #define BCM2835_FSEL_ALT4   3
    #define BCM2835_FSEL_ALT0   4
    #define BCM2835_FSEL_ALT1   5
    #define BCM2835_FSEL_ALT2   6
    #define BCM2835_FSEL_ALT3   7
    
    /* brcm,pull property */
    #define BCM2835_PUD_OFF     0
    #define BCM2835_PUD_DOWN    1
    #define BCM2835_PUD_UP      2
    
    #endif /* __DT_BINDINGS_PINCTRL_BCM2835_H__ */

     

     

    " ALT0 ~ ALT5가 뭔지 알아봐야 한다. 34 ~39 까지는 ALT3가 reserved다.

    BCM2837 Pin 정보

     

     

    " 그러므로, 이제 디바이스 트리의 `emmc_gpio34` 노드를 해석해보자.

    Pin Mux" GPIO[34 ~39]의 기능을 reserved로 한다.
    Pin Conf" GPIO[34 ~39]를 설정을 34번 풀다운, 35~39는 풀업으로 한다.

     

     

    " 그리고 빌드 돌리고 하면 저 내용들이 적용이 될까? 안된다. 저것만으로는 적용이 안된다. 저렇게 선언만 한다고 해서 `Pin Control Driver`가 저 설정을 적용하지 않는다. 내가 원하는 핀에 기능과 설정을 적용하기 위해서는 내가 Pin Control Driver에게 해당 내용을 적용해달라고 요청을 해야 한다. 즉, 선언만 해서는 아무것도 적용되지 않는다.

     

     

    " 핀을 어떻게 사용할지를 정의했으면, 이 정의한 내용을 적용해야 한다. 적용하기 위해서는 디바이스트리에 `pinctrl-names`와 `pinctrl-<id>` 프로퍼티들을 선언해야 한다. 지금부터는 디바이스트리 오버레이를 기반으로 DT를 설명할 것이다.

    /dts-v1/;
    /plugin/;
    
    /*
    // linux/include/dt-bindings/pinctrl/bcm2835.h
    #ifndef __DT_BINDINGS_PINCTRL_BCM2835_H__
    #define __DT_BINDINGS_PINCTRL_BCM2835_H__
    
    // brcm,function property
    #define BCM2835_FSEL_GPIO_IN    0
    #define BCM2835_FSEL_GPIO_OUT   1
    #define BCM2835_FSEL_ALT5   2
    #define BCM2835_FSEL_ALT4   3
    #define BCM2835_FSEL_ALT0   4
    #define BCM2835_FSEL_ALT1   5
    #define BCM2835_FSEL_ALT2   6
    #define BCM2835_FSEL_ALT3   7
    
    // brcm,pull property
    #define BCM2835_PUD_OFF     0
    #define BCM2835_PUD_DOWN    1
    #define BCM2835_PUD_UP      2
    */
    
    / {
        compatible = "brcm,bcm2835";
    
    	fragment@0 {
    		target = <&soc>;
    		__overlay__ {
    			yohda_button {
    				compatible = "yohda,button";			
    			
    				pinctrl-names = "default";
    				pinctrl-0 = <&key_button>;
    			};
    		};
    	};
    
    	fragment@1 {
            target = <&gpio>;
            __overlay__ {
                key_button: key_button {
                    brcm,pins = <17 27>;
                    brcm,function = <0 0>;
                    brcm,pull = <2 2>;    
                };    
            };
        };	
    };

     

     

    " fragment@1에 gpio 노드안에 사용하고 싶은 핀에 대한 기능과 설정을 작성을 해봤다. fragment@0에 해당 핀에 대한 정보를 가져와서 Pin Control Driver에게 적용해달라고 요청해야 되기 때문에 pinctrl-names와 pinctrl-<id> 프로퍼티를 작성한다. `pinctrl-<id>` 에 적용되는 값이 중요하다. 여기에 앞 서 정의했던 GPIO 정보드를 전달해줘야 한다. 그래야, `핀 컨틀롤러`가 해당 정보를 읽어서 실제 하드웨어 적용하게 된다. 

     

     

    " 이제 드라이버 코드에서 위의 DTB 정보를 가져와서 Pin Control Driver에서 가져오는 코드를 보자. 

    static int button_pinctrl(struct platform_device *pdev)
    {   
        struct pinctrl *p;
        struct pinctrl_state *s;
        int ret = 0;
        
        /* 
        	현재 device에 존재하는 pinctrl 정보를 가져온다.
        */
        p = pinctrl_get(&pdev->dev);
        if(IS_ERR(p))
            return p;
        
        /* 
        	default로 선언된 pinctrl에 대한 pinctrl 정보를 가져온다.
        */
        s = pinctrl_lookup_state(p, "default");
        if(IS_ERR(s)) {
            devm_pinctrl_put(p);
            return ERR_PTR(ret);
        }
        
        /*
        	해당 pinctrl에 대한 기능과 설정을 적용해달라고 요청한다.
        */
        ret = pinctrl_select_state(p ,s);
        if(ret < 0) {
            devm_pinctrl_put(p);
            return ERR_PTR(ret);
        }       
                
        return 0;
    }
    
    
    static int button_probe(struct platform_device *pdev) 
    { 
        int err;
    
        err = button_pinctrl(pdev);
        if(err)
        	return err;
    
        return 0;
    }

     

     

    " `button_probe` 콜백 함수는 `compatible`이 위의 DTB에 작성된 `yohda,button`과 일치해야 호출된다. DTB에서 핀 정보를 가져오는 코드는 `pinctrl_lookup_state` 함수에서 가져오게 된다. 위의 내용을 적용하면 GPIO[17,27]이 Input/pull-up으로 설정되는 것을 확인할 수 있다. 라즈베리파이에서 `gpio readall` 혹 `raspi-gpio ge`t을 통해서 GPIO 상태를 조회해 볼 수 있다.

     

     

    " 추가적으로 `핀 컨트롤러`에서 어떻게 Pin Mux와 Pin Config를 하는지 알면 좋다. 4가지 구조체를 확인해야 한다.

    1. struct pinctrl_ops - device tree 에서 파싱되는 pinctrl 노드들과 관련이 있는 구조체이다.

    2. struct pinmux_ops - 핀의 기능과 관련이 있는 구조체이다.

    3. struct pinconf_ops - 핀의 설정과 관련이 있는 구조체이다.

    4. struct pinctrl_desc - 3개의 구조체를 아우르는 Pin Control Subsystem의 가장 최상위 구조체이다.

     

     

    " 라즈베리파이3B 에서는 어떻게 작성되어 있는지 확인해보자.

     

    static const struct pinctrl_ops bcm2835_pctl_ops = {
        .get_groups_count = bcm2835_pctl_get_groups_count,
        .get_group_name = bcm2835_pctl_get_group_name,
        .get_group_pins = bcm2835_pctl_get_group_pins,
        .pin_dbg_show = bcm2835_pctl_pin_dbg_show,
        .dt_node_to_map = bcm2835_pctl_dt_node_to_map,
        .dt_free_map = bcm2835_pctl_dt_free_map,
    };
    
    ...
    ...
    
    static const struct pinmux_ops bcm2835_pmx_ops = {
        .free = bcm2835_pmx_free,
        .get_functions_count = bcm2835_pmx_get_functions_count,
        .get_function_name = bcm2835_pmx_get_function_name,
        .get_function_groups = bcm2835_pmx_get_function_groups,
        .set_mux = bcm2835_pmx_set,
        .gpio_disable_free = bcm2835_pmx_gpio_disable_free,
        .gpio_set_direction = bcm2835_pmx_gpio_set_direction,
    };
    
    ...
    ...
    
    static const struct pinconf_ops bcm2835_pinconf_ops = {
        .is_generic = true,
        .pin_config_get = bcm2835_pinconf_get,
        .pin_config_set = bcm2835_pinconf_set,
    };
    
    ...
    ...
    
    static const struct pinctrl_desc bcm2711_pinctrl_desc = {
        .name = "pinctrl-bcm2711",
        .pins = bcm2835_gpio_pins,
        .npins = BCM2711_NUM_GPIOS,
        .pctlops = &bcm2835_pctl_ops,
        .pmxops = &bcm2835_pmx_ops,
        .confops = &bcm2711_pinconf_ops,
        .owner = THIS_MODULE,
    };
    
    ...
    ...
    
    static int bcm2835_pinctrl_probe(struct platform_device *pdev)
    {
        strcut bcm2835_pinctrl pc;
        
        ...
        ...
        
        pc->pctl_dev = devm_pinctrl_register(dev, &pc->pctl_desc, pc);
        if (IS_ERR(pc->pctl_dev)) {
            gpiochip_remove(&pc->gpio_chip);
            return PTR_ERR(pc->pctl_dev);
        }
        
        ...
        ...
    }

     

     

    " 위에서 중요한 함수는 다음과 같다.

    1. pinmux_ops.set_mux : 핀의 기능
    2. pinconf_ops.pin_config_set : 핀의 설정

     

     

    " 디바이스 트리에 노드만 정의하는 것만으로는 `pinctrl_ops.dt_node_to_map()` 함수만 호출된다. 즉, 실제 GPIO를 PULL-UP으로 바꾸고, INPUT으로 변경하고 싶거나, OUTPUT으로 변경한 뒤 디폴트 상태로 SET 설정을 하고 싶다면, `pinctrl-names`과 `pinctrl-<id>`를 작성하고 `pinctrl_select_state` 함수를 호출해야 한다. `pinctrl_select_state` 함수가 호출되어야, 내부적으로 `pinmux_ops`, `pinconf_ops` 콜백 함수들이 호출된다.

     

     

    " pinctrl_select_state() 함수는 다음과 같다.

    /**
     * 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)
    {
    	if (p->state == state)
    		return 0;
    
    	return pinctrl_commit_state(p, state);
    }
    EXPORT_SYMBOL_GPL(pinctrl_select_state);

     

     

    " pinctrl_commit_state() 함수를 보면 setting->type 에 따라 pinmux_enable_setting() 및 pinconf_apply_setting() 함수를 호출하는 것을 볼 수 있다.

    // kernel/drivers/pinctrl/core.c
    /**
     * pinctrl_commit_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
     */
    static int pinctrl_commit_state(struct pinctrl *p, struct pinctrl_state *state)
    {
    	struct pinctrl_setting *setting, *setting2;
    	struct pinctrl_state *old_state = p->state;
    	int ret;
    	....
    
    	/* 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;
    		}
    
    		....
    	}
    
    	p->state = state;
    
    	return 0;
        ....
    }

     

     

    " pinctrl_selelct_state() 함수는 pinctrl_commit_state() 함수를 호출하고 이 안에서 setting->type 이 PINMUX 면 pinmux_enable_setting() 함수를 호출해서 SoC Pinctrl Driver 의 .pin_mux() 함수를 호출한다

    // kernel/drivers/pinctrl/pinmux.c
    int pinmux_enable_setting(struct pinctrl_setting const *setting)
    {
    	....
    	ret = ops->set_mux(pctldev, setting->data.mux.func,
    			   setting->data.mux.group);
    
    	....
    }

     

     

    " 혹은  setting->type 이 PINCONF 면 pinconf_apply_setting() 함수를 호출해서 SoC Pinctrl Driver 의 .pin_config_set() 함수를 호출한다.

    // kernel/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;
    
    	....
    
    	switch (setting->type) {
    	case PIN_MAP_TYPE_CONFIGS_PIN:
    		....
    		ret = ops->pin_config_set(pctldev,
    				setting->data.configs.group_or_pin,
    				setting->data.configs.configs,
    				setting->data.configs.num_configs);
    		....
    		break;
    	case PIN_MAP_TYPE_CONFIGS_GROUP:
    		....
    		ret = ops->pin_config_group_set(pctldev,
    				setting->data.configs.group_or_pin,
    				setting->data.configs.configs,
    				setting->data.configs.num_configs);
    		....
    		break;
    	default:
    		return -EINVAL;
    	}
    
    	....
    }

    'Linux > kernel' 카테고리의 다른 글

    [리눅스 커널] debug - dynamic debug  (0) 2023.08.05
    [리눅스 커널] Data structure - Linked list  (0) 2023.08.05
    [리눅스 커널] SMP - cpumask  (0) 2023.08.03
    [리눅스 커널] CPU topology  (0) 2023.08.03
    [리눅스 커널] CPU overview  (0) 2023.08.03
Designed by Tistory.