Embedded System/Bootloader & System Startup

임베디드 리눅스 U-Boot 드라이버 모델(DM): I2C 드라이버 구현 가이드

임베디드 친구 2026. 5. 10. 15:09
반응형

임베디드 시스템이 고도화되면서 하드웨어 제어 방식에도 변화가 필요해졌습니다. 과거의 임베디드 드라이버는 특정 하드웨어 레지스터에 직접 접근하거나 전역 함수를 호출하는 방식이 주를 이루었지만, 이는 코드의 의존성을 높이고 유지보수를 어렵게 만드는 원인이 되었습니다. 이러한 문제를 해결하기 위해 도입된 것이 바로 드라이버 모델(Driver Model, DM)입니다. 이번 글에서는 U-Boot의 표준으로 자리 잡은 DM 기반 I2C 드라이버의 구조와 실제 구현 방법을 실무적인 관점에서 정리해 보겠습니다.

Generated by Gemini AI.

핵심 요약

  • 드라이버 모델(DM)은 드라이버와 하드웨어를 분리하여 유지보수성과 확장성을 높이며, 디바이스 트리(Device Tree)를 통해 하드웨어 구성을 유연하게 관리합니다.
  • DM I2C 드라이버는 struct dm_i2c_ops를 통해 통신 로직을 구현하고 U_BOOT_DRIVER 매크로를 통해 시스템에 등록하는 구조를 가집니다.
  • dm tree 명령어와 디바이스 트리 설정을 활용하면 드라이버 바인딩 상태를 쉽게 점검하고 시스템 변경에 대응할 수 있습니다.

1. DM I2C 드라이버의 핵심 구성 요소

DM 체계에서 I2C 드라이버를 작성하려면 드라이버의 동작을 정의하는 인터페이스와 시스템 등록을 위한 메타데이터가 필요합니다.

구성 요소 역할
struct dm_i2c_ops 데이터 전송(xfer), 속도 설정(set_speed) 등 하드웨어 제어 로직 정의
U_BOOT_DRIVER 드라이버 이름, ID, ops 등을 시스템에 등록하는 매크로
Device Tree 하드웨어 사양과 드라이버를 매칭하는 연결 고리

2. 구현 코드: I2C Operations 정의

하드웨어가 실제 데이터를 주고받는 로직을 dm_i2c_ops 구조체에 구현해야 합니다.

C
 
/* I2C 통신을 위한 실제 동작 정의 */
static int my_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, int nmsgs) {
    /* 하드웨어 레지스터 제어 로직 구현 */
    return 0;
}

static int my_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) {
    /* 클럭 제어 로직 구현 */
    return 0;
}

/* Operations 구조체 연결 */
static const struct dm_i2c_ops my_i2c_ops = {
    .xfer           = my_i2c_xfer,
    .set_bus_speed  = my_i2c_set_bus_speed,
};

3. 드라이버 등록 및 매칭

작성한 드라이버가 디바이스 트리의 compatible 문자열과 매칭되도록 설정합니다.

C
 
static const struct udevice_id my_i2c_ids[] = {
    { .compatible = "myvendor,my-i2c-controller" },
    { }
};

U_BOOT_DRIVER(my_i2c_drv) = {
    .name   = "my_i2c_drv",
    .id     = UCLASS_I2C,        /* I2C 클래스로 등록 */
    .of_match = my_i2c_ids,
    .ops    = &my_i2c_ops,
    .priv_auto = sizeof(struct my_i2c_priv),
};

4. 디바이스 트리(DTS) 설정 예시

보드 설정 파일(.dts)에서 해당 컨트롤러를 활성화하고, 하위 장치를 명시합니다.

DTS
 
&i2c0 {
    compatible = "myvendor,my-i2c-controller";
    reg = <0x12340000 0x1000>;
    status = "okay";
    clock-frequency = <400000>;

    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
    };
};

5. 개발을 위한 팁

  • 명령어 활용: 디버깅 시 U-Boot 콘솔에서 dm tree 명령어를 사용하세요. 작성한 I2C 컨트롤러가 UCLASS_I2C 아래에 바인딩되었는지, 혹시 prob 단계에서 에러가 발생했는지 즉시 확인할 수 있습니다.
  • 에러 코드 준수: 통신 실패 시 단순히 0을 반환하지 말고 -ETIMEDOUT, -EIO 등 표준 에러 코드를 반환하세요. 그래야 상위 계층에서 어떤 이유로 통신이 실패했는지 인지하고 재시도 로직을 가동할 수 있습니다.

6. 흔히 하는 실수

  • Probe 순서 누락: probe 함수를 구현할 때 클럭 초기화와 핀 매핑(Pinmux)이 선행되지 않으면 하드웨어 동작이 보장되지 않습니다. 초기화 순서를 항상 확인하세요.
  • 메모리 할당 오류: priv_auto를 사용하여 드라이버 내부 데이터를 관리할 때, 데이터 크기 계산을 잘못하여 메모리 오버런이 발생하는 경우가 잦습니다. 구조체 크기를 항상 정확히 지정하세요.

결론

DM 기반 드라이버는 구현 초기에는 구조가 낯설고 복잡해 보일 수 있습니다. 하지만 드라이버와 하드웨어를 분리해 설계하는 방식은 시스템 규모가 커질수록 압도적인 유지보수 효율을 제공합니다. 오늘 다룬 구조를 바탕으로 디바이스 트리를 활용한 드라이버 개발에 익숙해진다면, 다양한 하드웨어 변경에도 유연하게 대처하는 역량 있는 임베디드 개발자가 될 것입니다.

반응형