GPIO 서브시스템 — GPIO / pinctrl

GPIO(General-Purpose Input/Output) 서브시스템의 아키텍처와 gpiod descriptor 기반 안전한 제어 패턴, gpio_chip 드라이버 구현, libgpiod 유저스페이스 접근, GPIO Expander, GPIO IRQ 컨트롤러(irqchip), pinctrl 핀 멀티플렉싱, Device Tree 바인딩까지 GPIO 관련 드라이버 개발에 필요한 핵심을 다룹니다.

전제 조건: 디바이스 드라이버인터럽트(Interrupt) 문서를 먼저 읽으세요. I2C는 I2C 서브시스템, SPI는 SPI 서브시스템 문서를 참조하세요.
일상 비유: GPIO는 만능 스위치 패널과 비슷합니다. 각 스위치(핀)를 입력 또는 출력으로 설정하고, 버튼 누름 감지, LED 점등, 리셋 라인 제어 등 다양한 용도로 활용합니다. pinctrl은 각 스위치의 기능을 결정하는 배선 설정표에 해당합니다.

핵심 요약

  • gpiod API 사용 — Legacy 정수 기반 API 대신 descriptor 기반 gpiod API를 사용합니다.
  • active-low 자동 처리 — gpiod_set_value()가 Device Tree의 GPIO_ACTIVE_LOW 플래그를 자동 반영합니다.
  • gpio_chip 구현 — GPIO 컨트롤러 드라이버는 gpio_chip 구조체(Struct)를 등록합니다.
  • IRQ 통합 — GPIO 핀을 인터럽트 소스로 사용할 때 gpio_irq_chip을 활용합니다.
  • pinctrl 연동 — 핀 멀티플렉싱과 전기적 특성은 pinctrl 서브시스템과 협력합니다.

단계별 이해

  1. GPIO 서브시스템 아키텍처 파악
    gpiolib, gpio_chip, gpiod 계층 구조를 이해합니다.
  2. gpiod API로 GPIO 제어
    descriptor 기반 획득, 값 읽기/쓰기, IRQ 변환을 익힙니다.
  3. gpio_chip 드라이버 작성
    GPIO 컨트롤러를 커널에 등록하는 방법을 학습합니다.
  4. GPIO IRQ와 pinctrl 통합
    인터럽트 지원과 핀 설정을 마무리합니다.
GPIO 서브시스템 gpiolib / gpiod / gpio_chip Pinctrl 서브시스템 pinmux / pinconf IRQ 서브시스템 gpio_irq_chip

GPIO 개요

GPIO (General-Purpose Input/Output)는 소프트웨어로 제어 가능한 범용 디지털 핀입니다. LED, 버튼, 리셋 라인, 칩 셀렉트, 인터럽트 입력 등 다양한 용도로 사용됩니다.

Linux GPIO 서브시스템은 drivers/gpio/에 구현되며, 크게 두 가지 API가 있습니다:

API헤더상태특징
Legacy (integer-based)<linux/gpio.h>Deprecatedgpio_request(), gpio_direction_input()
Descriptor-based (gpiod)<linux/gpio/consumer.h>현재 표준gpiod_get(), gpiod_set_value()
Legacy API 사용 금지: 새 코드에서 gpio_request(), gpio_free(), gpio_get_value() 등 정수 기반 legacy API를 사용하지 마세요. 커널 메인라인에서는 legacy GPIO API를 사용하는 새 드라이버를 받아들이지 않습니다.

GPIO 서브시스템 아키텍처

Linux GPIO 서브시스템은 하드웨어 GPIO 컨트롤러부터 유저스페이스 접근까지 여러 계층으로 구성됩니다. gpiolib이 핵심 프레임워크 역할을 하며, gpio_chip이 하드웨어 추상화를, gpiod가 소비자(consumer) API를 제공합니다.

Linux GPIO 서브시스템 아키텍처 User Space /dev/gpiochipN (chardev) libgpiod (v2 API) gpioget / gpioset / gpiomon gpio-cdev (chardev 드라이버) sysfs GPIO (deprecated) gpiod Consumer API devm_gpiod_get() / gpiod_set_value() / gpiod_to_irq() 커널 드라이버 (GPIO 소비자) SPI CS, I2C recovery, LED, Key, Reset, Regulator Enable... gpiolib Core (drivers/gpio/gpiolib.c) gpio_desc 관리, 라인 요청/해제, 값 읽기/쓰기, IRQ 매핑, 이벤트 관리 gpio_chip Provider Interface (devm_gpiochip_add_data) SoC GPIO Controller gpio-mxc, gpio-tegra, gpio-rcar, gpio-stm32 I2C/SPI GPIO Expander gpio-pca953x, gpio-mcp23s08, gpio-pcf857x ACPI GPIO gpiolib-acpi.c, ACPI _DSD GPIO 매핑 GPIO Aggregator gpio-aggregator, gpio-sim (테스트용) pinctrl 서브시스템과 연동: 핀 멀티플렉싱, 풀업/풀다운, 드라이브 강도 설정

GPIO 네이밍 규칙

GPIO 서브시스템에서 사용하는 주요 네이밍 개념을 정리합니다:

용어범위설명예시
gpiochip시스템 전역GPIO 컨트롤러 식별자 (/dev/gpiochipN)gpiochip0, gpiochip1
offset칩 로컬칩 내 핀 번호 (0부터 시작)0..ngpio-1
gpio_desc커널 내부gpiod descriptor (핀의 커널 내부 표현)struct gpio_desc *
con_id소비자 드라이버Device Tree property에서 <con_id>-gpios의 접두사"reset"reset-gpios
line name유저스페이스DT의 gpio-line-names로 부여된 사람 읽기용 이름"user-led", "wifi-reset"

GPIO 하드웨어 내부 구조

물리적 GPIO 핀은 여러 전기적 설정을 지원하며, 드라이버에서 이를 올바르게 구성하는 것이 중요합니다:

설정설명커널 API / DT 속성용도
Push-PullHIGH/LOW 모두 능동적으로 구동기본 출력 모드LED 제어, 리셋 라인
Open-DrainLOW만 능동 구동, HIGH는 풀업 의존GPIO_OPEN_DRAIN / drive-open-drainI2C SDA/SCL, 인터럽트 라인
Open-SourceHIGH만 능동 구동, LOW는 풀다운 의존GPIO_OPEN_SOURCE / drive-open-source특수 전원 제어
Pull-Up내장 풀업 저항 활성화GPIO_PULL_UP / bias-pull-up버튼 입력 (active-low)
Pull-Down내장 풀다운 저항 활성화GPIO_PULL_DOWN / bias-pull-down기본 LOW 유지 필요 시
Schmitt Trigger히스테리시스 입력 (노이즈 내성)input-schmitt-enable느린 신호 에지, 노이즈 환경
Debounce글리치 필터링 (HW/SW)input-debounce = <usec>기계식 버튼/스위치
핀 설정과 pinctrl: GPIO 하드웨어 설정(풀업/풀다운, 드라이브 강도, 슬루율 등)은 pinctrl 서브시스템과 밀접하게 연관됩니다. 대부분의 SoC에서 GPIO 핀은 pinctrl 핀과 1:1 매핑(Mapping)되며, pinctrl_gpio_set_config()를 통해 gpiolib에서 pinctrl로 설정이 전달됩니다.

