본문 바로가기
device driver

gpio contorl 전반적인 내용

by jsh91 2023. 6. 15.

pincontrol subsystem(pinctrl)

Pin multiplexing : uart, gpio 등 pin의 기능을 선택

Pin configuration : pull - up pull - down, debounce 등 pin의 전기적인 요소 기능을 선택

 

pincontrol driver는 각각의 ID가 존재한다

 

pinctrl-<ID> : pin configuration 설정 권한을 주기위해 dt 에서 설정한다.

pinctrl-name : pin에 ID를 부여한다 default로 작성하면 0으로 간주한다.

 

struct pinctrl *p;
struct pinctrl_state *s;
int ret;
p = pinctrl_get(dev);
if (IS_ERR(p))
 return p;
s = pinctrl_lookup_state(p, name);
if (IS_ERR(s)) {
 devm_pinctrl_put(p);
 return ERR_PTR(PTR_ERR(s));
}
ret = pinctrl_select_state(p, s);
if (ret < 0) {
 devm_pinctrl_put(p);
 return ERR_PTR(ret);
}

pinctrl_get() 를 사용하여 pinctrl을 가져온다

pinctrl_lookup_state()를 사용하여 pinctrl할 pin이 사용 중인지 확인한다

pinctrl_select_state() (pinctrl_put() 대체 api ) 를 사용하여 현재 설정을 적용한다

 

device driver가 해제 될 때 pinctrl_put을 사용

 

static struct pinctrl *pinctrl_get_select(struct device *dev, const char *name)

위 함수에서 name은 "pinctrl-name"에 명시한 name이며 default를 사용할 경우 pinctr_get_select_default() 함수를 사용한다.

함수를 사용하면 dt 에서 name이 맞는 node의 pinctrl을 가져온다.

label : node {
 status = "okay";
 pinctrl-names = "default"; <<<-----
 pinctrl-0 = <&pins>;
};

 

GPIO를 컨트롤 하는 방법

1. legacy

#include <linux/gpio.h> 를 사용한 방법

 

gpio_request() 함수를 사용하여 gpio를 ownership을 가져온다.

static int gpio_request(unsigned gpio, const char *label)

label은 디버깅을 위해 사용되며 "/sys/kernel/debug/gpio"에서 확인 할 수 있다.

 

'gpio_request()' 사용 후 'gpio_free'를 사용하여 사용하지 않을 때 해제해야 된다.

 

static bool gpio_is_valid(int number) - gpio 숫자가 사용해도 되는 값인지 확인하는 함수

static int gpio_direction_input(unsigned gpio) - gpio를 input으로 설정

static int gpio_direction_output(unsigned gpio, int value) - gpio를 output으로 설정

static int gpio_set_debounce(unsigned gpio, unsigned debounce) - gpio가 input상태에서 debounce 시간동안 low, high가 일정해야 벼튼이 눌렸다고 인식

static int gpio_get_value(unsigned gpio) - gpio가 output상태에서 gpio 값을 읽는다

void gpio_set_value(unsigned int gpio, int value) - gpio가 output 값을 변경한다.

 

 

interrupt 설정

int gpio_to_irq(unsigned gpio) - gpio와 irq를 연결한다.

return 값이 irq number이다

 

위 irq number를 사용하여 request_irq(request_threaded_irq) 를 사용하여 handler를 등록한

 

 

 

위와 다르게 gpio_desc를 사용하여  gpio control 하는 방법 ( 이 방법을 추천 한다 )

#include <linux/gpio/consumer.h> 파일에 정의되어 있다.

struct gpio_desc {
 struct gpio_chip *chip;
 unsigned long flags;
 const char *label;
};

descriptor-based interface(gpio_desc)를 사용하면 사전에 mapping 되어 있어야 하며, 어디서든지 사용하기 위해서는 legacy 방식(integer-based) 방식을 사용한다

 

GPIO descriptor mappings하려면 device tree에서 property에 gpio or gpios 를 접미로 붙여야 한다

Documentation/gpio/board.txt

foo_device {
 compatible = "acme,foo";
 [...]
 led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
 		 <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
 		<&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
 power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
 reset-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
};

 

gpio_desc 할당자

include/linux/gpio/consumer.h

 

struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags)

지정된 idx(index)에서 gpio에 해당하는 gpio_desc를 할당한다.

 

그리고 만약 gpio가 input일 경우 gpio_get_value() 함수를 사용하여 활성상태를 읽는다면 device tree에서 "GPIO_ACTIVE_HIGH"로 명시하면 high 일 때 ture

"GPIO_ACTIVE_LOW"로 명시하면 low 일 때 ture가 된다

 

 

