GPIO 서브시스템 — GPIO / pinctrl
GPIO(General-Purpose Input/Output) 서브시스템의 아키텍처와 gpiod descriptor 기반 안전한 제어 패턴, gpio_chip 드라이버 구현, libgpiod 유저스페이스 접근, GPIO Expander, GPIO IRQ 컨트롤러(irqchip), pinctrl 핀 멀티플렉싱, Device Tree 바인딩까지 GPIO 관련 드라이버 개발에 필요한 핵심을 다룹니다.
핵심 요약
- gpiod API 사용 — Legacy 정수 기반 API 대신 descriptor 기반 gpiod API를 사용합니다.
- active-low 자동 처리 — gpiod_set_value()가 Device Tree의 GPIO_ACTIVE_LOW 플래그를 자동 반영합니다.
- gpio_chip 구현 — GPIO 컨트롤러 드라이버는 gpio_chip 구조체를 등록합니다.
- IRQ 통합 — GPIO 핀을 인터럽트 소스로 사용할 때 gpio_irq_chip을 활용합니다.
- pinctrl 연동 — 핀 멀티플렉싱과 전기적 특성은 pinctrl 서브시스템과 협력합니다.
단계별 이해
- GPIO 서브시스템 아키텍처 파악
gpiolib, gpio_chip, gpiod 계층 구조를 이해합니다. - gpiod API로 GPIO 제어
descriptor 기반 획득, 값 읽기/쓰기, IRQ 변환을 익힙니다. - gpio_chip 드라이버 작성
GPIO 컨트롤러를 커널에 등록하는 방법을 학습합니다. - GPIO IRQ와 pinctrl 통합
인터럽트 지원과 핀 설정을 마무리합니다.
GPIO 개요
GPIO (General-Purpose Input/Output)는 소프트웨어로 제어 가능한 범용 디지털 핀입니다. LED, 버튼, 리셋 라인, 칩 셀렉트, 인터럽트 입력 등 다양한 용도로 사용됩니다.
Linux GPIO 서브시스템은 drivers/gpio/에 구현되며, 크게 두 가지 API가 있습니다:
| API | 헤더 | 상태 | 특징 |
|---|---|---|---|
| Legacy (integer-based) | <linux/gpio.h> | Deprecated | gpio_request(), gpio_direction_input() |
| Descriptor-based (gpiod) | <linux/gpio/consumer.h> | 현재 표준 | gpiod_get(), gpiod_set_value() |
gpio_request(), gpio_free(), gpio_get_value() 등 정수 기반 legacy API를 사용하지 마세요. 커널 메인라인에서는 legacy GPIO API를 사용하는 새 드라이버를 받아들이지 않습니다.
GPIO 서브시스템 아키텍처
Linux GPIO 서브시스템은 하드웨어 GPIO 컨트롤러부터 유저스페이스 접근까지 여러 계층으로 구성됩니다. gpiolib이 핵심 프레임워크 역할을 하며, gpio_chip이 하드웨어 추상화를, gpiod가 소비자(consumer) API를 제공합니다.
GPIO 하드웨어 내부 구조
물리적 GPIO 핀은 여러 전기적 설정을 지원하며, 드라이버에서 이를 올바르게 구성하는 것이 중요합니다:
| 설정 | 설명 | 커널 API / DT 속성 | 용도 |
|---|---|---|---|
| Push-Pull | HIGH/LOW 모두 능동적으로 구동 | 기본 출력 모드 | LED 제어, 리셋 라인 |
| Open-Drain | LOW만 능동 구동, HIGH는 풀업 의존 | GPIO_OPEN_DRAIN / drive-open-drain | I2C SDA/SCL, 인터럽트 라인 |
| Open-Source | HIGH만 능동 구동, 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 서브시스템과 밀접하게 연관됩니다. 대부분의 SoC에서 GPIO 핀은 pinctrl 핀과 1:1 매핑되며, pinctrl_gpio_set_config()를 통해 gpiolib에서 pinctrl로 설정이 전달됩니다.
gpiod API (Descriptor-based)
현대 Linux 커널의 표준 GPIO 인터페이스인 gpiod API를 사용합니다:
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()는 Device Tree의 GPIO_ACTIVE_LOW 플래그를 자동 반영합니다. gpiod_set_raw_value()는 물리적 라인 레벨을 직접 제어합니다. 일반적으로 gpiod_set_value()를 사용하세요.
GPIO 인터럽트 흐름
GPIO를 인터럽트 소스로 사용하는 경우, GPIO 서브시스템과 IRQ 서브시스템이 협력하여 핀 상태 변화를 커널 인터럽트로 변환합니다. 아래 다이어그램은 GPIO 인터럽트의 전체 처리 흐름을 보여줍니다.
GPIO Debounce (글리치 필터링)
기계식 버튼이나 스위치는 접점 바운싱으로 인해 한 번의 누름에 여러 번의 에지 변화가 발생합니다. Debounce는 이러한 글리치를 필터링하여 깨끗한 신호를 제공합니다.
| 방식 | 구현 | 지연 | 장점 | 단점 |
|---|---|---|---|---|
| HW Debounce | SoC GPIO 컨트롤러 내장 필터 | HW에서 설정 (수십 us ~ 수 ms) | CPU 부하 없음, 정밀한 타이밍 | 모든 SoC 지원 아님 |
| SW Debounce | gpiolib hrtimer 기반 | gpiod_set_debounce() | 모든 GPIO에 적용 가능 | CPU 오버헤드, 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 컨트롤러를 생성할 수 있습니다. 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_chip 구현
GPIO 컨트롤러 드라이버를 작성하려면 gpio_chip 구조체를 구현하고 등록합니다:
#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 인터페이스를 대체합니다.
/sys/class/gpio/export 인터페이스는 deprecated 상태입니다. 새 프로젝트에서는 chardev 기반(/dev/gpiochipN) 접근을 권장하며, 가능한 경우 libgpiod(v2 이상)를 사용하세요.
libgpiod 명령행 도구
| 도구 | 용도 | 예시 |
|---|---|---|
gpiodetect | 시스템의 GPIO 칩 목록 | gpiodetect |
gpioinfo | GPIO 라인 상세 정보 | gpioinfo gpiochip0 |
gpioget | GPIO 입력 값 읽기 | gpioget gpiochip0 7 |
gpioset | GPIO 출력 값 설정 | gpioset gpiochip0 7=1 |
gpiomon | GPIO 이벤트 모니터링 | 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 수 | 인터럽트 | 커널 드라이버 |
|---|---|---|---|---|
| MCP23017 | I2C | 16 | 지원 | gpio-mcp23s08 |
| MCP23S17 | SPI | 16 | 지원 | gpio-mcp23s08 |
| PCA9555 | I2C | 16 | 지원 | gpio-pca953x |
| PCA9535 | I2C | 16 | 지원 | gpio-pca953x |
| PCF8574 | I2C | 8 | 지원 | gpio-pcf857x |
| TCA6424A | I2C | 24 | 지원 | 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";
};
};
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 |
| Hierarchical | GPIO IRQ → 상위 IRQ 컨트롤러(GIC 등)에 계층적 매핑 | 부모 IRQ 컨트롤러가 별도 존재하는 경우 |
gpio_chip irqchip 구현
현대 커널(v5.10+)에서는 gpio_irq_chip을 gpio_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);
}
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처럼 슬립 가능한 버스 뒤에 있는 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;
}
pinctrl: 핀 멀티플렉싱
pinctrl 서브시스템은 SoC의 핀 멀티플렉싱(pinmux)과 핀 설정(pinconf)을 관리합니다. GPIO 서브시스템과 밀접하게 연동되며, 하나의 물리 핀이 GPIO, I2C SDA, SPI MOSI 등 여러 기능 중 하나로 설정될 수 있습니다.
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";
};
pm_runtime_suspend()에 들어가면 커널이 자동으로 "sleep" 상태의 핀 설정을 적용하고, resume 시 "default"로 복원합니다. 이 동작은 pinctrl-names에 "default"와 "sleep"이 정의되어 있을 때 활성화됩니다.
Device Tree 통합: GPIO 바인딩 패턴
GPIO 서브시스템의 Device Tree 바인딩에서 공통적으로 사용되는 패턴을 정리합니다.
GPIO 관련 공통 프로퍼티
| 프로퍼티 | 적용 대상 | 설명 |
|---|---|---|
compatible | 모든 디바이스 | 드라이버 매칭 문자열 (vendor,device) |
gpio-controller | GPIO 컨트롤러 | 이 노드가 GPIO 제공자임을 표시 |
#gpio-cells | GPIO 컨트롤러 | GPIO specifier 셀 수 (보통 2) |
*-gpios | GPIO 사용 디바이스 | GPIO specifier (phandle + 번호 + 플래그) |
interrupts | 인터럽트 사용 디바이스 | IRQ 스펙 |
interrupt-controller | IRQ 지원 GPIO 컨트롤러 | 인터럽트 컨트롤러 표시 |
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 디버깅
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로 디컴파일
gpioinfo로 라인 상태 확인 (사용 중인지, 방향이 올바른지),
(2) dmesg | grep gpio로 GPIO 컨트롤러 등록 확인,
(3) Device Tree의 *-gpios 속성이 올바른 컨트롤러, 오프셋, 플래그를 지정하는지 확인,
(4) pinctrl 설정이 올바른지 확인 (핀이 GPIO 기능으로 mux 되었는지),
(5) active-low/active-high 플래그가 하드웨어 회로와 일치하는지 확인.
참고자료
커널 공식 문서
- GPIO Subsystem — GPIO 서브시스템 문서
- GPIO Consumer Interface — GPIO 소비자 인터페이스 (gpiod API)
- GPIO Driver Interface — GPIO 칩 드라이버 인터페이스
- GPIO Mappings — GPIO 매핑 (보드 레벨)
- Using GPIO — GPIO 사용 가이드
- Pin Control Subsystem — Pin Control 서브시스템
- GPIO Userspace ABI — GPIO 유저스페이스 ABI (chardev)
커널 소스 코드
drivers/gpio/gpiolib.c— GPIO 라이브러리 코어drivers/gpio/gpiolib-cdev.c— GPIO Character Device (v2 ABI)include/linux/gpio/consumer.h— GPIO consumer API (gpiod_*)include/linux/gpio/driver.h— GPIO 칩 드라이버 인터페이스drivers/pinctrl/core.c— pinctrl 코어 구현include/linux/pinctrl/pinctrl.h— pinctrl 디스크립터include/linux/pinctrl/pinmux.h— 핀 멀티플렉싱include/linux/pinctrl/pinconf.h— 핀 설정tools/gpio/— GPIO 유저스페이스 도구 (lsgpio, gpio-event-mon 등)
외부 자료
- GPIO in the kernel: an introduction — 커널 GPIO 소개
- The pin control subsystem — pin control 서브시스템 해설
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.