gpiod API (Descriptor-based)

현대 Linux 커널의 표준 GPIO 인터페이스인 gpiod API를 사용합니다. 아래 다이어그램은 devm_gpiod_get() 호출 시 내부에서 일어나는 descriptor 해석 과정을 보여줍니다.

gpiod_get() Descriptor 해석 흐름 드라이버 호출 devm_gpiod_get(dev, "reset", OUT_HIGH) DT Property 검색 "reset-gpios" = <&gpio1 7 LOW> gpio_chip 탐색 phandle &gpio1 → gpio_chip 매칭 gpio_desc 생성 chip→desc[offset=7] 플래그 해석 GPIO_ACTIVE_LOW → active_low=1 방향 설정 GPIOD_OUT_HIGH → direction_output(1) pinctrl 요청 pinctrl_gpio_request(offset) devm 등록 자동 해제 콜백 등록 GPIO Lookup 우선순위 (DT 없을 때) 1. Device Tree <con_id>-gpios 프로퍼티 2. ACPI _DSD GPIO 리소스 3. Board File gpiod_lookup_table 4. 실패 -ENOENT 반환 없으면 → 없으면 → 없으면 → Board File Lookup (DT 없는 레거시 보드) gpiod_add_lookup_table(&my_gpios) → { .dev_id = "my-dev", .con_id = "reset", .chip_label = "gpiochip0", .idx = 7 }

GPIO 획득과 해제

#include <linux/gpio/consumer.h>

/* Device Tree에서 "reset-gpios" 속성을 참조하여 GPIO 획득 */
struct gpio_desc *reset_gpio;

reset_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(reset_gpio))
    return PTR_ERR(reset_gpio);

/* 선택적(optional) GPIO: 없어도 에러 아님 */
struct gpio_desc *led_gpio;
led_gpio = devm_gpiod_get_optional(&pdev->dev, "led", GPIOD_OUT_LOW);

/* 인덱스로 여러 GPIO 획득 */
struct gpio_desc *cs_gpio;
cs_gpio = devm_gpiod_get_index(&pdev->dev, "cs", 0, GPIOD_OUT_HIGH);

GPIO 동작

/* 출력 값 설정 (active-low 자동 처리) */
gpiod_set_value(reset_gpio, 1);  /* active (논리적 1) */
gpiod_set_value(reset_gpio, 0);  /* inactive (논리적 0) */

/* sleepable context에서 사용 (I2C/SPI GPIO expander 등) */
gpiod_set_value_cansleep(reset_gpio, 1);

/* 입력 값 읽기 */
int val = gpiod_get_value(button_gpio);

/* 방향 변경 */
gpiod_direction_input(gpio);
gpiod_direction_output(gpio, 1);

/* GPIO → IRQ 번호 변환 */
int irq = gpiod_to_irq(button_gpio);
if (irq < 0)
    return irq;

ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
        my_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
        "my-button", data);
gpiod_set_value vs gpiod_set_raw_value: gpiod_set_value()는 Device Tree의 GPIO_ACTIVE_LOW 플래그를 자동 반영합니다. gpiod_set_raw_value()는 물리적 라인 레벨을 직접 제어합니다. 일반적으로 gpiod_set_value()를 사용하세요.

GPIO 인터럽트 흐름

GPIO를 인터럽트 소스로 사용하는 경우, GPIO 서브시스템과 IRQ 서브시스템이 협력하여 핀 상태 변화를 커널 인터럽트로 변환합니다. 아래 다이어그램은 GPIO 인터럽트의 전체 처리 흐름을 보여줍니다.

GPIO 인터럽트 처리 흐름 GPIO Pin 에지/레벨 변화 gpio_chip irqchip 콜백 irq_mask/unmask/set_type irq_domain HW IRQ → Linux virq 매핑 Generic IRQ irq_desc / action handle_edge_irq() Threaded IRQ request_threaded_irq IRQF_ONESHOT Driver handler() gpiod_to_irq(gpio_desc) → irq_find_mapping(domain, hwirq) → Linux virq 반환 드라이버의 GPIO IRQ 설정 절차 1. gpio = devm_gpiod_get(dev, "alert", GPIOD_IN); 2. irq = gpiod_to_irq(gpio); 3. devm_request_threaded_irq(dev, irq, NULL, handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, ...);

GPIO Debounce (글리치 필터링)

기계식 버튼이나 스위치는 접점 바운싱으로 인해 한 번의 누름에 여러 번의 에지 변화가 발생합니다. Debounce는 이러한 글리치를 필터링하여 깨끗한 신호를 제공합니다.

버튼 바운싱과 Debounce 타이밍 원본 신호 (바운싱) 바운싱 (~5ms) 바운싱 (~5ms) 디바운스 (필터링 후) debounce_us debounce_us IRQ 이벤트 디바운스 없음: 10+회 오발생 디바운스 적용: 1회 버튼 누름 유지 8+회 오발생 1회
방식구현지연(Latency)장점단점
HW DebounceSoC GPIO 컨트롤러 내장 필터HW에서 설정 (수십 us ~ 수 ms)CPU 부하 없음, 정밀한 타이밍모든 SoC 지원 아님
SW Debouncegpiolib hrtimer 기반gpiod_set_debounce()모든 GPIO에 적용 가능CPU 오버헤드(Overhead), hrtimer 정밀도 의존
/* HW debounce 설정 (지원하는 컨트롤러만) */
ret = gpiod_set_debounce(button_gpio, 50000);  /* 50ms */
if (ret == -ENOTSUPP)
    dev_warn(dev, "HW debounce not supported, using SW\\n");

/* gpio_chip에서 HW debounce 구현 */
static int my_gpio_set_config(struct gpio_chip *gc,
                              unsigned int offset,
                              unsigned long config)
{
    if (pinconf_to_config_param(config) == PIN_CONFIG_INPUT_DEBOUNCE) {
        u32 debounce_us = pinconf_to_config_argument(config);
        /* HW 디바운스 레지스터 설정 */
        writel(debounce_us / 31, priv->base + DEBOUNCE_REG(offset));
        return 0;
    }
    return -ENOTSUPP;
}

GPIO Aggregator

