-
[리눅스 커널] pinctrl - raspberry pi 3 overviewLinux/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
글의 전제
- 그림 출처는 항시 그림 아래에 표시했다.
- 이 글의 내용은 라즈베리파이에만 테스트를 진행했다. 다른 보드에서는 테스트해 보지 않았다.
글의 내용
" 먼저 기본 개념을 알고 가야한다. 리눅스 `핀 서브시스템`은 아래의 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다.
" 그러므로, 이제 디바이스 트리의 `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