device tree

 foo_device {
 compatible = "packt,gpio-descriptor-sample";
 led-gpios = <&gpio2 15 GPIO_ACTIVE_HIGH>, // red
 <&gpio2 16 GPIO_ACTIVE_HIGH>, // green

 btn1-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>;
 btn2-gpios = <&gpio2 31 GPIO_ACTIVE_LOW>;
 };

source code

 red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_LOW);
 green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_LOW);

 

struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags)

0번 gpio_desc를 할당한다

 

gpiod_put() - gpiod_get_index or gpiod_get 를 사용 후 끝낼 때 사용한다.

 

gpio_desc를 사용하여 gpio 방향을 변

int gpiod_direction_input(struct gpio_desc *desc);

int gpiod_direction_output(struct gpio_desc *desc, int value);

int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce);

int gpiod_get_value(const struct gpio_desc *desc);

void gpiod_set_value(struct gpio_desc *desc, int value);

int gpiod_to_irq(const struct gpio_desc *desc);

 

gpio number와 gpio_desc를 변경 할 수 있다.

struct gpio_desc *gpio_to_desc(unsigned gpio);

int desc_to_gpio(const struct gpio_desc *desc);

 

 

#gpio-cells 2개 지정시 사용 예

/ {
    soc {
        compatible = "simple-soc";
        #address-cells = <1>;
        #size-cells = <0>;

        gpio1: gpio@0 {
            compatible = "simple-gpio";
            gpio-controller;
            #gpio-cells = <2>;
        };

        device@1 {
            compatible = "simple-device";
            gpios = <&gpio1 10 0>;
        };
    };
};

- gpio-controller; 속성은 이 노드가 GPIO 컨트롤러임을 나타냅니다.
- #gpio-cells = <2>; 속성은 이 GPIO 컨트롤러에서 GPIO를 명세하는 데 2개의 셀이 필요함을 나타냅니다. 여기서는 첫 번째 셀이 GPIO 번호, 두 번째 셀이 GPIO 플래그를 나타내는 것으로 가정하겠습니다.
- device@1 노드에서 gpios = <&gpio1 10 0>; 속성은 이 디바이스가 gpio1 컨트롤러의 10번 GPIO를 사용하며, 해당 GPIO의 플래그는 0으로 설정된다는 것을 나타냅니다.

 

 

#gpio-cells 1개 지정시 사용 예

/ {
    soc {
        compatible = "simple-soc";
        #address-cells = <1>;
        #size-cells = <0>;

        gpio1: gpio@0 {
            compatible = "simple-gpio";
            gpio-controller;
            #gpio-cells = <1>;
        };

        device@1 {
            compatible = "simple-device";
            gpios = <&gpio1 5>;
        };
    };
};

- device@1의 gpios 프로퍼티에서 <&gpio1 5>는 이 디바이스가 gpio1 컨트롤러의 5번 핀을 사용

 

 

The legacy integer-based interface and device tree

 

int of_get_named_gpio(struct device_node *np, const char *propname, int index)

int of_get_named_gpio_count(struct device_node *np, const char* propname) - 

위 함수를 사용하여 아래 device tree에서 gpio를 가져오는 방법을 확인해보자

gpio1: gpio1 {
 gpio-controller;
 #gpio-cells = <2>;
};
gpio2: gpio2 {
 gpio-controller;
 #gpio-cells = <1>;
};
[...]
cs-gpios = 	<&gpio1 17 0>,
 		<&gpio2 2>;
 		<0>, /* holes are permitted, means no GPIO 2 */
 		<&gpio1 17 0>;
reset-gpios = 	<&gpio1 30 0>;
cd-gpios = 	<&gpio2 10>;

 

int n_gpios = of_get_named_gpio_count(dev.of_node, "cs-gpios"); /* return 4 */
int second_gpio = of_get_named_gpio(dev.of_node, "cs-gpio", 1); /* <&gpio2 2>; */
int rst_gpio = of_get_named_gpio("reset-gpio", 0); /* reset-gpios */
gpio_request(second_gpio, "my-gpio");

 

 

 

device tree에서 gpio를 [<name>-gpio] 와 같이 작성하는 경우가 있다(legacy)

이럴 경우 아래 api를 사하여야 한다.

int of_gpio_count(struct device_node *np)

int of_get_gpio(struct device_node *np, int index)

 

device tree

my_node@addr {
 compatible = "[...]";
 gpios = <&gpio1 2 0>, /* INT */
 <&gpio1 5 0>; /* RST */
 [...]
};

source code

struct device_node *np = dev->of_node;