gpio-aggregator는 여러 물리적 GPIO 라인을 하나의 가상 GPIO 컨트롤러로 묶어 유저스페이스에 노출하는 기능입니다. 보안 제한이나 권한 분리가 필요한 환경에서 특정 GPIO 라인만 선택적으로 컨테이너(Container)나 VM에 전달할 때 유용합니다.

/* GPIO Aggregator 사용 (sysfs 인터페이스) */
$ echo "gpiochip0 3,5,7" > /sys/bus/platform/drivers/gpio-aggregator/new_device
/* 새로운 /dev/gpiochipN 생성 (3개 라인: 0=pin3, 1=pin5, 2=pin7) */

$ echo "gpiochip0 3,5,7" > /sys/bus/platform/drivers/gpio-aggregator/delete_device
/* 가상 GPIO 컨트롤러 제거 */
gpio-sim: 테스트 환경에서는 gpio-sim 모듈을 사용하여 실제 하드웨어 없이 가상 GPIO 컨트롤러를 생성할 수 있습니다. configfs를 통해 라인 수, 라벨, 초기 값 등을 설정합니다. CI/CD 파이프라인(Pipeline)이나 유닛 테스트에 유용합니다.

GPIO Device Tree 바인딩

my_device: my-device@0 {
    compatible = "vendor,my-device";
    /* 프로퍼티 이름: <con-id>-gpios */
    reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>;
    led-gpios = <&gpio2 3 GPIO_ACTIVE_HIGH>;
    cs-gpios = <&gpio1 4 GPIO_ACTIVE_LOW>,
               <&gpio1 5 GPIO_ACTIVE_LOW>;
};

GPIO Bulk Operations

여러 GPIO 핀을 동시에 제어해야 할 때 gpiod_get_array()gpiod_set_array_value()를 사용하면 하드웨어 레벨에서 단일 레지스터(Register) 접근으로 최적화됩니다:

/* 여러 GPIO를 한 번에 획득 (Device Tree: data-gpios = <...>, <...>, <...>) */
struct gpio_descs *data_gpios;

data_gpios = devm_gpiod_get_array(&pdev->dev, "data", GPIOD_OUT_LOW);
if (IS_ERR(data_gpios))
    return PTR_ERR(data_gpios);

dev_info(dev, "acquired %u data GPIOs\n", data_gpios->ndescs);

/* 8비트 병렬 데이터 버스 출력 예시 */
unsigned long *values;

values = bitmap_alloc(data_gpios->ndescs, GFP_KERNEL);
bitmap_zero(values, data_gpios->ndescs);

/* 0xA5 = 10100101 출력 */
__set_bit(0, values);  /* bit 0 */
__set_bit(2, values);  /* bit 2 */
__set_bit(5, values);  /* bit 5 */
__set_bit(7, values);  /* bit 7 */

/* 같은 칩의 GPIO들은 단일 writel()로 최적화됨 */
gpiod_set_array_value(data_gpios->ndescs,
                      data_gpios->desc, data_gpios->info, values);

bitmap_free(values);
Bulk 최적화: 같은 gpio_chip에 속하는 GPIO들은 gpio_chip.set_multiple() 콜백(Callback)을 통해 단일 레지스터 접근으로 처리됩니다. 서로 다른 칩의 GPIO가 섞여 있으면 칩별로 그룹화하여 순차 처리됩니다. 고속 병렬 데이터 버스(Bus)(8080 LCD 인터페이스 등)에서 성능 차이가 큽니다.

GPIO Hog

GPIO Hog는 Device Tree에서 GPIO 라인의 초기 상태를 선언적으로 설정하는 메커니즘입니다. 부팅 시 gpiolib이 자동으로 해당 GPIO를 요청하고 방향/값을 설정합니다. 특정 드라이버 없이 보드 레벨에서 리셋 라인이나 전원 인에이블 핀을 고정해야 할 때 유용합니다:

&gpio1 {
    /* GPIO Hog: 부팅 시 자동 설정 */
    wifi-reset-hog {
        gpio-hog;
        gpios = <5 GPIO_ACTIVE_LOW>;
        output-high;  /* 논리적 HIGH (active-low이므로 물리적 LOW) */
        line-name = "wifi-reset";
    };

    pmic-enable-hog {
        gpio-hog;
        gpios = <12 GPIO_ACTIVE_HIGH>;
        output-high;
        line-name = "pmic-enable";
    };

    debug-input-hog {
        gpio-hog;
        gpios = <20 GPIO_ACTIVE_HIGH>;
        input;        /* 입력으로 설정 */
        line-name = "debug-detect";
    };
};
GPIO Hog 주의사항: Hog된 GPIO는 다른 드라이버에서 gpiod_get()으로 획득할 수 없습니다(이미 사용 중). 따라서 드라이버가 나중에 제어해야 하는 핀에는 hog를 사용하지 마세요. Hog는 보드 레벨에서 부팅 직후 고정되어야 하는 핀(전원, 리셋 해제 등)에만 적합합니다.

gpio_chip 구현

GPIO 컨트롤러 드라이버를 작성하려면 gpio_chip 구조체를 구현하고 등록합니다. 아래 다이어그램은 gpio_chip 등록 시 내부에서 일어나는 과정을 보여줍니다.

devm_gpiochip_add_data() 등록 시퀀스 Driver probe() gpiolib core gpio-cdev irq_domain (옵션) devm_gpiochip_add_data() gpio_desc[] 할당 base 번호 배정 gpiolib_cdev_register() /dev/gpiochipN gpiochip_add_irqchip() (gpio_irq_chip 설정 시) irq_domain 생성 of_gpiochip_add() return 0 (성공) ngpio 개 chardev 생성 Hog 처리
#include <linux/gpio/driver.h>

struct my_gpio {
    struct gpio_chip gc;
    void __iomem *base;
    struct mutex lock;
};

static int my_gpio_get(struct gpio_chip *gc, unsigned int offset)
{
    struct my_gpio *priv = gpiochip_get_data(gc);
    u32 reg;

    reg = readl(priv->base + 0x10);  /* Data Input Register */
    return !!(reg & BIT(offset));
}

static void my_gpio_set(struct gpio_chip *gc,
                         unsigned int offset, int value)
{
    struct my_gpio *priv = gpiochip_get_data(gc);
    u32 reg;

    mutex_lock(&priv->lock);
    reg = readl(priv->base + 0x14);  /* Data Output Register */
    if (value)
        reg |= BIT(offset);
    else
        reg &= ~BIT(offset);
    writel(reg, priv->base + 0x14);
    mutex_unlock(&priv->lock);
}

static int my_gpio_direction_input(struct gpio_chip *gc,
                                    unsigned int offset)
{
    struct my_gpio *priv = gpiochip_get_data(gc);
    u32 reg;

    mutex_lock(&priv->lock);
    reg = readl(priv->base + 0x04);  /* Direction Register */
    reg &= ~BIT(offset);            /* 0 = input */
    writel(reg, priv->base + 0x04);
    mutex_unlock(&priv->lock);
    return 0;
}

static int my_gpio_direction_output(struct gpio_chip *gc,
                                     unsigned int offset, int value)
{
    my_gpio_set(gc, offset, value);

    struct my_gpio *priv = gpiochip_get_data(gc);
    u32 reg;

    mutex_lock(&priv->lock);
    reg = readl(priv->base + 0x04);
    reg |= BIT(offset);              /* 1 = output */
    writel(reg, priv->base + 0x04);
    mutex_unlock(&priv->lock);
    return 0;
}

static int my_gpio_probe(struct platform_device *pdev)
{
    struct my_gpio *priv;

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

    priv->base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);

    mutex_init(&priv->lock);

    priv->gc.label            = "my-gpio";
    priv->gc.parent           = &pdev->dev;
    priv->gc.owner            = THIS_MODULE;
    priv->gc.base             = -1;  /* 동적 번호 할당 */
    priv->gc.ngpio            = 32;
    priv->gc.get              = my_gpio_get;
    priv->gc.set              = my_gpio_set;
    priv->gc.direction_input  = my_gpio_direction_input;
    priv->gc.direction_output = my_gpio_direction_output;

    return devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv);
}

libgpiod 유저스페이스

libgpiod는 Linux GPIO character device (/dev/gpiochipN)를 통한 유저스페이스 GPIO 접근 라이브러리입니다. 기존의 /sys/class/gpio/ sysfs 인터페이스를 대체합니다.

sysfs GPIO 폐기: /sys/class/gpio/export 인터페이스는 deprecated 상태입니다. 새 프로젝트에서는 chardev 기반(/dev/gpiochipN) 접근을 권장하며, 가능한 경우 libgpiod(v2 이상)를 사용하세요.

libgpiod 명령행 도구

도구용도예시
gpiodetect시스템의 GPIO 칩 목록gpiodetect
gpioinfoGPIO 라인 상세 정보gpioinfo gpiochip0
gpiogetGPIO 입력 값 읽기gpioget gpiochip0 7
gpiosetGPIO 출력 값 설정gpioset gpiochip0 7=1
gpiomonGPIO 이벤트 모니터링gpiomon gpiochip0 7

libgpiod C API (v2)

#include <gpiod.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    struct gpiod_chip *chip;
    struct gpiod_line_settings *settings;
    struct gpiod_line_config *line_cfg;
    struct gpiod_request_config *req_cfg;
    struct gpiod_line_request *request;
    unsigned int offsets[] = { 7 };
    enum gpiod_line_value value;

    chip = gpiod_chip_open("/dev/gpiochip0");
    settings = gpiod_line_settings_new();
    gpiod_line_settings_set_direction(settings,
                                       GPIOD_LINE_DIRECTION_INPUT);
    gpiod_line_settings_set_bias(settings,
                                  GPIOD_LINE_BIAS_PULL_UP);

    line_cfg = gpiod_line_config_new();
    gpiod_line_config_add_line_settings(line_cfg, offsets, 1, settings);

    req_cfg = gpiod_request_config_new();
    gpiod_request_config_set_consumer(req_cfg, "my-app");

    request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);

    value = gpiod_line_request_get_value(request, 7);
    printf("GPIO 7 = %d\\n", value);

    gpiod_line_request_release(request);
    gpiod_request_config_free(req_cfg);
    gpiod_line_config_free(line_cfg);
    gpiod_line_settings_free(settings);
    gpiod_chip_close(chip);
    return 0;
}

GPIO Expander

GPIO expander는 I2C 또는 SPI를 통해 GPIO 핀 수를 확장하는 디바이스입니다. 커널에서는 일반 GPIO 컨트롤러와 동일한 gpio_chip 인터페이스로 통합됩니다.

디바이스인터페이스GPIO 수인터럽트커널 드라이버
MCP23017I2C16지원gpio-mcp23s08
MCP23S17SPI16지원gpio-mcp23s08
PCA9555I2C16지원gpio-pca953x
PCA9535I2C16지원gpio-pca953x
PCF8574I2C8지원gpio-pcf857x
TCA6424AI2C24지원gpio-pca953x

GPIO Expander Device Tree 예시

&i2c1 {
    gpio_exp: gpio-expander@20 {
        compatible = "nxp,pca9555";
        reg = <0x20>;
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-parent = <&gpio1>;
        interrupts = <12 IRQ_TYPE_EDGE_FALLING>;
        interrupt-controller;
        #interrupt-cells = <2>;
    };
};

/* GPIO expander의 핀을 다른 디바이스에서 참조 */
my_led: led-controller {
    compatible = "gpio-leds";
    led-status {
        gpios = <&gpio_exp 3 GPIO_ACTIVE_HIGH>;
        label = "status";
        linux,default-trigger = "heartbeat";
    };
};
can_sleep 플래그: I2C/SPI 기반 GPIO expander는 버스 전송이 필요하므로 gpio_chip.can_sleep = true로 설정됩니다. 이 경우 인터럽트 컨텍스트에서 gpiod_get_value()를 호출할 수 없으며, 반드시 gpiod_get_value_cansleep()을 사용해야 합니다.

GPIO IRQ 컨트롤러 (irqchip)

GPIO 컨트롤러가 인터럽트를 지원하려면 gpio_chip에 IRQ chip 기능을 통합해야 합니다. Linux 커널은 GPIOLIB_IRQCHIP 인프라를 통해 이 과정을 크게 단순화합니다. gpio_irq_chip 구조체를 gpio_chip에 내장하여 등록하면, gpiolib이 자동으로 irq_domain을 생성하고 관리합니다.

IRQ Domain 유형

유형설명적용 대상
Flat (Linear)GPIO 번호가 직접 HW IRQ 번호로 매핑일반 SoC GPIO 컨트롤러, GPIO expander
HierarchicalGPIO IRQ → 상위 IRQ 컨트롤러(GIC 등)에 계층적 매핑부모 IRQ 컨트롤러가 별도 존재하는 경우
계층적 GPIO IRQ Domain vs Flat IRQ Domain Flat IRQ Domain GPIO 0..31 gpio_chip + irq_chip chained_irq_handler irq_domain (linear) hwirq 0..31 → virq Parent IRQ (GIC) 단일 parent IRQ line chained handler가 부모 IRQ에서 호출되어 개별 GPIO IRQ를 dispatch Hierarchical IRQ Domain GPIO 0 GPIO 1 GPIO 2 ... GPIO irq_domain (child) gpio_irq_chip + irq_domain_ops Parent irq_domain (GIC/INTC) 각 GPIO가 독립된 parent HW IRQ에 매핑 HW IRQ A HW IRQ B HW IRQ C ... 각 GPIO → 독립된 parent HW IRQ

gpio_chip irqchip 구현