int n_gpios = of_gpio_count(); /* Will return 2 */
int gpio_int = of_get_gpio(np, 0);

if (!gpio_is_valid(gpio_int)) {
 dev_err(dev, "failed to get interrupt gpio\n");
 return ERR_PTR(-EINVAL);
}

gpio_rst = of_get_gpio(np, 1);
if (!gpio_is_valid(pdata->gpio_rst)) {
 dev_err(dev, "failed to get reset gpio\n");
 ret

 

 

device tree에서 gpio와 매핑하는 방법

device tree에서 property를 작성 내용

interrupt-parent: This is the GPIO controller for the GPIO

interrupts: This is the interrupts specifier list

gpio4: gpio4 {
 gpio-controller;
 #gpio-cells = <2>;
 interrupt-controller;
 #interrupt-cells = <2>;
};
my_label: node@0 {
 reg = <0>;
 spi-max-frequency = <1000000>;
 interrupt-parent = <&gpio4>;
 interrupts = <29 IRQ_TYPE_LEVEL_LOW>;
};

#interrupt-cells 의 역할

first : gpio number이며 이는 irp와 매핑된다

second : interrupt가 발생하는 조건

 

여러 interrupt를 설정하였을 경우

/ {
    my_device: device@0 {
        compatible = "vendor,my-device";
        reg = <0x0 0x1000>;  /* Base address and size */
        interrupt-parent = <&gpio1>;
        interrupts = <16 0>, <17 0>, <18 0>;  /* GPIO numbers and flags */
    };

    gpio1: gpio@1 {
        compatible = "vendor,gpio";
        gpio-controller;
        #gpio-cells = <2>;
    };
};

platform_get_irq(pdev, 2) 호출은 my_device 디바이스의 세 번째 인터럽트 (즉, 18번 GPIO 핀에 연결된 인터럽트)를 가져오게 된다.

 

gpio 와 sysfs

/sys/class/gpio/ 경로에 모든 gpio가 존재한다

echo 1 > export 하면 gpio 1번을 control 할 수 있다

echo 1 > unexport 하면 gpio 1번을 control 하지 못한다

 

아래 함수를 사용하면 gpio를 /sys/class/gpio/ 영역에 export 할 수 있다.

int gpio_export(unsigned gpio, bool direction_may_change);

int gpiod_export(struct gpio_desc *desc, bool direction_may_change);

 

int gpio_export_link(struct device *dev, const char *name, unsigned gpio)

int gpiod_export_link(struct device *dev, const char *name, struct gpio_desc *desc)

위 api를 사용한 예제는 아래와 같다

gpio_export_link(dev, "my_gpio", 4); -> /sys/class/gpio/my_gpio 에 생성

 

 

 

지금까지는 전반적인 gpio를 컨트롤 하는 방법을 알아봤다면

지금부터 gpio_chip을사용한 gpio control 방법에 대해 공부해보자

struct gpio_chip

struct gpio_chip {
 const char *label;
 struct device *dev;
 struct module *owner;
 int (*request)(struct gpio_chip *chip, unsigned offset);
 void (*free)(struct gpio_chip *chip, unsigned offset);
 int (*get_direction)(struct gpio_chip *chip, unsigned offset);
 int (*direction_input)(struct gpio_chip *chip, unsigned offset);
 int (*direction_output)(struct gpio_chip *chip, unsigned offset,
 int value);
 int (*get)(struct gpio_chip *chip,unsigned offset);
 void (*set)(struct gpio_chip *chip, unsigned offset, int value);
 void (*set_multiple)(struct gpio_chip *chip, unsigned long *mask,
 unsigned long *bits);
 int (*set_debounce)(struct gpio_chip *chip, unsigned offset,
 unsigned debounce);
 int (*to_irq)(struct gpio_chip *chip, unsigned offset);
 int base;
 u16 ngpio;
 const char *const *names;
 bool can_sleep;
 bool irq_not_threaded;
 bool exported;
#ifdef CONFIG_GPIOLIB_IRQCHIP
 /*
 * With CONFIG_GPIOLIB_IRQCHIP we get an irqchip
 * inside the gpiolib to handle IRQs for most practical cases.
 */
 struct irq_chip *irqchip;
 struct irq_domain *irqdomain;
 unsigned int irq_base;
 irq_flow_handler_t irq_handler;
 unsigned int irq_default_type;
#endif
#if defined(CONFIG_OF_GPIO)
 /*
 * If CONFIG_OF is enabled, then all GPIO controllers described in the
 * device tree automatically may have an OF translation
 */
 struct device_node *of_node;
int of_gpio_n_cells;
 int (*of_xlate)(struct gpio_chip *gc,
 const struct of_phandle_args *gpiospec, u32 *flags);
}

- request는 칩별 활성화를 위한 선택적 후크입니다. 제공되면 gpio_request() 또는 gpiod_get()을 호출할 때마다 GPIO를 할당하기 전에 실행됩니다.
- free는 칩별 비활성화를 위한 선택적 후크입니다. 제공된 경우 gpiod_put() 또는 gpio_free()를 호출할 때마다 GPIO가 할당 해제되기 전에 실행됩니다.
- get_direction은 GPIO 오프셋의 방향을 알아야 할 때마다 실행됩니다. 반환 값은 out을 의미하는 경우 0, in을 의미하는 경우 1(GPIOF_DIR_XXX와 동일) 또는 음수 오류여야 합니다.
- direction_input은 신호 오프셋을 입력으로 구성하거나 오류를 반환합니다.
- get은 GPIO 오프셋 값을 반환합니다. 출력 신호의 경우 실제로 감지된 값 또는 0을 반환합니다.
- set은 출력 값을 GPIO 오프셋에 할당합니다.
- set_multiple은 mask로 정의된 여러 신호에 대한 출력 값을 할당해야 할 때 호출됩니다. 제공되지 않으면 커널은 마스크 비트를 통과하고 각 비트 세트에서 chip->set(i)를 실행하는 일반 후크를 설치합니다.

- set_debounce 컨트롤러에서 지원하는 경우 이 후크는 지정된 GPIO에 대한 디바운스 시간을 설정하기 위해 제공되는 선택적 콜백입니다.
- to_irq는 IRQ 매핑에 GPIO를 제공하는 선택적 후크입니다. gpio_to_irq() 또는 gpiod_to_irq()를 실행하려고 할 때마다 호출됩니다.
기능. 이 구현은 잠들지 않을 수 있습니다.
- base는 이 칩이 처리하는 첫 번째 GPIO 번호를 식별합니다. 또는 등록 중에 음수이면 커널이 자동으로 (동적으로) 하나를 할당합니다.
- ngpio는 이 컨트롤러가 제공하는 GPIO의 수입니다. base에서 (base + ngpio - 1)까지 시작합니다.
- name이 설정된 경우 이 칩의 GPIO에 대한 대체 이름으로 사용할 문자열 배열이어야 합니다.
배열은 ngpio 크기여야 하며 별칭이 필요하지 않은 모든 GPIO는 배열에서 해당 항목을 NULL로 설정할 수 있습니다.
- can_sleep은 get()/set() 메서드가 잠들 수 있는 경우 설정할 부울 플래그입니다. I2C 또는 SPI와 같은 버스에 있는 GPIO 컨트롤러(확장기라고도 함)의 경우 액세스가 절전 모드로 이어질 수 있습니다. 이는 칩이 IRQ를 지원하는 경우, 예를 들어 IRQ 상태 레지스터를 읽을 때 칩 액세스가 휴면 상태일 수 있으므로 이러한 IRQ를 스레딩해야 함을 의미합니다. 메모리(SoC의 일부)에 매핑된 GPIO 컨트롤러의 경우 false로 설정할 수 있습니다.
- irq_not_threaded는 부울 플래그이며 can_sleep이 설정된 경우 설정해야 하지만 IRQ는 스레드할 필요가 없습니다.

 

우에서 선언한 struct gpio_chip은 gpiochip_add()를 사용하여 등록하고 gpiochip_remove()를 사용하여 해제한

gpiochip_add를 사용하여 등록하면 /sys/class/gpio/gpiochipX/ 경로에 파일이 생성된

 

 

이제 interrupt에 대해 정리해보자

 

interrupt를 관리하는 struct irq_chip 는 아래와 같다

struct irq_chip {
 struct device *parent_device;
 const char *name;
 void (*irq_enable)(struct irq_data *data);
 void (*irq_disable)(struct irq_data *data);
 void (*irq_ack)(struct irq_data *data);
 void (*irq_mask)(struct irq_data *data);
 void (*irq_unmask)(struct irq_data *data);
 void (*irq_eoi)(struct irq_data *data);
 int (*irq_set_affinity)(struct irq_data *data, const struct cpumask
*dest, bool force);
 int (*irq_retrigger)(struct irq_data *data);
 int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
 int (*irq_set_wake)(struct irq_data *data, unsigned int on);
 void (*irq_bus_lock)(struct irq_data *data);
 void (*irq_bus_sync_unlock)(struct irq_data *data);
 int (*irq_get_irqchip_state)(struct irq_data *data, enum
irqchip_irq_state which, bool *state);
 int(*irq_set_irqchip_state)(struct irq_data *data, enum
irqchip_irq_state which, bool state);
 unsigned long flags;
};

 

- parent_device: 이것은 이 irqchip의 부모에 대한 포인터입니다.

- name: 이것은 /proc/interrupts 파일의 이름입니다.
- irq_enable: 이 후크는 인터럽트를 활성화하며 기본값은 NULL인 경우 chip->unmask입니다.
- irq_disable: 인터럽트를 비활성화합니다.
- irq_ack: 이것은 새로운 인터럽트의 시작입니다. 일부 컨트롤러에는 이것이 필요하지 않습니다.
- Linux는 인터럽트가 발생하자마자 서비스를 받기 훨씬 전에 이 함수를 호출합니다. 일부 구현은 이 함수를 chip->disable()에 매핑하므로 라인의 다른 인터럽트 요청은 현재 인터럽트 요청이 처리될 때까지 다른 인터럽트를 일으키지 않습니다. 더 이상 키울 수 없도록.
- irq_unmask: 이 후크는 인터럽트 소스의 마스크를 해제합니다.
- irq_eoi: eoi는 인터럽트 종료를 나타냅니다. Linux는 IRQ 서비스가 완료된 직후 이 후크를 호출합니다. 이 기능을 사용하여 해당 라인에서 다른 인터럽트 요청을 수신하기 위해 필요에 따라 컨트롤러를 재구성합니다. 일부 구현은 이 함수를 chip->enable()에 매핑하고 chip->ack()에서 수행된 작업을 반대로 합니다.
- irq_set_affinity: 이것은 CPU 선호도를 설정하지만 SMP 머신에서만 가능합니다. SMP 환경에서 이 기능은 인터럽트가 서비스될 CPU를 설정합니다. 이 기능은 단일 프로세서 시스템에서 사용되지 않습니다.
- irq_retrigger: 하드웨어에서 인터럽트를 다시 트리거하여 IRQ를 CPU로 다시 보냅니다.
- irq_set_type: IRQ의 흐름 유형(IRQ_TYPE_LEVEL/ 등)을 설정합니다.
- irq_set_wake: 이것은 IRQ의 전원 관리 깨우기를 활성화/비활성화합니다.
- irq_bus_lock: 느린 버스(I2C) 칩에 대한 액세스를 잠급니다. 뮤텍스를 잠그는 것으로 충분합니다.
- irq_bus_sync_unlock: 느린 버스(I2C) 칩을 동기화하고 잠금 해제합니다. 이전에 잠겨 있던 뮤텍스를 잠금 해제합니다.
- irq_get_irqchip_state 및 irq_set_irqchip_state: 이들은 각각 인터럽트의 내부 상태를 반환하거나 설정합니다.

 

struct irq_domain는 interrupt controller domain을 설명한 구조체 이며 hardware IRQ 와 Linux IRQ(virtual IRQ)와 매핑을 한다

 

struct irq_domain {
 const char *name;
 const struct irq_domain_ops *ops;
 void *host_data;
 unsigned int flags;
 /* Optional data */
 struct fwnode_handle *fwnode;
 [...]
};

- name은 인터럽트 도메인의 이름입니다.
- ops는 irq_domain 메서드에 대한 포인터입니다.
- host_data는 소유자가 사용하는 개인용 데이터 포인터입니다. irq_domain 핵심 코드에 의해 건드리지 않습니다.
- flags는 irq_domain 플래그별 호스트입니다.
- fwnode는 선택 사항입니다. irq_domain과 관련된 DT 노드에 대한 포인터입니다. DT 인터럽트 지정자를 디코딩할 때 사용됩니다.

 

 

interrupt controller driver는 irq_domain을 생성하고 등록해야 된다

irq_domain)add_<mapping_method>를 함수들을 사용하여서

irq_domain_add_linear()

irq_domain_add_tree()

irq_domain_add_nomap()

 

irq_create_mapping() 함수는 매핑을 생성하고 도메일에게 할당한다

unsigned int irq_create_mapping(struct irq_domain *domain, irq_hw_number_t hwirq)

domain: 이것은 이 하드웨어 인터럽트가 속한 도메인입니다. NULL은 기본 도메인
Hwirq: 해당 도메인 공간의 하드웨어 IRQ 번호입니다.

 

 

'device driver' 카테고리의 다른 글

Waitqueue  (0) 2023.12.13
charter device driver - 9. expander device moudle 만들기  (0) 2023.06.19
kernel linked list api 사용법  (0) 2023.06.01
Waiting queues  (0) 2023.06.01
charter device driver - 8. interrupt platform driver 만들기  (0) 2023.05.30

댓글