현대 커널(v5.10+)에서는 gpio_irq_chipgpio_chip 내에 설정하고 devm_gpiochip_add_data()로 한 번에 등록하는 것이 권장 패턴입니다:

#include <linux/gpio/driver.h>
#include <linux/interrupt.h>

struct my_gpio_irq {
    struct gpio_chip gc;
    void __iomem *base;
    struct mutex lock;
    u32 irq_mask;     /* 소프트웨어 IRQ 마스크 상태 */
    u32 irq_type;     /* 에지/레벨 타입 비트맵 */
};

/* irq_chip 콜백: 인터럽트 마스크/언마스크 */
static void my_gpio_irq_mask(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    struct my_gpio_irq *priv = gpiochip_get_data(gc);
    u32 mask = BIT(irqd_to_hwirq(d));

    priv->irq_mask &= ~mask;
    writel(priv->irq_mask, priv->base + IRQ_MASK_REG);
    gpiochip_disable_irq(gc, irqd_to_hwirq(d));
}

static void my_gpio_irq_unmask(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    struct my_gpio_irq *priv = gpiochip_get_data(gc);
    u32 mask = BIT(irqd_to_hwirq(d));

    gpiochip_enable_irq(gc, irqd_to_hwirq(d));
    priv->irq_mask |= mask;
    writel(priv->irq_mask, priv->base + IRQ_MASK_REG);
}

/* 인터럽트 타입 설정 (에지/레벨) */
static int my_gpio_irq_set_type(struct irq_data *d,
                                 unsigned int type)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    struct my_gpio_irq *priv = gpiochip_get_data(gc);
    u32 bit = BIT(irqd_to_hwirq(d));

    switch (type & IRQ_TYPE_SENSE_MASK) {
    case IRQ_TYPE_EDGE_RISING:
        priv->irq_type |= bit;
        writel(priv->irq_type, priv->base + IRQ_EDGE_REG);
        break;
    case IRQ_TYPE_EDGE_FALLING:
        priv->irq_type &= ~bit;
        writel(priv->irq_type, priv->base + IRQ_EDGE_REG);
        break;
    case IRQ_TYPE_EDGE_BOTH:
        /* 하드웨어가 지원하면 both-edge 설정 */
        break;
    default:
        return -EINVAL;
    }
    return 0;
}

/* IMMUTABLE irq_chip: 런타임 수정 불가 (v6.0+ 필수) */
static const struct irq_chip my_gpio_irqchip = {
    .name         = "my-gpio-irq",
    .irq_mask     = my_gpio_irq_mask,
    .irq_unmask   = my_gpio_irq_unmask,
    .irq_set_type = my_gpio_irq_set_type,
    .flags        = IRQCHIP_IMMUTABLE,
    GPIOCHIP_IRQ_RESOURCE_HELPERS,
};

/* Chained IRQ handler: 부모 IRQ에서 호출 */
static void my_gpio_irq_handler(struct irq_desc *desc)
{
    struct gpio_chip *gc = irq_desc_get_handler_data(desc);
    struct my_gpio_irq *priv = gpiochip_get_data(gc);
    struct irq_chip *irqchip = irq_desc_get_chip(desc);
    u32 pending;

    chained_irq_enter(irqchip, desc);

    pending = readl(priv->base + IRQ_STATUS_REG);
    pending &= priv->irq_mask;

    while (pending) {
        int hwirq = __ffs(pending);
        generic_handle_domain_irq(gc->irq.domain, hwirq);
        pending &= ~BIT(hwirq);
    }

    /* 인터럽트 상태 클리어 (W1C) */
    writel(pending, priv->base + IRQ_STATUS_REG);

    chained_irq_exit(irqchip, desc);
}

/* probe에서 GPIO + IRQ chip 등록 */
static int my_gpio_irq_probe(struct platform_device *pdev)
{
    struct my_gpio_irq *priv;
    struct gpio_irq_chip *girq;
    int parent_irq;

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

    priv->base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);

    parent_irq = platform_get_irq(pdev, 0);
    if (parent_irq < 0)
        return parent_irq;

    mutex_init(&priv->lock);

    /* GPIO chip 기본 설정 */
    priv->gc.label            = "my-gpio-irq";
    priv->gc.parent           = &pdev->dev;
    priv->gc.owner            = THIS_MODULE;
    priv->gc.base             = -1;
    priv->gc.ngpio            = 32;
    priv->gc.get              = my_gpio_get;
    priv->gc.set              = my_gpio_set;
    priv->gc.direction_input  = my_gpio_direction_input;
    priv->gc.direction_output = my_gpio_direction_output;

    /* IRQ chip 내장 설정 (권장 패턴) */
    girq = &priv->gc.irq;
    gpio_irq_chip_set_chip(girq, &my_gpio_irqchip);
    girq->parent_handler = my_gpio_irq_handler;
    girq->num_parents = 1;
    girq->parents = devm_kcalloc(&pdev->dev, 1,
                                  sizeof(*girq->parents), GFP_KERNEL);
    if (!girq->parents)
        return -ENOMEM;
    girq->parents[0] = parent_irq;
    girq->default_type = IRQ_TYPE_NONE;
    girq->handler = handle_edge_irq;

    /* GPIO chip + IRQ chip 동시 등록 */
    return devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv);
}
IRQCHIP_IMMUTABLE 필수: v6.0 이후 커널에서는 irq_chip 구조체에 IRQCHIP_IMMUTABLE 플래그를 설정해야 합니다. 이는 런타임에 irq_chip이 수정되는 것을 방지합니다. 또한 gpiochip_enable_irq()gpiochip_disable_irq()를 unmask/mask 콜백에서 호출하고, GPIOCHIP_IRQ_RESOURCE_HELPERS 매크로(Macro)를 포함해야 합니다.

Nested (Threaded) IRQ 패턴

I2C/SPI GPIO expander처럼 슬립(Sleep) 가능한 버스 뒤에 있는 GPIO 컨트롤러는 chained handler를 사용할 수 없습니다(인터럽트 컨텍스트에서 I2C/SPI 전송 불가). 이 경우 nested (threaded) IRQ 패턴을 사용합니다:

/* I2C GPIO expander의 threaded IRQ 패턴 */
girq = &priv->gc.irq;
gpio_irq_chip_set_chip(girq, &my_expander_irqchip);

/* parent_handler를 NULL로 설정하면 nested(threaded) IRQ 사용 */
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_bad_irq;  /* 직접 호출되면 안 됨 */
girq->threaded = true;           /* 핵심: threaded IRQ 사용 */

/* 부모 IRQ를 threaded handler로 직접 등록 */
ret = devm_request_threaded_irq(&client->dev, client->irq,
        NULL, my_expander_irq_thread,
        IRQF_ONESHOT | IRQF_SHARED,
        "my-expander", priv);

/* threaded IRQ handler에서 I2C 통신으로 상태 확인 */
static irqreturn_t my_expander_irq_thread(int irq, void *data)
{
    struct my_gpio_irq *priv = data;
    u32 pending;

    /* I2C/SPI 통신으로 인터럽트 상태 레지스터 읽기 (sleep 가능) */
    pending = i2c_smbus_read_byte_data(priv->client, INT_STATUS_REG);

    while (pending) {
        int hwirq = __ffs(pending);
        handle_nested_irq(irq_find_mapping(
            priv->gc.irq.domain, hwirq));
        pending &= ~BIT(hwirq);
    }
    return IRQ_HANDLED;
}
chained vs nested IRQ 선택 기준: SoC 내장 GPIO 컨트롤러(MMIO 접근)는 chained handler를 사용합니다. I2C/SPI GPIO expander처럼 슬립 가능한 버스 뒤의 컨트롤러는 nested (threaded) handler를 사용합니다. chained handler는 하드 IRQ 컨텍스트에서 실행되므로 더 빠르지만, 슬립이 불가능합니다.

pinctrl: 핀 멀티플렉싱

pinctrl 서브시스템은 SoC의 핀 멀티플렉싱(pinmux)과 핀 설정(pinconf)을 관리합니다. GPIO 서브시스템과 밀접하게 연동되며, 하나의 물리 핀이 GPIO, I2C SDA, SPI MOSI 등 여러 기능 중 하나로 설정될 수 있습니다.

pinctrl ↔ GPIO 서브시스템 연동 SoC 물리 핀 (PA0, PA1, PA2, ... PB0, PB1, ...) 각 핀은 여러 기능(Function) 중 하나로 설정 가능 pinctrl 서브시스템 pinmux 기능 선택 pinconf 풀업/강도/슬루율 pinctrl_state default/sleep/idle PA9 → i2c1_sda | gpio | spi1_mosi | uart2_tx (pinmux가 하나를 선택) GPIO 서브시스템 gpiolib gpio_desc 관리 gpio_chip get/set/direction gpio_irq_chip 인터럽트 지원 gpiod_get() → pinctrl_gpio_request() 자동 호출 (핀을 GPIO 기능으로 mux 설정) GPIO ↔ pinctrl 자동 연동 포인트 gpiod_get() / gpiod_request() → pinctrl_gpio_request(offset) gpiod_set_config() → pinctrl_gpio_set_config(offset) gpiod_free() / devm cleanup → pinctrl_gpio_free(offset)

pinctrl 핵심 개념

개념설명예시
Pin Group함께 설정되는 핀 그룹i2c1_pins: {SDA, SCL}
Function핀 그룹이 수행하는 기능i2c, spi, gpio, uart
pinmux핀과 기능의 매핑PA9 → I2C1_SDA
pinconf핀 전기적 특성 설정풀업, 드라이브 강도, 슬루율
State디바이스 상태별 핀 설정default, sleep, idle

Device Tree pinctrl 바인딩

/* SoC pinctrl 노드에서 핀 설정 정의 */
&pinctrl {
    i2c1_default: i2c1-default-pins {
        pins = "PA9", "PA10";
        function = "i2c1";
        bias-pull-up;
        drive-open-drain;
    };

    i2c1_sleep: i2c1-sleep-pins {
        pins = "PA9", "PA10";
        function = "gpio";
        bias-high-impedance;
    };

    spi1_default: spi1-default-pins {
        mosi-sck-pins {
            pins = "PB3", "PB5";
            function = "spi1";
            bias-disable;
            drive-push-pull;
            slew-rate = <1>;  /* high speed */
        };
        miso-pin {
            pins = "PB4";
            function = "spi1";
            bias-pull-down;
        };
    };

    user_led_pin: user-led-pin {
        pins = "PC13";
        function = "gpio";
        drive-push-pull;
        output-low;
    };
};

/* 디바이스 노드에서 pinctrl 상태 참조 */
&i2c1 {
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&i2c1_default>;
    pinctrl-1 = <&i2c1_sleep>;
    status = "okay";
};

&spi1 {
    pinctrl-names = "default";
    pinctrl-0 = <&spi1_default>;
    status = "okay";
};
pinctrl 자동 전환: 디바이스가 pm_runtime_suspend()에 들어가면 커널이 자동으로 "sleep" 상태의 핀 설정을 적용하고, resume 시 "default"로 복원합니다. 이 동작은 pinctrl-names에 "default"와 "sleep"이 정의되어 있을 때 활성화됩니다.

pinctrl 드라이버 구현 핵심

GPIO 컨트롤러와 pinctrl을 통합하는 SoC 드라이버의 핵심 구조를 보여줍니다. 대부분의 SoC GPIO 드라이버는 gpio_chippinctrl_desc를 모두 등록합니다:

#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinconf-generic.h>

/* 핀 정의 */
static const struct pinctrl_pin_desc my_pins[] = {
    PINCTRL_PIN(0, "PA0"),
    PINCTRL_PIN(1, "PA1"),
    /* ... */
    PINCTRL_PIN(31, "PA31"),
};

/* 그룹 정의: 함께 mux되는 핀 집합 */
static const unsigned int i2c1_pins[] = { 9, 10 };
static const unsigned int spi1_pins[] = { 3, 4, 5 };

/* pinmux ops: 핀 기능 선택 */
static int my_pmx_set_mux(struct pinctrl_dev *pctldev,
                           unsigned int func_selector,
                           unsigned int group_selector)
{
    /* SoC mux 레지스터에 기능 번호 기록 */
    writel(func_selector, priv->base + MUX_REG(group_selector));
    return 0;
}

/* GPIO 요청 시 핀을 GPIO 기능으로 전환 */
static int my_pmx_gpio_request_enable(struct pinctrl_dev *pctldev,
                                       struct pinctrl_gpio_range *range,
                                       unsigned int offset)
{
    /* 해당 핀의 mux를 GPIO 기능(보통 0)으로 설정 */
    writel(0, priv->base + MUX_REG(offset));
    return 0;
}

static const struct pinmux_ops my_pmx_ops = {
    .get_functions_count = my_pmx_get_funcs_count,
    .get_function_name   = my_pmx_get_func_name,
    .get_function_groups = my_pmx_get_func_groups,
    .set_mux             = my_pmx_set_mux,
    .gpio_request_enable = my_pmx_gpio_request_enable,
    .strict              = true,  /* GPIO와 다른 기능 동시 사용 불가 */
};

/* pinconf ops: 전기적 특성 설정 */
static int my_pinconf_set(struct pinctrl_dev *pctldev,
                          unsigned int pin,
                          unsigned long *configs,
                          unsigned int num_configs)
{
    for (int i = 0; i < num_configs; i++) {
        u32 param = pinconf_to_config_param(configs[i]);
        u32 arg   = pinconf_to_config_argument(configs[i]);

        switch (param) {
        case PIN_CONFIG_BIAS_PULL_UP:
            my_set_pullup(priv, pin, arg);
            break;
        case PIN_CONFIG_BIAS_PULL_DOWN:
            my_set_pulldown(priv, pin, arg);
            break;
        case PIN_CONFIG_DRIVE_STRENGTH:
            my_set_drive(priv, pin, arg);
            break;
        case PIN_CONFIG_INPUT_DEBOUNCE:
            my_set_debounce(priv, pin, arg);
            break;
        }
    }
    return 0;
}
strict 모드: pinmux_ops.strict = true로 설정하면 하나의 핀이 GPIO와 다른 기능(I2C, SPI 등)에 동시에 할당되는 것을 방지합니다. 대부분의 SoC에서 물리적으로 불가능하므로 strict 모드가 권장됩니다.

GPIO 전원 관리(Power Management)

시스템 절전(Suspend) 시 GPIO 상태를 올바르게 관리하는 것이 중요합니다. 특히 wakeup 소스로 사용되는 GPIO 인터럽트와 suspend/resume 시 핀 상태 보존에 주의해야 합니다.

GPIO Wakeup 소스

/* GPIO를 wakeup 소스로 설정 */
int irq = gpiod_to_irq(button_gpio);

devm_request_threaded_irq(dev, irq, NULL, button_handler,
        IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "wakeup-button", data);

/* 이 IRQ를 시스템 wakeup 소스로 활성화 */
device_init_wakeup(dev, true);
dev_pm_set_wake_irq(dev, irq);

/* 또는 enable_irq_wake() 직접 사용 */
enable_irq_wake(irq);

Suspend/Resume 핀 상태

시나리오suspend 동작resume 동작구현 방법
출력 GPIO (LED 등)현재 값 유지 또는 끔이전 값 복원드라이버 suspend/resume 콜백에서 처리
Wakeup GPIOIRQ 활성 유지IRQ 처리 후 복귀enable_irq_wake()
pinctrl sleep 상태sleep 핀 설정 적용default 핀 설정 복원DT pinctrl-names = "default", "sleep"
GPIO Expander (I2C)I2C 버스 비활성화 전 설정 저장I2C 버스 활성화 후 설정 복원regmap_cache 활용
/* GPIO expander 드라이버의 suspend/resume 예시 */
static int my_gpio_exp_suspend(struct device *dev)
{
    struct my_gpio_exp *priv = dev_get_drvdata(dev);

    /* regmap 캐시에 현재 레지스터 값 보존 */
    regcache_cache_only(priv->regmap, true);
    regcache_mark_dirty(priv->regmap);
    return 0;
}

static int my_gpio_exp_resume(struct device *dev)
{
    struct my_gpio_exp *priv = dev_get_drvdata(dev);

    /* 캐시된 값을 하드웨어에 복원 */
    regcache_cache_only(priv->regmap, false);
    return regcache_sync(priv->regmap);
}

static DEFINE_SIMPLE_DEV_PM_OPS(my_gpio_exp_pm,
        my_gpio_exp_suspend, my_gpio_exp_resume);
GPIO expander resume 순서: I2C/SPI GPIO expander의 resume은 버스 컨트롤러가 먼저 활성화된 후에 실행되어야 합니다. dev_pm_domain_attach() 또는 DT의 power-domains로 의존성을 명시하세요. Resume 순서가 잘못되면 I2C 전송이 실패하여 GPIO 상태 복원에 실패합니다.

GPIO 테스트와 시뮬레이션

실제 하드웨어 없이 GPIO 드라이버를 개발하고 테스트하는 방법입니다.

gpio-sim: 가상 GPIO 컨트롤러

gpio-sim 모듈(v5.17+)은 configfs를 통해 가상 GPIO 컨트롤러를 생성합니다:

# gpio-sim 모듈 로드
modprobe gpio-sim

# configfs에서 가상 GPIO 칩 생성
mkdir -p /sys/kernel/config/gpio-sim/my-test-chip
mkdir -p /sys/kernel/config/gpio-sim/my-test-chip/bank0

# 8개 라인, 라벨 설정
echo 8 > /sys/kernel/config/gpio-sim/my-test-chip/bank0/num_lines
echo "test-line-0" > /sys/kernel/config/gpio-sim/my-test-chip/bank0/line0/name
echo "test-line-1" > /sys/kernel/config/gpio-sim/my-test-chip/bank0/line1/name

# 활성화 → /dev/gpiochipN 생성
echo 1 > /sys/kernel/config/gpio-sim/my-test-chip/live

# 외부에서 핀 값 주입 (입력 시뮬레이션)
echo 1 > /sys/kernel/config/gpio-sim/my-test-chip/bank0/line0/pull

# libgpiod로 확인
gpioget $(gpiodetect | grep gpio-sim | awk '{print $1}') 0

# 정리
echo 0 > /sys/kernel/config/gpio-sim/my-test-chip/live
rmdir /sys/kernel/config/gpio-sim/my-test-chip/bank0/line*/
rmdir /sys/kernel/config/gpio-sim/my-test-chip/bank0
rmdir /sys/kernel/config/gpio-sim/my-test-chip
CI/CD에서 GPIO 테스트: gpio-simgpio-aggregator를 조합하면 실제 하드웨어 없이도 GPIO 드라이버의 기능을 자동화 테스트할 수 있습니다. 커널의 tools/testing/selftests/gpio/에 셀프 테스트 예제가 있습니다.

Device Tree 통합: GPIO 바인딩 패턴

GPIO 서브시스템의 Device Tree 바인딩에서 공통적으로 사용되는 패턴을 정리합니다.

GPIO 관련 공통 프로퍼티

프로퍼티적용 대상설명
compatible모든 디바이스드라이버 매칭 문자열 (vendor,device)
gpio-controllerGPIO 컨트롤러이 노드가 GPIO 제공자임을 표시
#gpio-cellsGPIO 컨트롤러GPIO specifier 셀 수 (보통 2)
*-gpiosGPIO 사용 디바이스GPIO specifier (phandle + 번호 + 플래그)
interrupts인터럽트 사용 디바이스IRQ 스펙
interrupt-controllerIRQ 지원 GPIO 컨트롤러인터럽트 컨트롤러(Interrupt Controller) 표시
pinctrl-*핀 설정 필요 디바이스pinctrl 상태

종합 예제: GPIO LED/버튼 + GPIO Expander

실제 임베디드 보드에서 GPIO LED, 버튼, GPIO Expander를 함께 사용하는 Device Tree 예제:

/ {
    model = "My Custom Board";
    compatible = "vendor,my-board";

    leds {
        compatible = "gpio-leds";
        pinctrl-names = "default";
        pinctrl-0 = <&user_led_pin>;

        led-status {
            gpios = <&gpioc 13 GPIO_ACTIVE_LOW>;
            label = "board:green:status";
            linux,default-trigger = "heartbeat";
        };
    };

    gpio-keys {
        compatible = "gpio-keys";

        button-user {
            label = "User Button";
            gpios = <&gpioa 0 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_ENTER>;
            debounce-interval = <20>;
        };
    };
};

&i2c1 {
    status = "okay";

    /* GPIO expander */
    gpio_exp: gpio@20 {
        compatible = "nxp,pca9555";
        reg = <0x20>;
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-parent = <&gpiob>;
        interrupts = <8 IRQ_TYPE_EDGE_FALLING>;
        interrupt-controller;
        #interrupt-cells = <2>;
    };
};

GPIO 디버깅(Debugging)

GPIO 관련 Device Tree 문제를 디버깅하는 방법:

# GPIO 상태 확인
gpiodetect                        # GPIO 칩 목록
gpioinfo                          # 모든 GPIO 라인 정보
cat /sys/kernel/debug/gpio        # debugfs GPIO 상태
cat /sys/kernel/debug/pinctrl/*/pins  # pinctrl 핀 매핑

# Device Tree 런타임 확인
ls /proc/device-tree/             # DT 노드 트리
dtc -I fs /proc/device-tree/      # 런타임 DT를 DTS로 디컴파일
GPIO가 동작하지 않을 때 체크리스트: (1) gpioinfo로 라인 상태 확인 (사용 중인지, 방향이 올바른지), (2) dmesg | grep gpio로 GPIO 컨트롤러 등록 확인, (3) Device Tree의 *-gpios 속성이 올바른 컨트롤러, 오프셋(Offset), 플래그를 지정하는지 확인, (4) pinctrl 설정이 올바른지 확인 (핀이 GPIO 기능으로 mux 되었는지), (5) active-low/active-high 플래그가 하드웨어 회로와 일치하는지 확인.

커널 6.x GPIO 최신 동향 (2025-2026)

Linux 6.6 LTS부터 6.18까지 GPIO 서브시스템에는 gpio-cdev v2 ABI 정착, gpio-aggregator의 configfs 인터페이스, GPIO 라인 이벤트의 하드웨어 타임스탬프(High-resolution Timer Engine, HTE) 지원 같은 변화가 누적되었습니다. Bartosz Golaszewski가 메인테이너입니다.

커널 버전변경사항영향
6.7gpio-aggregator sysfs 동적 묶음 생성, libgpiod 2.x 호환 안정화가상 GPIO 칩 동적 구성
6.10Renesas RZ/V2H pinctrl 머지차세대 임베디드 SoC
6.12Snapdragon X Elite(X1E80100), MediaTek MT8196 pinctrlARM 노트북 지원
6.13GPIO line event timestamp가 CLOCK_HARDWARE_TIMESTAMP 기반(PTP 동기화) 지원1ns 분해능 입력 캡처
6.14gpio-aggregator에 configfs 인터페이스 추가runtime 가상 칩 생성
6.15NXP i.MX95 pinctrl 머지, GPIO v1 ABI deprecated 경고v2 마이그레이션 권장
6.16Rockchip RK3576 pinctrl, GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE 정착NVIDIA Tegra234 GTE 1ns 캡처
6.17gpio-sim 양방향 시뮬레이션과 trigger 주입, AMD Ryzen AI 300 GPIO race fixCI 자동화 테스트
6.18gpio-aggregator의 nested aggregation 안정화다단 가상 GPIO 매핑

gpio-cdev v2 ABI와 하드웨어 타임스탬프 (HTE)

v2 ABI(GPIO_V2_LINE_*)는 6.6 시점 이미 안정화되어 libgpiod 2.x와 호환되며, 6.15에서 v1 ABI(linehandle, lineevent)는 deprecated 경고가 붙기 시작했습니다. 6.13에서 추가된 GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE 플래그는 NVIDIA Tegra234의 GTE(GPIO Timestamp Engine) 같은 하드웨어가 GPIO edge를 1ns 분해능으로 직접 타임스탬프하도록 합니다. 카메라 동기화, 모터 인코더, 정밀 측정 시나리오에서 결정적 타이밍을 제공합니다.

/* libgpiod 2.x로 HTE 타임스탬프 활성화 */
struct gpiod_line_settings *settings = gpiod_line_settings_new();
gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
gpiod_line_settings_set_edge_detection(settings, GPIOD_LINE_EDGE_BOTH);
gpiod_line_settings_set_event_clock(settings, GPIOD_LINE_CLOCK_HTE);  /* HTE */

/* 이벤트 폴링 */
struct gpiod_edge_event_buffer *buffer = gpiod_edge_event_buffer_new(64);
gpiod_line_request_read_edge_events(req, buffer, 16);

gpio-aggregator와 gpio-sim — 가상 칩 동적 생성

drivers/gpio/gpio-aggregator.c는 다른 GPIO 컨트롤러의 라인을 모아 새 가상 GPIO 칩을 만듭니다. 6.7의 sysfs(new_device/delete_device)에 이어 6.14에서 configfs 인터페이스가 추가되어, runtime에 컨테이너처럼 GPIO 라인을 묶거나 풀 수 있습니다. 컨테이너/Kubernetes 환경에서 호스트의 GPIO 일부만 워크로드에 노출하는 시나리오에 유용합니다.

# configfs로 가상 GPIO 칩 생성 (6.14+) 
$ mount -t configfs none /sys/kernel/config
$ mkdir /sys/kernel/config/gpio-aggregator/agg0
$ echo "gpiochip0 0,5,8 gpiochip2 1-3" > /sys/kernel/config/gpio-aggregator/agg0/lines
$ echo 1 > /sys/kernel/config/gpio-aggregator/agg0/live
# 새 /dev/gpiochipN 디바이스가 생성됨

drivers/gpio/gpio-sim.c는 6.17에서 양방향 시뮬레이션과 trigger event 주입을 지원하여, GPIO 기반 드라이버를 실제 하드웨어 없이 CI 파이프라인에서 테스트할 수 있습니다. KUnit 테스트와 결합하여 BSP(Board Support Package) 회귀 검증을 자동화합니다.

AMD Ryzen AI 300 GPIO 인터럽트 race 수정 (6.17)

실제 사례: AMD Ryzen AI 300 시리즈 노트북에서 터치패드 wakeup이 간헐적으로 실패하는 race condition이 보고되었습니다. drivers/pinctrl/pinctrl-amd.c의 wake interrupt 마스크 처리에 lockless가 부족하여 suspend 직전 edge가 손실되었습니다. 6.17에서 amd_gpio_irq_* 함수들이 spinlock 영역을 재정비하여 fix되었으며, 6.16.x stable 시리즈에 백포팅되었습니다.

참고자료

커널 공식 문서

커널 소스 코드

외부 자료

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.