커널 버스 서브시스템 심화

Linux 커널 버스 서브시스템을 장치 열거, 식별, 바인딩, 전원 상태 전이의 공통 규약 관점에서 심층 분석합니다. PCI/PCIe, USB, SPI/I2C, AMBA, MFD, Regmap, MDIO, SDIO의 버스별 특성과 driver-model 접점, probe/defer/remove 순서, 버스별 주소 체계와 리소스 할당, Device Tree/ACPI 기술 정보 해석, 버스 오류 복구와 재탐색 전략, 성능 병목 관찰 지점까지 멀티버스 환경에서 안정적으로 드라이버를 운영하는 방법을 다룹니다.

전제 조건: 디바이스 드라이버인터럽트 문서를 먼저 읽으세요. 버스/열거/프로브 경로는 초기화 순서와 자원 등록 규칙이 핵심이므로, 장치 발견부터 바인딩까지 흐름을 먼저 고정해야 합니다.
일상 비유: 이 주제는 터미널 입출고 게이트 운영과 비슷합니다. 차량(디바이스)이 들어오면 게이트 규칙(버스 규약)에 맞춰 배정하고 점검하듯이, 드라이버도 바인딩 규약을 정확히 따라야 합니다.

핵심 요약

  • 초기화 순서 — 탐색, 바인딩, 자원 등록 순서를 점검합니다.
  • 제어/데이터 분리 — 빠른 경로와 설정 경로를 분리 설계합니다.
  • IRQ/작업 분할 — 즉시 처리와 지연 처리를 구분합니다.
  • 안전 한계 — 전원/열/타이밍 임계값을 함께 관리합니다.
  • 운영 복구 — 오류 시 재초기화와 롤백 경로를 준비합니다.

단계별 이해

  1. 장치 수명주기 확인
    probe부터 remove까지 흐름을 점검합니다.
  2. 비동기 경로 설계
    IRQ, 워크큐, 타이머 역할을 분리합니다.
  3. 자원 정합성 검증
    DMA/클록/전원 참조를 교차 확인합니다.
  4. 현장 조건 테스트
    연결 끊김/복구/부하 상황을 재현합니다.
관련 표준: PCI Local Bus Spec 3.0, USB 3.2 Spec, I2C-bus Specification, SPI Protocol — 버스 프로토콜 표준 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
관련 페이지: 기본 디바이스 드라이버 모델(bus_type, device, driver, class)은 디바이스 드라이버 페이지를 참고하세요.

커널 버스 서브시스템 심화

PCI/PCIe 서브시스템

#include <linux/pci.h>

/* PCI 디바이스 ID 테이블 */
static const struct pci_device_id my_pci_ids[] = {
    { PCI_DEVICE(0x8086, 0x1234) },  /* vendor, device */
    { PCI_DEVICE_CLASS(0x020000, 0xFFFF00) }, /* 클래스 매칭 */
    { 0, }
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);

/* PCI 드라이버 프로브 */
static int my_pci_probe(struct pci_dev *pdev,
        const struct pci_device_id *id)
{
    int ret;

    /* 1. PCI 디바이스 활성화 */
    ret = pcim_enable_device(pdev);  /* devm 버전 */

    /* 2. BAR (Base Address Register) 매핑 */
    ret = pcim_iomap_regions(pdev, BIT(0), "my_driver");
    void __iomem *base = pcim_iomap_table(pdev)[0];

    /* 3. DMA 마스크 설정 */
    ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));

    /* 4. Bus Master 활성화 (DMA 사용 시 필수) */
    pci_set_master(pdev);

    /* 5. MSI/MSI-X 인터럽트 설정 */
    ret = pci_alloc_irq_vectors(pdev, 1, 32, PCI_IRQ_MSIX | PCI_IRQ_MSI);

    return 0;
}

static struct pci_driver my_pci_driver = {
    .name     = "my_pci",
    .id_table = my_pci_ids,
    .probe    = my_pci_probe,
};
module_pci_driver(my_pci_driver);
PCI/PCIe 드라이버 주의사항:
  • BAR 매핑ioremap() 반환값은 __iomem 포인터. readl()/writel()로만 접근 (직접 포인터 역참조 금지)
  • Config Space 접근pci_read_config_dword()로 구성 공간 읽기. 잘못된 접근은 시스템 행(hang) 유발 가능
  • MSI vs MSI-X — MSI는 최대 32개, MSI-X는 2048개 벡터. 멀티큐 디바이스에서는 MSI-X 필수
  • SR-IOVpci_enable_sriov()로 VF 생성. VF 드라이버는 PF와 별도로 바인딩
  • AER (Advanced Error Reporting) — PCIe 에러 복구 콜백 pci_error_handlers 등록 권장
  • ASPM (Active State Power Management) — 전력 절감이지만 지연 증가. 고성능 NIC에서 pcie_aspm=off 필요한 경우 있음

I2C/SMBus 서브시스템

설명 요약:
  • === I2C (Inter-Integrated Circuit) 프로토콜 개요 ===
  • Philips(NXP)가 1982년 개발한 2-wire 직렬 버스.
  • 저속 주변장치(센서, EEPROM, RTC, PMIC 등) 연결에 표준적으로 사용.
  • 물리 계층:
  • SDA (Serial Data) — 양방향 데이터 라인
  • SCL (Serial Clock) — 마스터가 생성하는 클럭 라인
  • 둘 다 오픈 드레인 + 외부 풀업 저항 (보통 4.7kΩ~10kΩ)
  • → 다중 마스터/슬레이브가 하나의 버스를 공유 (wired-AND)
  • 속도 모드:
  • Standard Mode (SM) — 100 kbit/s (기본)
  • Fast Mode (FM) — 400 kbit/s (대부분의 센서)
  • Fast Mode Plus (FM+) — 1 Mbit/s (20mA 전류 드라이버)
  • High Speed Mode (Hs) — 3.4 Mbit/s (마스터 코드 필요)
  • Ultra Fast Mode (UFm) — 5 Mbit/s (단방향, 푸시풀)
  • 트랜잭션 포맷:
  • [S] [ADDR(7bit)] [R/W(1bit)] [ACK] [DATA(8bit)] [ACK] ... [P]
  • S = START 조건 (SDA↓ while SCL=HIGH)
  • P = STOP 조건 (SDA↑ while SCL=HIGH)
  • Sr = Repeated START (STOP 없이 새 트랜잭션 시작)
  • ACK = 수신자가 SDA를 LOW로 당김 (정상)
  • NACK = 수신자가 응답하지 않음 (SDA=HIGH 유지)
  • 7-bit 주소: 0x00~0x7F (유효 범위 0x08~0x77, 나머지는 예약)
  • 10-bit 주소: 첫 바이트 11110XX + 두 번째 바이트 8bit (드물게 사용)
  • Clock Stretching:
  • 슬레이브가 SCL을 LOW로 유지하여 마스터를 대기시킴
  • → 느린 디바이스의 처리 시간 확보
  • → 하드웨어 I2C 컨트롤러가 지원해야 함
  • Multi-Master:
  • 여러 마스터가 동시에 전송 시도 → 중재(Arbitration)
  • SDA에서 0(LOW)을 보낸 마스터가 1(HIGH)을 읽으면 → 중재 패배, 버스 양보
/* === SMBus vs I2C 차이 ===
 *
 * SMBus(System Management Bus)는 Intel이 1995년 정의한 I2C의 부분집합.
 * PC 메인보드의 전원 관리, 팬 제어, 온도 센서, DIMM SPD EEPROM 등에 사용.
 *
 * ┌────────────────────┬──────────────────────┬──────────────────────┐
 * │     항목           │       I2C            │       SMBus          │
 * ├────────────────────┼──────────────────────┼──────────────────────┤
 * │ 전압 범위          │ Vdd 자유 (1.8~5V)   │ 1.8V 또는 3.3V 고정 │
 * │ 최대 속도          │ 3.4 Mbit/s (Hs)     │ 100kHz (1.0)         │
 * │                    │                      │ 400kHz (2.0)         │
 * │                    │                      │ 1MHz (3.0)           │
 * │ 클럭 최소 주파수   │ 없음 (DC 허용)       │ 10kHz (타임아웃)     │
 * │ 타임아웃           │ 없음                 │ 25~35ms SCL LOW      │
 * │ 최대 데이터 크기   │ 제한 없음            │ 32바이트 (블록)      │
 * │ 주소 해석 (ARP)    │ 없음                 │ 지원 (동적 주소 할당)│
 * │ 패킷 에러 체크     │ 없음                 │ PEC (CRC-8) 선택적   │
 * │ Alert 핀           │ 없음                 │ SMBALERT# (인터럽트)  │
 * │ Host Notify        │ 없음                 │ 슬레이브→마스터 알림 │
 * └────────────────────┴──────────────────────┴──────────────────────┘
 *
 * 리눅스 커널에서의 권장:
 *   - 디바이스가 SMBus 호환이면 → i2c_smbus_*() 함수 사용
 *   - SMBus 전용 컨트롤러(Intel PCH 등)에서도 동작 보장
 *   - Raw I2C 필요 시에만 → i2c_transfer() 사용
 *     (SMBus 전용 컨트롤러에서는 동작 안 할 수 있음)
 */

/* === SMBus 프로토콜 명령 유형 ===
 *
 * Quick Command:     [S][Addr][R/W][A][P]           (데이터 없이 R/W 비트만)
 * Send Byte:         [S][Addr][W][A][Data][A][P]    (1바이트 전송)
 * Receive Byte:      [S][Addr][R][A][Data][NA][P]   (1바이트 수신)
 * Write Byte:        [S][Addr][W][A][Cmd][A][Data][A][P]
 * Read Byte:         [S][Addr][W][A][Cmd][A][Sr][Addr][R][A][Data][NA][P]
 * Write Word:        [S][Addr][W][A][Cmd][A][DataLow][A][DataHigh][A][P]
 * Read Word:         Write Cmd → Repeated Start → Read 2 bytes
 * Block Write:       [S][Addr][W][A][Cmd][A][Count][A][Data0]...[DataN][A][P]
 * Block Read:        Write Cmd → Sr → Read Count + Data bytes
 * I2C Block:         커널 확장 (Count 바이트 없이 직접 블록 전송)
 * Process Call:      Write Word → Sr → Read Word (요청-응답)
 * Block Process Call: Block Write → Sr → Block Read
 *
 * Cmd = 커맨드/레지스터 주소 (8-bit)
 * Count = 이후 데이터 바이트 수 (1~32)
 * PEC = 선택적 CRC-8 체크 바이트 (마지막에 추가)
 */
I2C vs SMBus 핵심 차이 항목 I2C SMBus 전압/물리 규칙 Vdd 범위 유연 (1.8~5V) 1.8V 또는 3.3V 중심 속도 최대 3.4 Mbit/s (Hs) 100k/400k/1MHz 표준화 타임아웃/클럭 규정 최소 클럭/타임아웃 규정 약함 SCL LOW 25~35ms 타임아웃 데이터 크기 프레임 길이 제한 유연 블록 전송 32바이트 제약 부가 기능 기본 프로토콜 중심 PEC, SMBALERT#, Host Notify 리눅스 I2C 서브시스템 아키텍처 (drivers/i2c/) 사용자 공간: i2cdetect / i2cdump / i2cget / i2cset 인터페이스: /dev/i2c-N, /sys/bus/i2c/ I2C Core: i2c_transfer(), i2c_smbus_xfer() 역할: 디바이스 등록/매칭, 버스 잠금, 재시도 로직 파일: i2c-core-*.c Adapter Driver 계층 (컨트롤러 드라이버) i2c-designware-*.c, i2c-bcm2835.c, i2c-imx.c i2c-i801.c (Intel PCH SMBus) 역할: master_xfer/smbus_xfer 구현, IRQ/DMA 처리 대상: 하드웨어 I2C/SMBus 컨트롤러 핵심 구조체 struct i2c_adapter: I2C 버스(컨트롤러) 인스턴스 struct i2c_algorithm: 전송 함수 집합(master_xfer, smbus_xfer) struct i2c_client: 버스에 연결된 슬레이브 디바이스 struct i2c_driver: 슬레이브 디바이스 드라이버 struct i2c_msg: 단일 메시지(방향/주소/버퍼/길이) 데이터 경로: userspace → i2c-dev → i2c core → adapter → controller
#include <linux/i2c.h>

/* === I2C 디바이스 드라이버 (클라이언트 드라이버) === */

/* 디바이스 ID 테이블 (platform 매칭용) */
static const struct i2c_device_id my_i2c_ids[] = {
    { "my_sensor", 0 },
    { "my_sensor_v2", 1 },  /* driver_data로 변형 구분 */
    { }
};
MODULE_DEVICE_TABLE(i2c, my_i2c_ids);

/* Device Tree 매칭 */
static const struct of_device_id my_of_ids[] = {
    { .compatible = "vendor,my-sensor", .data = (void *)0 },
    { .compatible = "vendor,my-sensor-v2", .data = (void *)1 },
    { }
};
MODULE_DEVICE_TABLE(of, my_of_ids);

/* ACPI 매칭 (x86 플랫폼) */
static const struct acpi_device_id my_acpi_ids[] = {
    { "VNDR0001", 0 },  /* ACPI _HID */
    { }
};
MODULE_DEVICE_TABLE(acpi, my_acpi_ids);

static int my_i2c_probe(struct i2c_client *client)
{
    u8 reg_val;
    s32 ret;

    /* SMBus 기능 확인 */
    if (!i2c_check_functionality(client->adapter,
            I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) {
        dev_err(&client->dev, "SMBus byte/word 미지원\\n");
        return -ENODEV;
    }

    /* === SMBus 전송 함수 (권장) === */

    /* 1바이트 레지스터 읽기 */
    ret = i2c_smbus_read_byte_data(client, 0x00);
    if (ret < 0)
        return ret;
    reg_val = ret & 0xFF;

    /* 1바이트 레지스터 쓰기 */
    ret = i2c_smbus_write_byte_data(client, 0x01, 0xFF);

    /* 2바이트(word) 읽기/쓰기 */
    s32 word = i2c_smbus_read_word_data(client, 0x02);
    i2c_smbus_write_word_data(client, 0x02, 0x1234);

    /* 블록 읽기 (최대 I2C_SMBUS_BLOCK_MAX = 32 바이트) */
    u8 block[I2C_SMBUS_BLOCK_MAX];
    ret = i2c_smbus_read_i2c_block_data(client, 0x10,
                                          sizeof(block), block);

    /* === Raw I2C 전송 (SMBus로 불가능한 경우) === */
    u8 reg_addr = 0x20;
    u8 buf[256];
    struct i2c_msg msgs[2] = {
        {   /* 첫 번째 메시지: 레지스터 주소 쓰기 */
            .addr = client->addr,
            .flags = 0,          /* 쓰기 */
            .len = 1,
            .buf = ®_addr,
        },
        {   /* 두 번째 메시지: 데이터 읽기 (Repeated START) */
            .addr = client->addr,
            .flags = I2C_M_RD,   /* 읽기 */
            .len = sizeof(buf),
            .buf = buf,
        },
    };
    ret = i2c_transfer(client->adapter, msgs, 2);
    if (ret != 2)  /* 반환값 = 성공한 메시지 수 */
        return ret < 0 ? ret : -EIO;

    return 0;
}

static struct i2c_driver my_i2c_driver = {
    .driver = {
        .name = "my_sensor",
        .of_match_table = my_of_ids,
        .acpi_match_table = my_acpi_ids,
        .pm = &my_pm_ops,          /* 전원 관리 콜백 */
    },
    .probe = my_i2c_probe,
    .id_table = my_i2c_ids,
};
module_i2c_driver(my_i2c_driver);
/* module_i2c_driver(): module_init + module_exit +
 * i2c_add_driver / i2c_del_driver를 자동 생성하는 매크로 */
/* === regmap을 통한 I2C 레지스터 접근 (권장 패턴) ===
 *
 * 소스: drivers/base/regmap/regmap-i2c.c
 *
 * regmap은 I2C/SPI/MMIO 등 다양한 버스에 대해 통일된 레지스터 접근 API를 제공.
 * 캐싱, 바이트 순서 변환, 범위 검증, 디버깅을 자동 처리.
 */
#include <linux/regmap.h>

static const struct regmap_config my_regmap_config = {
    .reg_bits = 8,               /* 레지스터 주소 비트 수 */
    .val_bits = 8,               /* 레지스터 값 비트 수 */
    .max_register = 0xFF,        /* 최대 레지스터 주소 (범위 검증) */
    .cache_type = REGCACHE_RBTREE, /* 레지스터 캐시 (suspend/resume 복원) */
    .volatile_reg = my_volatile,  /* 캐시하지 않을 레지스터 (상태 레지스터 등) */
};

static int my_probe(struct i2c_client *client)
{
    struct regmap *regmap;
    unsigned int val;

    regmap = devm_regmap_init_i2c(client, &my_regmap_config);
    if (IS_ERR(regmap))
        return PTR_ERR(regmap);

    /* 통일된 읽기/쓰기 API */
    regmap_read(regmap, 0x00, &val);
    regmap_write(regmap, 0x01, 0xFF);

    /* 비트 조작 (read-modify-write 원자적 수행) */
    regmap_update_bits(regmap, 0x02,
        0x30,    /* mask: 비트 4,5 */
        0x20);   /* val:  비트 5만 설정 */

    /* 벌크 읽기/쓰기 */
    regmap_bulk_read(regmap, 0x10, buf, 16);
    regmap_bulk_write(regmap, 0x20, data, 8);

    return 0;
}

/* suspend 시 regmap 캐시 자동 활용:
 * regcache_cache_only(regmap, true)  → H/W 접근 차단, 캐시만 사용
 * regcache_mark_dirty(regmap)        → 모든 캐시를 dirty 표시
 * regcache_cache_only(regmap, false) → H/W 접근 재개
 * regcache_sync(regmap)              → dirty 레지스터를 H/W에 기록 (resume) */
/* === I2C Adapter (컨트롤러) 드라이버 ===
 *
 * I2C 컨트롤러 하드웨어를 구동하는 드라이버.
 * i2c_algorithm을 구현하여 I2C core에 등록.
 */
static int my_i2c_xfer(struct i2c_adapter *adap,
                        struct i2c_msg *msgs, int num)
{
    struct my_i2c_dev *dev = i2c_get_adapdata(adap);
    int i;

    for (i = 0; i < num; i++) {
        if (msgs[i].flags & I2C_M_RD)
            my_hw_read(dev, &msgs[i]);    /* H/W 레지스터로 읽기 */
        else
            my_hw_write(dev, &msgs[i]);   /* H/W 레지스터로 쓰기 */
    }
    return num;  /* 성공한 메시지 수 반환 */
}

static u32 my_i2c_func(struct i2c_adapter *adap)
{
    /* 이 컨트롤러가 지원하는 기능 플래그 반환 */
    return I2C_FUNC_I2C              /* raw I2C 전송 */
         | I2C_FUNC_SMBUS_BYTE_DATA   /* SMBus byte 읽기/쓰기 */
         | I2C_FUNC_SMBUS_WORD_DATA   /* SMBus word 읽기/쓰기 */
         | I2C_FUNC_SMBUS_BLOCK_DATA  /* SMBus block 전송 */
         | I2C_FUNC_10BIT_ADDR;       /* 10-bit 주소 지원 */
}

static const struct i2c_algorithm my_i2c_algo = {
    .master_xfer = my_i2c_xfer,       /* raw I2C 전송 구현 */
    .functionality = my_i2c_func,      /* 기능 플래그 조회 */
    /* .smbus_xfer = my_smbus_xfer,  — SMBus 전용 컨트롤러일 때 */
};

/* Adapter 등록 (platform driver의 probe에서) */
static int my_i2c_controller_probe(struct platform_device *pdev)
{
    struct my_i2c_dev *dev;
    int ret;

    dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);

    dev->adap.owner = THIS_MODULE;
    dev->adap.algo = &my_i2c_algo;
    dev->adap.dev.parent = &pdev->dev;
    dev->adap.dev.of_node = pdev->dev.of_node;  /* DT 자식 디바이스 열거 */
    strlcpy(dev->adap.name, "my-i2c", sizeof(dev->adap.name));
    i2c_set_adapdata(&dev->adap, dev);

    ret = devm_i2c_add_adapter(&pdev->dev, &dev->adap);
    /* devm: remove 시 자동 i2c_del_adapter() */

    return ret;
}
설명 요약:
  • === I2C Mux (멀티플렉서) ===
  • 소스: drivers/i2c/i2c-mux.c, drivers/i2c/muxes/
  • 하나의 I2C 버스를 여러 세그먼트로 분할.
  • 동일 주소의 디바이스를 서로 다른 세그먼트에 배치 가능.
  • 일반적인 MUX 칩: PCA9548A (8채널), PCA9546A (4채널), TCA9548A
  • Device Tree 예시:
  • i2c-mux@70 {
  • compatible = "nxp,pca9548";
  • reg = ;
  • #address-cells = ;
  • #size-cells = ;
  • i2c@0 { ← 채널 0 (가상 I2C 버스)
  • reg = ;
  • #address-cells = ;
  • #size-cells = ;
  • sensor@48 { compatible = "ti,tmp102"; reg = ; };
  • };
  • i2c@1 { ← 채널 1 (같은 주소 0x48도 가능)
  • reg = ;
  • sensor@48 { compatible = "ti,tmp102"; reg = ; };
  • };
  • };
  • 커널 내부: i2c_mux_alloc() → i2c_mux_add_adapter()
  • → 각 채널마다 가상 i2c_adapter 생성
  • → select/deselect 콜백으로 MUX 채널 전환 */
설명 요약:
  • === I2C Slave Mode (커널 5.2+) ===
  • 소스: drivers/i2c/i2c-slave-*.c
  • 리눅스를 I2C 슬레이브로 동작시키는 기능.
  • 임베디드에서 BMC(Baseboard Management Controller) 등에 활용.
  • i2c_slave_register(client, I2C_SLAVE_DEFAULTS, slave_cb)
  • 콜백 이벤트:
  • I2C_SLAVE_WRITE_REQUESTED — 마스터가 쓰기 시작
  • I2C_SLAVE_WRITE_RECEIVED — 데이터 바이트 수신
  • I2C_SLAVE_READ_REQUESTED — 마스터가 읽기 요청
  • I2C_SLAVE_READ_PROCESSED — 데이터 바이트 전송 완료
  • I2C_SLAVE_STOP — STOP 조건 감지
  • 기본 제공 슬레이브 백엔드:
  • i2c-slave-eeprom — EEPROM 에뮬레이션 (256바이트)
  • i2c-slave-testunit — 테스트용 슬레이브
# === I2C/SMBus 사용자 공간 도구 (i2c-tools) ===

# 시스템의 I2C 버스 목록 확인
ls /dev/i2c-*
# /dev/i2c-0  /dev/i2c-1  /dev/i2c-2 ...

# I2C 버스 컨트롤러 정보
cat /sys/bus/i2c/devices/i2c-0/name
# "SMBus I801 adapter at efa0"  (Intel PCH)

# 버스 스캔 — 응답하는 슬레이브 주소 탐지
i2cdetect -y 0           # Quick Command 방식
i2cdetect -y -r 0        # Read Byte 방식 (Quick 미지원 디바이스)
#      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
# 20: -- -- -- -- -- -- -- --
# 30: -- -- -- -- -- -- -- --
# 40: -- -- -- -- -- -- -- --
# 50: 50 -- -- -- -- -- -- --  ← 0x50에 디바이스 존재 (EEPROM)

# 단일 레지스터 읽기/쓰기
i2cget -y 0 0x50 0x00          # 버스0, 주소0x50, 레지스터0x00 읽기
i2cset -y 0 0x50 0x00 0xFF     # 레지스터 0x00에 0xFF 쓰기

# 전체 레지스터 덤프
i2cdump -y 0 0x50              # 기본: SMBus byte 방식
i2cdump -y -r 0x00-0x0f 0 0x50 # 범위 지정 덤프

# raw I2C 메시지 전송 (i2c-tools 4.0+)
i2ctransfer -y 0 w1@0x50 0x00 r4  # reg 0x00 쓰기 후 4바이트 읽기
# w1@0x50 = 쓰기 1바이트 주소 0x50, r4 = 읽기 4바이트
i2ctransfer -y 0 w2@0x50 0x00 0x10 0xFF  # 2바이트 레지스터 + 데이터 쓰기

# SMBus 컨트롤러 기능 확인
i2cdetect -F 0
# I2C                            yes
# SMBus Quick Command             yes
# SMBus Byte                      yes
# SMBus Byte Data                 yes
# SMBus Word Data                 yes
# SMBus Block Data                yes

# DIMM SPD EEPROM 읽기 (DDR4/DDR5 메모리 정보)
# SMBus에 연결된 DIMM SPD (주소 0x50~0x57)
i2cdump -y 0 0x50 s  # SMBus block 방식으로 SPD 덤프
decode-dimms           # i2c-tools 부가 도구: SPD 데이터를 사람이 읽을 수 있게 파싱

# 커널 I2C 디바이스 트리 확인
ls /sys/bus/i2c/devices/
# 0-0050  1-0020  i2c-0  i2c-1
# 형식: {버스번호}-{16진수주소}
cat /sys/bus/i2c/devices/0-0050/name
I2C/SMBus 주의사항:
  • SMBus 전용 컨트롤러 — Intel PCH의 i2c-i801은 SMBus 전용. i2c_transfer()로 32바이트 초과 전송 불가. i2c_check_functionality()로 반드시 확인
  • 버스 잠금i2c_transfer()는 내부적으로 adapter mutex를 잡음. 콜백 안에서 동일 adapter 재접근 시 교착. i2c_transfer_buffer_flags() 또는 __i2c_transfer() 사용
  • 원자적 I2C — panic/reboot 경로에서는 mutex를 잡을 수 없음. i2c_algorithm.master_xfer_atomic 콜백 구현 필요 (폴링 방식)
  • 주소 충돌 — 같은 버스에 동일 주소 디바이스 불가. I2C MUX로 해결하거나 i2c_new_ancillary_device()로 보조 주소 사용
  • 풀업 저항 — 버스에 디바이스가 많으면 총 버스 커패시턴스 증가 → 풀업 저항값 낮춰야 함. 400pF 초과 시 Fast Mode 불가
  • 전압 레벨 — 1.8V/3.3V 혼재 시 레벨 시프터 필요. 풀업 전압과 디바이스 Vdd가 일치해야 함

I3C (MIPI I3C) 서브시스템

특성I2CSMBus 3.0I3C (Basic)I3C (HDR)
최대 속도3.4 Mbps (Hs)1 Mbps12.5 Mbps (SDR)25 Mbps (HDR-DDR)
출력 방식오픈 드레인오픈 드레인푸시풀 (SDR)푸시풀
주소 할당고정 7/10bit고정/ARP동적 (DAA)동적 (DAA)
인터럽트별도 IRQ 라인SMBALERT#In-Band (IBI)In-Band (IBI)
Hot-Join미지원미지원지원지원
I2C 호환I2C 하위호환레거시 I2C 혼용레거시 I2C 혼용
전력외부 풀업 필요외부 풀업 필요내부 풀업 (SDA)내부 풀업 (SDA)
핀 수SDA, SCLSDA, SCLSDA, SCL (동일)SDA, SCL (동일)
커널 소스drivers/i2c/drivers/i2c/drivers/i3c/ (5.0+)
/* === I3C (MIPI Improved Inter-Integrated Circuit) 개요 ===
 *
 * MIPI Alliance가 2017년 발표한 차세대 저속 직렬 버스 규격.
 * I2C의 한계를 극복하면서 물리적 호환성(SDA/SCL 2-wire) 유지.
 *
 * 핵심 특징:
 *
 * 1. 동적 주소 할당 (DAA — Dynamic Address Assignment)
 *    - I2C: 제조 시 고정된 7-bit 주소 → 동일 칩 다수 사용 시 충돌
 *    - I3C: 마스터가 버스 초기화 시 각 디바이스에 7-bit 주소 동적 할당
 *    - ENTDAA CCC: 디바이스의 48-bit Provisioned ID로 식별 후 주소 배정
 *    - SETDASA CCC: 레거시 I2C 디바이스에 정적 주소 설정
 *
 * 2. In-Band Interrupt (IBI)
 *    - I2C: 인터럽트마다 별도 GPIO 라인 필요 → 핀 수 증가
 *    - I3C: 슬레이브가 SDA 라인으로 직접 인터럽트 신호 전달
 *    - 마스터가 버스 유휴 시 SDA를 모니터링 → 슬레이브가 LOW로 당김
 *    - 중재 후 해당 슬레이브의 주소 + MDB(Mandatory Data Byte) 수신
 *    - → 추가 GPIO 핀 불필요 → 패키지 크기/비용 절감
 *
 * 3. HDR (High Data Rate) 모드
 *    - SDR (Single Data Rate): 12.5 Mbps (기본, 오픈 드레인/푸시풀 혼합)
 *    - HDR-DDR (Double Data Rate): 25 Mbps (양 에지 데이터 전송)
 *    - HDR-TSP (Ternary Symbol Pure): 25 Mbps (3-레벨 신호)
 *    - HDR-TSL (Ternary Symbol Legacy): HDR-TSP + I2C 호환 레벨
 *    - HDR-BT (Bulk Transport): 최대 50 Mbps (I3C v1.1.1)
 *
 * 4. Hot-Join
 *    - 동작 중인 버스에 새 디바이스 추가 가능
 *    - 새 디바이스가 Hot-Join 요청 → 마스터가 DAA 수행 → 주소 할당
 *
 * 5. CCC (Common Command Codes)
 *    - 브로드캐스트 CCC: 모든 디바이스에 전달 (RSTDAA, ENEC, DISEC 등)
 *    - 다이렉트 CCC: 특정 디바이스에 전달 (SETDASA, GETMRL 등)
 */

/* === I3C 버스 초기화 순서 ===
 *
 * 1. 마스터가 SCL을 12.5MHz로 설정, 버스 리셋
 * 2. RSTDAA (브로드캐스트) — 기존 동적 주소 모두 해제
 * 3. SETDASA (다이렉트)  — 레거시 I2C 디바이스에 정적 주소 할당
 * 4. ENTDAA (브로드캐스트) — I3C 디바이스 동적 주소 할당
 *    ├── 각 디바이스가 48-bit PID(Provisioned ID) 응답
 *    │     ├── bits[47:33] — MIPI 제조사 ID
 *    │     ├── bits[32]    — ID 유형 (랜덤/고정)
 *    │     └── bits[31:0]  — 파트/인스턴스 번호
 *    ├── BCR (Bus Characteristics Register, 8-bit)
 *    │     ├── bit[7:6] — 디바이스 역할 (I3C slave, Master-capable 등)
 *    │     ├── bit[5]   — HDR 지원 여부
 *    │     ├── bit[3]   — IBI 페이로드 유무
 *    │     └── bit[1]   — IBI 요청 가능 여부
 *    ├── DCR (Device Characteristics Register, 8-bit)
 *    │     → 디바이스 타입: 가속도계, 자이로, 기압계, 온도 등
 *    └── 마스터가 7-bit 동적 주소 배정 → ACK → 다음 디바이스
 *
 * 5. ENEC (브로드캐스트) — IBI, Hot-Join 이벤트 활성화
 * 6. 정상 통신 시작 (SDR 또는 HDR 모드)
 */
/* === 리눅스 I3C 서브시스템 아키텍처 (커널 5.0+) ===
 *
 * 소스: drivers/i3c/
 *
 * ┌───────────────────────────────────────────────────┐
 * │           사용자 공간 (향후 i3c-tools 등)         │
 * ├───────────────────────────────────────────────────┤
 * │              I3C Core (drivers/i3c/master.c)      │
 * │   i3c_device_do_priv_xfers()  — 프라이빗 전송     │
 * │   i3c_device_send_ccc_cmd()   — CCC 명령          │
 * │   DAA 수행, IBI 관리, 디바이스 등록/매칭           │
 * ├───────────────────────────────────────────────────┤
 * │   I3C Master Controller Driver                    │
 * │   drivers/i3c/master/dw-i3c-master.c (DesignWare) │
 * │   drivers/i3c/master/svc-i3c-master.c (Silvaco)   │
 * │   drivers/i3c/master/mipi-i3c-hci.c (MIPI HCI)    │
 * │   drivers/i3c/master/cdns-i3c-master.c (Cadence)   │
 * ├───────────────────────────────────────────────────┤
 * │              하드웨어 I3C 컨트롤러                 │
 * └───────────────────────────────────────────────────┘
 *
 * 핵심 구조체:
 *   struct i3c_master_controller — I3C 마스터 인스턴스
 *   struct i3c_master_controller_ops — 컨트롤러 드라이버 콜백
 *   struct i3c_device             — I3C 디바이스 (동적 주소 보유)
 *   struct i3c_driver             — I3C 디바이스 드라이버
 *   struct i2c_dev_desc           — I3C 버스의 레거시 I2C 디바이스
 */

/* === I3C 디바이스 드라이버 예시 === */
#include <linux/i3c/device.h>
#include <linux/i3c/master.h>

static const struct i3c_device_id my_i3c_ids[] = {
    /* MIPI 제조사 ID + 파트 ID + 추가 정보 */
    I3C_DEVICE(0x0123, 0x0456, NULL),
    /* 또는 DCR 기반 매칭 (디바이스 타입별) */
    I3C_DEVICE_EXTRA_INFO(0x0123, 0x0456, 0, NULL),
    { }
};
MODULE_DEVICE_TABLE(i3c, my_i3c_ids);

static int my_i3c_probe(struct i3c_device *i3cdev)
{
    struct i3c_device_info info;
    u8 tx_buf[2], rx_buf[4];

    /* 디바이스 정보 조회 (PID, BCR, DCR, 동적 주소) */
    i3c_device_get_info(i3cdev, &info);
    dev_info(&i3cdev->dev,
        "PID: 0x%012llx, BCR: 0x%02x, DCR: 0x%02x, addr: 0x%02x\\n",
        info.pid, info.bcr, info.dcr, info.dyn_addr);

    /* === 프라이빗 전송 (SDR 모드) ===
     * I2C의 i2c_transfer()에 해당 */
    struct i3c_priv_xfer xfers[2] = {
        {   /* 레지스터 주소 쓰기 */
            .rnw = false,         /* 쓰기 */
            .len = sizeof(tx_buf),
            .data.out = tx_buf,
        },
        {   /* 데이터 읽기 */
            .rnw = true,          /* 읽기 */
            .len = sizeof(rx_buf),
            .data.in = rx_buf,
        },
    };
    tx_buf[0] = 0x00;  /* 레지스터 주소 */
    i3c_device_do_priv_xfers(i3cdev, xfers, 2);

    /* === IBI (In-Band Interrupt) 등록 === */
    struct i3c_ibi_setup ibi_setup = {
        .handler = my_ibi_handler,     /* IBI 수신 콜백 */
        .max_payload_len = 2,          /* IBI 페이로드 최대 크기 */
        .num_slots = 4,                /* IBI 큐 슬롯 수 */
    };
    i3c_device_request_ibi(i3cdev, &ibi_setup);
    i3c_device_enable_ibi(i3cdev);

    return 0;
}

/* IBI 핸들러 — 워크큐 컨텍스트에서 호출 */
static void my_ibi_handler(struct i3c_device *i3cdev,
                            const struct i3c_ibi_payload *payload)
{
    /* payload->data: IBI와 함께 전달된 데이터 (MDB + 추가 바이트)
     * payload->len:  페이로드 길이
     * MDB(Mandatory Data Byte): 인터럽트 원인 식별자 */
    dev_dbg(&i3cdev->dev, "IBI: MDB=0x%02x, len=%zu\\n",
        payload->data[0], payload->len);
}

static void my_i3c_remove(struct i3c_device *i3cdev)
{
    i3c_device_disable_ibi(i3cdev);
    i3c_device_free_ibi(i3cdev);
}

static struct i3c_driver my_i3c_driver = {
    .driver = { .name = "my_i3c_sensor" },
    .probe = my_i3c_probe,
    .remove = my_i3c_remove,
    .id_table = my_i3c_ids,
};
module_i3c_driver(my_i3c_driver);
/* === I3C Master Controller 드라이버 핵심 콜백 ===
 *
 * 컨트롤러 하드웨어를 구동하는 드라이버가 구현해야 하는 ops:
 */
struct i3c_master_controller_ops {
    /* 버스 초기화 (DAA 수행, 클럭 설정 등) */
    int (*bus_init)(struct i3c_master_controller *master);
    void (*bus_cleanup)(struct i3c_master_controller *master);

    /* 동적 주소 할당 (ENTDAA 수행) */
    int (*do_daa)(struct i3c_master_controller *master);

    /* I3C 프라이빗 전송 (SDR/HDR) */
    int (*send_ccc_cmd)(struct i3c_master_controller *master,
                         struct i3c_ccc_cmd *cmd);
    int (*priv_xfers)(struct i3c_dev_desc *dev,
                       struct i3c_priv_xfer *xfers, int nxfers);

    /* 레거시 I2C 전송 (버스의 I2C 디바이스용) */
    int (*i2c_xfers)(struct i2c_dev_desc *dev,
                      const struct i2c_msg *xfers, int nxfers);

    /* IBI (In-Band Interrupt) 관리 */
    int (*request_ibi)(struct i3c_dev_desc *dev,
                        const struct i3c_ibi_setup *req);
    void (*free_ibi)(struct i3c_dev_desc *dev);
    int (*enable_ibi)(struct i3c_dev_desc *dev);
    int (*disable_ibi)(struct i3c_dev_desc *dev);
    void (*recycle_ibi_slot)(struct i3c_dev_desc *dev,
                              struct i3c_ibi_slot *slot);
};

/* Device Tree 예시:
 *
 * i3c-master@d040000 {
 *     compatible = "snps,dw-i3c-master";
 *     reg = <0x0d040000 0x1000>;
 *     interrupts = ;
 *     clocks = <&i3c_clk>;
 *     #address-cells = <3>;  ← I3C: 3셀 (정적주소, PID 상위, PID 하위)
 *     #size-cells = <0>;
 *     i2c-scl-hz = <400000>;   ← 레거시 I2C SCL 속도
 *     i3c-scl-hz = <12500000>; ← I3C SCL 속도
 *
 *     ← I3C 디바이스 (동적 주소 할당 대상)
 *     sensor@0,11220000000 {
 *         reg = <0 0x112 0x20000000>;  ← 정적주소=0, PID
 *         assigned-address = <0x09>;    ← 원하는 동적 주소 (힌트)
 *     };
 *
 *     ← 레거시 I2C 디바이스 (정적 주소)
 *     eeprom@50 {
 *         compatible = "atmel,24c64";
 *         reg = <0x50 0 0>;  ← I2C 주소 0x50
 *     };
 * };
 */
I3C 도입 현황 (2024~): I3C는 주로 모바일 SoC(Qualcomm, Samsung)의 센서 허브에서 먼저 도입되었습니다. 가속도계, 자이로스코프, 기압계 등의 저전력 센서를 소수 핀으로 연결하는 데 최적화되어 있습니다. 서버/데스크톱 환경에서는 아직 I2C/SMBus가 지배적이며, DDR5 DIMM의 온도 센서 인터페이스에 I3C Basic이 채택되기 시작했습니다.

SPI 서브시스템

/* === SPI (Serial Peripheral Interface) 프로토콜 개요 ===
 *
 * Motorola가 1980년대 개발한 전이중(full-duplex) 동기식 직렬 버스.
 * I2C보다 빠른 속도가 필요한 주변장치(Flash, ADC/DAC, 디스플레이, 센서) 연결에 사용.
 *
 * 물리 계층 (4-wire 기본):
 *   SCLK (Serial Clock)  — 마스터가 생성하는 클럭
 *   MOSI (Master Out Slave In) — 마스터→슬레이브 데이터 (= SDO, COPI)
 *   MISO (Master In Slave Out) — 슬레이브→마스터 데이터 (= SDI, CIPO)
 *   CS/SS (Chip Select)   — 슬레이브 선택 (Active Low, 디바이스별 1개)
 *
 *   ┌──────────┐          SCLK           ┌──────────┐
 *   │          │─────────────────────────→│          │
 *   │          │          MOSI            │          │
 *   │  Master  │─────────────────────────→│  Slave   │
 *   │  (SoC)   │          MISO            │ (Flash)  │
 *   │          │←─────────────────────────│          │
 *   │          │          CS#             │          │
 *   │          │─────────────────────────→│          │
 *   └──────────┘                          └──────────┘
 *
 * I2C와의 핵심 차이:
 *   - 풀업 저항 불필요 (푸시풀 출력)
 *   - 주소 체계 없음 — CS 라인으로 디바이스 선택
 *   - 전이중 통신 — 동시에 송수신 가능
 *   - 클럭 속도 제한 없음 — 수십~수백 MHz 가능 (PCB 설계가 제한 요인)
 *   - 디바이스마다 CS 핀 1개 필요 → 다수 디바이스 시 핀 소모
 *
 * SPI 모드 (CPOL/CPHA):
 *   ┌──────┬──────┬──────┬─────────────────────────────────────┐
 *   │ Mode │ CPOL │ CPHA │ 설명                                │
 *   ├──────┼──────┼──────┼─────────────────────────────────────┤
 *   │  0   │  0   │  0   │ 유휴=LOW,  첫 번째 에지(상승)에서 샘플 │
 *   │  1   │  0   │  1   │ 유휴=LOW,  두 번째 에지(하강)에서 샘플 │
 *   │  2   │  1   │  0   │ 유휴=HIGH, 첫 번째 에지(하강)에서 샘플 │
 *   │  3   │  1   │  1   │ 유휴=HIGH, 두 번째 에지(상승)에서 샘플 │
 *   └──────┴──────┴──────┴─────────────────────────────────────┘
 *
 * 변형 프로토콜:
 *   - Dual SPI: MOSI/MISO를 양방향 2-bit으로 → 2배 속도 (QSPI Flash 읽기)
 *   - Quad SPI (QSPI): 4-bit 데이터 라인 → 4배 속도 (NOR Flash 표준)
 *   - Octal SPI (OSPI): 8-bit 데이터 라인 → 8배 속도 (xSPI/HyperBus)
 *   - 3-wire SPI: MOSI/MISO를 단일 양방향 라인으로 (반이중)
 */
리눅스 SPI 서브시스템 아키텍처 (drivers/spi/) 사용자 공간 (User Space) /dev/spidevB.C (spidev) spi-tools / flashrom libspi / Python spidev SPI 디바이스 드라이버 ioctl / read / write SPI Core (drivers/spi/spi.c) spi_sync() 동기 메시지 전송 spi_async() 비동기 메시지 전송 spi_register_controller() 컨트롤러 등록 spi_register_driver() 디바이스 드라이버 등록 DMA 매핑 관리 | CS(Chip Select) 제어 | 메시지 큐 처리 | 버스 잠금 | 통계 spi_message (전송 묶음) → spi_transfer 리스트 → 컨트롤러 드라이버 전달 transfer_one_message() SPI Controller Driver (= Master Driver) spi-bcm2835.c (Raspberry Pi) spi-sun6i.c (Allwinner) spi-stm32.c (STM32) spi-imx.c (i.MX) • transfer_one(): 실제 하드웨어 레지스터 조작으로 FIFO/DMA 전송 시작 • set_cs(): CS GPIO 또는 하드웨어 CS 라인 제어 | prepare_transfer_hardware(): 클럭/전력 설정 spi-rockchip.c / spi-pl022.c / spi-dw.c (Synopsys DesignWare) 등 다수 드라이버 포함 FIFO write / DMA trigger 하드웨어 SPI 컨트롤러 (FIFO · DMA 채널 · CS 라인) SCLK · MOSI · MISO · CS# 시그널 → SPI Flash · ADC · 센서 · 디스플레이 등 슬레이브 디바이스 핵심 구조체 spi_controller — 컨트롤러 인스턴스 (max_speed_hz, num_chipselect, transfer_one 콜백) spi_device — 슬레이브 디바이스 (CS번호, mode, max_speed_hz) | spi_driver — probe/remove 콜백 spi_transfer — 단일 전송 (tx_buf, rx_buf, len, speed_hz) | spi_message — 전송 묶음 (atomic CS assert~deassert)
/* === SPI 디바이스 드라이버 개발 === */
#include <linux/spi/spi.h>
#include <linux/module.h>

struct my_spi_data {
    struct spi_device *spi;
    u8 tx_buf[64] ____cacheline_aligned;  /* DMA 정렬 */
    u8 rx_buf[64] ____cacheline_aligned;
};

static int my_spi_read_reg(struct my_spi_data *priv,
                            u8 reg, u8 *val, size_t len)
{
    struct spi_transfer xfers[2] = {
        {
            .tx_buf = ®,
            .len = 1,
        },
        {
            .rx_buf = val,
            .len = len,
        },
    };
    struct spi_message msg;

    /* spi_message에 transfer 체인 구성 */
    spi_message_init(&msg);
    spi_message_add_tail(&xfers[0], &msg);
    spi_message_add_tail(&xfers[1], &msg);

    /* 동기 전송: CS assert → xfers[0] → xfers[1] → CS deassert */
    return spi_sync(priv->spi, &msg);
}

static int my_spi_probe(struct spi_device *spi)
{
    struct my_spi_data *priv;
    u8 chip_id;
    int ret;

    /* SPI 모드, 비트 순서, 워드 크기 설정 */
    spi->mode = SPI_MODE_0;       /* CPOL=0, CPHA=0 */
    spi->bits_per_word = 8;
    spi->max_speed_hz = 10000000; /* 10 MHz */
    ret = spi_setup(spi);
    if (ret)
        return ret;

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

    priv->spi = spi;
    spi_set_drvdata(spi, priv);

    /* Chip ID 읽기 */
    ret = my_spi_read_reg(priv, 0x00, &chip_id, 1);
    if (ret)
        return ret;

    dev_info(&spi->dev, "Chip ID: 0x%02x, SPI mode %d, %u Hz\\n",
        chip_id, spi->mode, spi->max_speed_hz);

    return 0;
}

/* SPI 간편 API — 단일 전송 시 spi_message 구성 불필요 */
static int my_spi_simple_ops(struct spi_device *spi)
{
    u8 cmd = 0x9F;        /* JEDEC Read ID */
    u8 id[3];
    int ret;

    /* spi_write(): 단순 쓰기 (CS assert → 데이터 전송 → CS deassert) */
    ret = spi_write(spi, &cmd, 1);

    /* spi_read(): 단순 읽기 */
    ret = spi_read(spi, id, 3);

    /* spi_write_then_read(): 쓰기 후 읽기 (가장 많이 사용)
     * 내부적으로 임시 DMA 버퍼 할당 → 소량 데이터에 적합 */
    ret = spi_write_then_read(spi, &cmd, 1, id, 3);
    /* id[0]=제조사, id[1]=메모리타입, id[2]=용량 */

    /* spi_w8r8(): 1바이트 쓰고 1바이트 읽기 (레지스터 읽기) */
    ret = spi_w8r8(spi, 0x05);  /* 상태 레지스터 읽기, 반환값=레지스터값 */

    /* spi_w8r16(): 1바이트 쓰고 2바이트 읽기 (16-bit 레지스터) */
    ret = spi_w8r16(spi, 0x01); /* 반환값=16bit 값 (big-endian) */

    return ret;
}

/* Device Tree / ACPI 매칭 테이블 */
static const struct of_device_id my_spi_of_match[] = {
    { .compatible = "vendor,my-spi-sensor" },
    { .compatible = "vendor,my-spi-adc", .data = &adc_chip_info },
    { }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);

static const struct spi_device_id my_spi_ids[] = {
    { "my-spi-sensor", 0 },
    { "my-spi-adc",    1 },
    { }
};
MODULE_DEVICE_TABLE(spi, my_spi_ids);

static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my-spi-device",
        .of_match_table = my_spi_of_match,
    },
    .probe = my_spi_probe,
    .id_table = my_spi_ids,
};
module_spi_driver(my_spi_driver);
/* === SPI 비동기 전송 (고성능/DMA) ===
 *
 * spi_async()는 비블로킹. 완료 시 콜백 호출.
 * DMA 전송 시 반드시 DMA-safe 버퍼 사용 (kmalloc, not stack/global).
 */

static void my_spi_complete(void *context)
{
    struct completion *done = context;
    complete(done);
}

static int my_spi_async_xfer(struct my_spi_data *priv,
                              u8 *tx, u8 *rx, size_t len)
{
    struct spi_transfer xfer = {
        .tx_buf = tx,
        .rx_buf = rx,
        .len = len,
        .speed_hz = 20000000,   /* 전송별 속도 오버라이드 */
        .bits_per_word = 8,
    };
    struct spi_message msg;
    DECLARE_COMPLETION_ONSTACK(done);

    spi_message_init(&msg);
    spi_message_add_tail(&xfer, &msg);
    msg.complete = my_spi_complete;
    msg.context = &done;

    /* 비블로킹 전송 — 즉시 반환, 완료 시 콜백 */
    int ret = spi_async(priv->spi, &msg);
    if (ret)
        return ret;

    /* 완료 대기 (또는 워크큐에서 처리) */
    wait_for_completion(&done);
    return msg.status;
}

/* === SPI와 regmap 통합 ===
 *
 * I2C와 동일하게 regmap API로 통합 접근 가능.
 * 내부적으로 SPI 전송을 자동 처리.
 */
#include <linux/regmap.h>

static const struct regmap_config my_spi_regmap_cfg = {
    .reg_bits = 8,          /* 레지스터 주소 비트 수 */
    .val_bits = 8,          /* 레지스터 값 비트 수 */
    .max_register = 0xFF,
    .read_flag_mask = 0x80,  /* 읽기 시 레지스터 주소에 OR (디바이스 관례) */
    .cache_type = REGCACHE_RBTREE,
};

/* probe에서: */
struct regmap *regmap = devm_regmap_init_spi(spi, &my_spi_regmap_cfg);
regmap_read(regmap, 0x00, &chip_id);   /* 내부적으로 SPI 전송 */
regmap_write(regmap, 0x01, 0x42);      /* 레지스터 쓰기 */
regmap_update_bits(regmap, 0x02, 0x0F, 0x05); /* RMW */
/* === SPI 컨트롤러 (Master) 드라이버 핵심 구조 ===
 *
 * SoC의 SPI 하드웨어 블록을 제어하는 드라이버.
 * spi_controller 구조체의 콜백을 구현.
 */

static int my_spi_transfer_one(struct spi_controller *ctlr,
                                struct spi_device *spi,
                                struct spi_transfer *xfer)
{
    /* 하드웨어 레지스터에 FIFO/DMA로 데이터 전송
     *
     * xfer->tx_buf  — 송신 데이터 (NULL이면 더미 바이트 전송)
     * xfer->rx_buf  — 수신 버퍼   (NULL이면 수신 데이터 폐기)
     * xfer->len     — 전송 바이트 수
     * xfer->speed_hz — 이 전송의 클럭 속도
     * xfer->bits_per_word — 워드 크기 (8, 16, 32)
     *
     * 반환: 0=완료, 1=진행중(비동기 완료 시 spi_finalize_current_transfer 호출)
     */

    /* 클럭 분주비 설정 */
    my_hw_set_speed(ctlr, xfer->speed_hz);

    /* FIFO 방식 전송 예시 */
    for (int i = 0; i < xfer->len; i++) {
        if (xfer->tx_buf)
            writel(xfer->tx_buf[i], regs + SPI_TX_FIFO);
        else
            writel(0x00, regs + SPI_TX_FIFO);

        /* TX 완료 대기 */
        my_hw_wait_tx_done(ctlr);

        if (xfer->rx_buf)
            xfer->rx_buf[i] = readl(regs + SPI_RX_FIFO);
    }

    return 0;
}

static int my_spi_controller_probe(struct platform_device *pdev)
{
    struct spi_controller *ctlr;

    /* SPI 마스터 컨트롤러 할당 (0 = 추가 private 데이터 크기) */
    ctlr = devm_spi_alloc_master(&pdev->dev, 0);
    if (!ctlr)
        return -ENOMEM;

    /* 컨트롤러 능력 설정 */
    ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST;
    ctlr->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);
    ctlr->min_speed_hz = 100000;     /* 100 KHz */
    ctlr->max_speed_hz = 50000000;   /* 50 MHz */
    ctlr->num_chipselect = 4;        /* CS 라인 수 */
    ctlr->bus_num = -1;              /* 자동 할당 */

    /* 핵심 콜백 등록 */
    ctlr->transfer_one = my_spi_transfer_one;
    ctlr->set_cs = my_spi_set_cs;    /* CS assert/deassert */
    ctlr->use_gpio_descriptors = true; /* DT의 cs-gpios 자동 처리 */

    /* DMA 지원 설정 (선택) */
    ctlr->can_dma = my_spi_can_dma;
    ctlr->max_dma_len = 65536;

    platform_set_drvdata(pdev, ctlr);
    return devm_spi_register_controller(&pdev->dev, ctlr);
}
설명 요약:
  • === SPI Device Tree 바인딩 ===
  • SPI 컨트롤러 노드 아래에 슬레이브 디바이스를 자식 노드로 정의.
  • reg 속성은 CS(Chip Select) 번호.
  • spi0: spi@fe204000 {
  • compatible = "brcm,bcm2835-spi";
  • reg = ;
  • interrupts = ;
  • clocks = ;
  • #address-cells = ; ← CS 번호
  • #size-cells = ;
  • cs-gpios = , ← CS0: GPIO8
  • ; ← CS1: GPIO7
  • dmas = , ;
  • dma-names = "tx", "rx";
  • status = "okay";
  • ← CS0에 연결된 NOR Flash
  • flash@0 {
  • compatible = "jedec,spi-nor";
  • reg = ; ← CS 번호 = 0
  • spi-max-frequency = ; ← 최대 50MHz
  • spi-rx-bus-width = ; ← Quad SPI 읽기 (4-bit)
  • spi-tx-bus-width = ; ← 단일 라인 쓰기
  • m25p,fast-read; ← Fast Read 명령 사용
  • partitions {
  • compatible = "fixed-partitions";
  • #address-cells = ;
  • #size-cells = ;
  • boot@0 {
  • reg = ; ← 1MB 부트 파티션
  • label = "boot";
  • };
  • rootfs@100000 {
  • reg = ;
  • label = "rootfs";
  • };
  • };
  • };
  • ← CS1에 연결된 ADC
  • adc@1 {
  • compatible = "ti,ads7950";
  • reg = ; ← CS 번호 = 1
  • spi-max-frequency = ;
  • spi-cpol; ← CPOL=1 (Mode 2 or 3)
  • spi-cpha; ← CPHA=1 (Mode 1 or 3) → Mode 3
  • #io-channel-cells = ;
  • vref-supply = ;
  • };
  • }; */
/* === spidev — 유저스페이스 SPI 접근 ===
 *
 * /dev/spidevB.C (B=버스번호, C=CS번호)를 통해
 * 유저스페이스에서 직접 SPI 전송 가능.
 *
 * Device Tree에서 spidev 활성화:
 *   test_device@0 {
 *       compatible = "linux,spidev";  ← 또는 "rohm,dh2228fv" 등
 *       reg = <0>;
 *       spi-max-frequency = <1000000>;
 *   };
 */
/* 유저스페이스 SPI 프로그래밍 예시 */
#include <fcntl.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>

int main(void)
{
    int fd = open("/dev/spidev0.0", O_RDWR);

    /* SPI 모드 설정 */
    uint8_t mode = SPI_MODE_0;
    ioctl(fd, SPI_IOC_WR_MODE, &mode);

    /* 클럭 속도 설정 */
    uint32_t speed = 1000000;  /* 1 MHz */
    ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

    /* 전이중 전송 (ioctl) */
    uint8_t tx[] = {0x9F, 0x00, 0x00, 0x00};
    uint8_t rx[4] = {0};

    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = 4,
        .speed_hz = speed,
        .bits_per_word = 8,
    };
    ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
    /* rx[1..3] = JEDEC ID (제조사, 타입, 용량) */

    close(fd);
    return 0;
}
# SPI 디버깅/확인 명령

# 등록된 SPI 디바이스 확인
ls /sys/bus/spi/devices/
# spi0.0  spi0.1  spi1.0
# 형식: spi{버스}.{CS}

# SPI 디바이스 상세 정보
cat /sys/bus/spi/devices/spi0.0/modalias
# spi:spidev
cat /sys/bus/spi/devices/spi0.0/max_speed_hz
# 1000000

# SPI 컨트롤러 통계 (커널 4.16+)
cat /sys/class/spi_master/spi0/statistics/transfers
cat /sys/class/spi_master/spi0/statistics/bytes
cat /sys/class/spi_master/spi0/statistics/errors

# spi-tools를 이용한 유저스페이스 전송
# spi-config -d /dev/spidev0.0 -q  # 현재 설정 조회
# spi-pipe -d /dev/spidev0.0 -s 1000000 < tx_data > rx_data

# flashrom으로 SPI NOR Flash 읽기/쓰기
# flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=10000 -r backup.bin
SPI 주의사항:
  • DMA 버퍼 정렬spi_write_then_read()는 내부적으로 DMA-safe 버퍼를 할당하므로 편리하지만, 대량 전송 시 kmalloc()으로 할당한 DMA-safe 버퍼에 spi_sync() 사용. 스택/글로벌 변수는 DMA 불가
  • CS 타이밍 — 일부 디바이스는 CS assert 후 데이터 전송까지 딜레이 필요. spi_transfer.delay.value.cs_change_delay로 제어
  • CPOL/CPHA 불일치 — 모드가 디바이스 스펙과 맞지 않으면 데이터 corruption. 데이터시트의 타이밍 다이어그램에서 클럭 극성과 샘플링 에지 반드시 확인
  • CS GPIO — 하드웨어 CS가 부족하면 GPIO를 CS로 사용 (cs-gpios DT 속성). GPIO CS는 소프트웨어 제어라 속도 저하 가능
  • Quad/Dual SPIspi-rx-bus-width/spi-tx-bus-width DT 속성 설정 필수. 컨트롤러 드라이버가 SPI_RX_QUAD/SPI_TX_QUAD 지원해야 함
  • 전이중 vs 반이중 — 대부분의 SPI Flash는 명령/주소 전송 후 데이터 수신 (반이중 방식). spi_transfer에서 tx_bufrx_buf를 동시 설정하면 진정한 전이중. Flash 프로토콜에서는 각 phase를 별도 spi_transfer로 분리

GPIO 서브시스템

GPIO 서브시스템 아키텍처 사용자 공간 `/dev/gpiochipN` + libgpiod(gpioget/gpioset/gpiomon) `/sys/class/gpio`는 레거시(deprecated) GPIO Character Device (`gpiolib-cdev`) GPIO_V2_GET_LINE_IOCTL / LINE_SET_VALUES / LINE_GET_VALUES poll/read 기반 에지 이벤트 전달 gpiolib Core (`drivers/gpio/gpiolib.c`) Descriptor API: `gpiod_get()` / `gpiod_set_value()` `gpiochip_add_data()`로 칩 등록, IRQ 도메인/pinctrl 연동 GPIO mux, interrupt mapping, line ownership 관리 SoC 컨트롤러 gpio-mmio.c gpio-pl061.c gpio-dwapb.c / gpio-rockchip.c Expander 컨트롤러 I2C: gpio-pca953x.c SPI: gpio-mcp23s08.c 외부 GPIO 확장 칩 하드웨어 핀 LED / 버튼 / 리셋 / IRQ CS 라인 / 범용 디지털 I/O SoC + I2C/SPI 확장기 포함 권장 API: descriptor 기반(`gpiod_*`) + chardev v2 레거시 integer GPIO/sysfs export 경로는 신규 코드에서 지양
/* === GPIO 소비자 API (커널 드라이버에서 GPIO 사용) === */
#include <linux/gpio/consumer.h>

static int my_probe(struct platform_device *pdev)
{
    struct gpio_desc *reset_gpio, *irq_gpio;
    struct gpio_descs *leds;

    /* === 기본: 단일 GPIO 가져오기 ===
     * devm_gpiod_get(dev, con_id, flags)
     *   con_id: DT의 "{con_id}-gpios" 속성에서 가져옴
     *   flags:  GPIOD_OUT_LOW, GPIOD_OUT_HIGH, GPIOD_IN,
     *           GPIOD_OUT_LOW_OPEN_DRAIN, GPIOD_ASIS
     */
    reset_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_HIGH);
    if (IS_ERR(reset_gpio))
        return PTR_ERR(reset_gpio);

    /* === 선택적 GPIO (없어도 에러 아님) === */
    struct gpio_desc *enable_gpio;
    enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable", GPIOD_OUT_LOW);
    /* NULL 반환이면 DT에 해당 GPIO 없음 → 정상 */

    /* === 복수 GPIO 한번에 가져오기 === */
    leds = devm_gpiod_get_array(&pdev->dev, "led", GPIOD_OUT_LOW);
    /* DT: led-gpios = <&gpio1 0 0>, <&gpio1 1 0>, <&gpio1 2 0>;
     * leds->ndescs = 3, leds->desc[0..2] */

    /* === 인덱스로 GPIO 가져오기 (동일 이름 복수) === */
    struct gpio_desc *cs_gpio;
    cs_gpio = devm_gpiod_get_index(&pdev->dev, "cs", 1, GPIOD_OUT_HIGH);
    /* DT: cs-gpios = <&gpio1 8 0>, <&gpio1 7 0>; → index 1 = gpio1 7 */

    /* === GPIO 출력 제어 === */
    gpiod_set_value_cansleep(reset_gpio, 0);  /* 논리 LOW */
    msleep(10);
    gpiod_set_value_cansleep(reset_gpio, 1);  /* 논리 HIGH */

    /* === 복수 GPIO 한번에 설정 === */
    unsigned long values[] = { 0x05 }; /* 비트마스크: LED0=1, LED1=0, LED2=1 */
    gpiod_set_array_value_cansleep(leds->ndescs, leds->desc,
                                    leds->info, values);

    /* === GPIO 입력 읽기 === */
    int val = gpiod_get_value_cansleep(irq_gpio); /* 0 또는 1 (논리값) */

    /* === GPIO 방향 런타임 변경 === */
    gpiod_direction_output(reset_gpio, 1); /* 출력으로 변경, 값=1 */
    gpiod_direction_input(reset_gpio);      /* 입력으로 변경 */

    /* === GPIO → 인터럽트 변환 === */
    irq_gpio = devm_gpiod_get(&pdev->dev, "irq", GPIOD_IN);
    int irq = gpiod_to_irq(irq_gpio);
    if (irq < 0)
        return irq;

    devm_request_threaded_irq(&pdev->dev, irq,
        NULL,              /* hardirq handler (NULL → threaded only) */
        my_irq_handler,    /* threaded handler */
        IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
        "my_gpio_irq", priv);

    return 0;
}

/* === Device Tree 예시 === */
/* my_device {
 *     compatible = "vendor,my-device";
 *
 *     // GPIO 소비자 속성 (이름-gpios = <&컨트롤러 핀번호 플래그>)
 *     reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
 *     irq-gpios   = <&gpio2 12 GPIO_ACTIVE_HIGH>;
 *     enable-gpios = <&gpio1 20 GPIO_ACTIVE_HIGH>;
 *
 *     // 복수 GPIO (배열)
 *     led-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>,  // LED0
 *                 <&gpio1 1 GPIO_ACTIVE_HIGH>,  // LED1
 *                 <&gpio1 2 GPIO_ACTIVE_LOW>;   // LED2 (active low)
 *
 *     // CS GPIO (SPI 컨트롤러에서도 사용)
 *     cs-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>,
 *                <&gpio1 7 GPIO_ACTIVE_LOW>;
 * }; */
/* === GPIO 컨트롤러 드라이버 (gpio_chip 구현) ===
 *
 * SoC 내장 GPIO 블록이나 I2C/SPI GPIO expander를 제어.
 * struct gpio_chip의 콜백을 구현.
 */
#include <linux/gpio/driver.h>

struct my_gpio {
    struct gpio_chip gc;
    void __iomem *regs;
    struct irq_chip irq_chip;
    raw_spinlock_t lock;
};

/* 방향 설정 콜백 */
static int my_gpio_direction_input(struct gpio_chip *gc,
                                    unsigned int offset)
{
    struct my_gpio *priv = gpiochip_get_data(gc);
    u32 val;
    unsigned long flags;

    raw_spin_lock_irqsave(&priv->lock, flags);
    val = readl(priv->regs + GPIO_DIR_REG);
    val &= ~BIT(offset);   /* 0 = 입력 */
    writel(val, priv->regs + GPIO_DIR_REG);
    raw_spin_unlock_irqrestore(&priv->lock, flags);

    return 0;
}

static int my_gpio_direction_output(struct gpio_chip *gc,
                                     unsigned int offset, int value)
{
    struct my_gpio *priv = gpiochip_get_data(gc);
    u32 val;
    unsigned long flags;

    raw_spin_lock_irqsave(&priv->lock, flags);

    /* 출력 값 먼저 설정 */
    val = readl(priv->regs + GPIO_DATA_REG);
    if (value)
        val |= BIT(offset);
    else
        val &= ~BIT(offset);
    writel(val, priv->regs + GPIO_DATA_REG);

    /* 방향을 출력으로 설정 */
    val = readl(priv->regs + GPIO_DIR_REG);
    val |= BIT(offset);    /* 1 = 출력 */
    writel(val, priv->regs + GPIO_DIR_REG);

    raw_spin_unlock_irqrestore(&priv->lock, flags);

    return 0;
}

/* 값 읽기/쓰기 콜백 */
static int my_gpio_get(struct gpio_chip *gc, unsigned int offset)
{
    struct my_gpio *priv = gpiochip_get_data(gc);
    return !!(readl(priv->regs + GPIO_DATA_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 val;
    unsigned long flags;

    raw_spin_lock_irqsave(&priv->lock, flags);
    val = readl(priv->regs + GPIO_DATA_REG);
    if (value)
        val |= BIT(offset);
    else
        val &= ~BIT(offset);
    writel(val, priv->regs + GPIO_DATA_REG);
    raw_spin_unlock_irqrestore(&priv->lock, flags);
}

/* 복수 핀 한번에 읽기 (성능 최적화) */
static int my_gpio_get_multiple(struct gpio_chip *gc,
                                unsigned long *mask,
                                unsigned long *bits)
{
    struct my_gpio *priv = gpiochip_get_data(gc);
    *bits = readl(priv->regs + GPIO_DATA_REG) & *mask;
    return 0;
}

/* 복수 핀 한번에 쓰기 */
static void my_gpio_set_multiple(struct gpio_chip *gc,
                                  unsigned long *mask,
                                  unsigned long *bits)
{
    struct my_gpio *priv = gpiochip_get_data(gc);
    u32 val;
    unsigned long flags;

    raw_spin_lock_irqsave(&priv->lock, flags);
    val = readl(priv->regs + GPIO_DATA_REG);
    val = (val & ~*mask) | (*bits & *mask);
    writel(val, priv->regs + GPIO_DATA_REG);
    raw_spin_unlock_irqrestore(&priv->lock, flags);
}

/* GPIO 컨트롤러 등록 */
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->regs = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(priv->regs))
        return PTR_ERR(priv->regs);

    raw_spin_lock_init(&priv->lock);

    /* gpio_chip 초기화 */
    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.direction_input = my_gpio_direction_input;
    priv->gc.direction_output = my_gpio_direction_output;
    priv->gc.get = my_gpio_get;
    priv->gc.set = my_gpio_set;
    priv->gc.get_multiple = my_gpio_get_multiple;
    priv->gc.set_multiple = my_gpio_set_multiple;
    priv->gc.can_sleep = false;     /* MMIO → atomic 접근 가능 */

    return devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv);
}
/* === GPIO 인터럽트 지원 (irqchip 통합) ===
 *
 * GPIO 컨트롤러가 인터럽트를 지원하면 gpio_irq_chip을 설정.
 * gpiolib이 irq_domain을 자동 생성하여 gpiod_to_irq() 지원.
 */

static void my_gpio_irq_ack(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    struct my_gpio *priv = gpiochip_get_data(gc);
    writel(BIT(d->hwirq), priv->regs + GPIO_INT_CLR);
}

static void my_gpio_irq_mask(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    struct my_gpio *priv = gpiochip_get_data(gc);
    u32 val = readl(priv->regs + GPIO_INT_EN);
    val &= ~BIT(d->hwirq);
    writel(val, priv->regs + GPIO_INT_EN);
}

static void my_gpio_irq_unmask(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    struct my_gpio *priv = gpiochip_get_data(gc);
    u32 val = readl(priv->regs + GPIO_INT_EN);
    val |= BIT(d->hwirq);
    writel(val, priv->regs + GPIO_INT_EN);
}

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 *priv = gpiochip_get_data(gc);
    u32 val = readl(priv->regs + GPIO_INT_TYPE);

    switch (type & IRQ_TYPE_SENSE_MASK) {
    case IRQ_TYPE_EDGE_RISING:
        val |= BIT(d->hwirq);     /* 상승 에지 */
        break;
    case IRQ_TYPE_EDGE_FALLING:
        val &= ~BIT(d->hwirq);    /* 하강 에지 */
        break;
    case IRQ_TYPE_EDGE_BOTH:
        /* 양쪽 에지 — 하드웨어 지원 여부에 따라 SW 에뮬레이션 */
        break;
    case IRQ_TYPE_LEVEL_HIGH:
    case IRQ_TYPE_LEVEL_LOW:
        /* 레벨 트리거 설정 */
        break;
    default:
        return -EINVAL;
    }

    writel(val, priv->regs + GPIO_INT_TYPE);
    return 0;
}

/* GPIO 인터럽트 핸들러 (부모 IRQ에서 호출) */
static void my_gpio_irq_handler(struct irq_desc *desc)
{
    struct gpio_chip *gc = irq_desc_get_handler_data(desc);
    struct my_gpio *priv = gpiochip_get_data(gc);
    struct irq_chip *irqchip = irq_desc_get_chip(desc);
    u32 pending;

    chained_irq_enter(irqchip, desc);

    pending = readl(priv->regs + GPIO_INT_STATUS);
    while (pending) {
        int bit = __ffs(pending);
        generic_handle_domain_irq(gc->irq.domain, bit);
        pending &= ~BIT(bit);
    }

    chained_irq_exit(irqchip, desc);
}

/* probe에서 GPIO irqchip 등록 (커널 5.x+ 방식) */
static int my_gpio_probe_with_irq(struct platform_device *pdev)
{
    struct my_gpio *priv;
    struct gpio_irq_chip *girq;
    int parent_irq;

    /* ... gpio_chip 기본 설정 생략 ... */

    /* irq_chip 설정 */
    priv->irq_chip.name = "my-gpio-irq";
    priv->irq_chip.irq_ack = my_gpio_irq_ack;
    priv->irq_chip.irq_mask = my_gpio_irq_mask;
    priv->irq_chip.irq_unmask = my_gpio_irq_unmask;
    priv->irq_chip.irq_set_type = my_gpio_irq_set_type;
    priv->irq_chip.flags = IRQCHIP_IMMUTABLE;
    INIT_IRQ_DEFAULT_HANDLER(priv->irq_chip);

    parent_irq = platform_get_irq(pdev, 0);

    /* gpio_chip에 irqchip 연결 */
    girq = &priv->gc.irq;
    gpio_irq_chip_set_chip(girq, &priv->irq_chip);
    girq->parent_handler = my_gpio_irq_handler;
    girq->num_parents = 1;
    girq->parents = devm_kcalloc(&pdev->dev, 1,
                                   sizeof(*girq->parents), GFP_KERNEL);
    girq->parents[0] = parent_irq;
    girq->default_type = IRQ_TYPE_NONE;
    girq->handler = handle_bad_irq; /* set_type에서 변경 */

    return devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv);
}
/* === GPIO Expander (I2C/SPI 기반 외부 GPIO 컨트롤러) ===
 *
 * SoC의 GPIO 핀이 부족할 때 I2C/SPI로 연결하는 GPIO 확장 칩.
 * 커널에 이미 다수의 GPIO expander 드라이버가 포함되어 있음.
 *
 * 주요 칩과 커널 드라이버:
 *   ┌──────────────┬──────────┬────────┬─────────────────────────┐
 *   │ 칩           │ 인터페이스│ GPIO수 │ 커널 드라이버            │
 *   ├──────────────┼──────────┼────────┼─────────────────────────┤
 *   │ PCA9535/9555 │ I2C      │ 16     │ gpio-pca953x.c          │
 *   │ PCA9534/9538 │ I2C      │ 8      │ gpio-pca953x.c          │
 *   │ PCAL6524     │ I2C      │ 24     │ gpio-pca953x.c          │
 *   │ MCP23017     │ I2C      │ 16     │ gpio-mcp23s08.c (공용)  │
 *   │ MCP23S17     │ SPI      │ 16     │ gpio-mcp23s08.c         │
 *   │ PCF8574      │ I2C      │ 8      │ gpio-pcf857x.c          │
 *   │ TCA6416      │ I2C      │ 16     │ gpio-pca953x.c          │
 *   │ SX1509       │ I2C      │ 16     │ gpio-sx150x.c           │
 *   │ MAX7301      │ SPI      │ 28     │ gpio-max7301.c          │
 *   └──────────────┴──────────┴────────┴─────────────────────────┘
 *
 * I2C GPIO expander의 핵심 차이:
 *   - can_sleep = true → gpiod_set_value_cansleep() 만 사용 가능
 *   - IRQ 컨텍스트(hardirq)에서 접근 불가 → threaded IRQ 사용 필수
 *   - I2C 전송 지연으로 토글 속도 제한 (수십 kHz 이하)
 *
 * Device Tree 예시:
 *
 * &i2c1 {
 *     gpio_exp: gpio@20 {
 *         compatible = "nxp,pca9555";
 *         reg = <0x20>;
 *         gpio-controller;
 *         #gpio-cells = <2>;
 *         interrupt-parent = <&gpio1>;     ← SoC GPIO를 부모 IRQ로
 *         interrupts = <15 IRQ_TYPE_EDGE_FALLING>;
 *         interrupt-controller;
 *         #interrupt-cells = <2>;
 *
 *         // GPIO 라인 이름 지정 (디버깅용)
 *         gpio-line-names = "EXT_LED0", "EXT_LED1", "EXT_BTN0", "EXT_BTN1",
 *                           "EXT_CS0", "EXT_CS1", "EXT_RST", "EXT_EN",
 *                           "IO8", "IO9", "IO10", "IO11",
 *                           "IO12", "IO13", "IO14", "IO15";
 *     };
 * };
 *
 * // 다른 디바이스에서 expander의 GPIO 참조
 * my_device {
 *     enable-gpios = <&gpio_exp 7 GPIO_ACTIVE_HIGH>;  // EXT_EN
 *     reset-gpios  = <&gpio_exp 6 GPIO_ACTIVE_LOW>;   // EXT_RST
 * };
 */
설명 요약:
  • === GPIO와 pinctrl 연동 ===
  • 대부분의 SoC에서 GPIO 핀은 다중 기능(mux) 핀.
  • 동일 핀이 GPIO, UART TX, SPI MOSI 등으로 사용 가능.
  • pinctrl 서브시스템이 핀 기능 선택과 전기적 특성(풀업/풀다운, 드라이브 강도) 관리.
  • GPIO 요청 시 자동으로 pinctrl과 연동:
  • gpiod_get() → gpiolib → pinctrl_gpio_request()
  • → 핀을 GPIO 모드로 mux
  • gpiod_put() → gpiolib → pinctrl_gpio_free()
  • → 핀 해제
  • gpio_chip에서 pinctrl 연동을 위한 gpio_ranges 설정:
  • Device Tree에서 GPIO ↔ pinctrl 매핑:
  • gpio1: gpio@e6051000 {
  • compatible = "renesas,gpio-r8a7795";
  • reg = ;
  • #gpio-cells = ;
  • gpio-controller;
  • gpio-ranges = ;
  • // &pfc: pinctrl 노드
  • // 0: GPIO 시작 오프셋
  • // 32: pinctrl 핀 시작 번호
  • // 32: 핀 개수
  • // 핀 설정 (풀업, 드라이브 강도 등)은 pinctrl에서:
  • // &pfc {
  • // button_pins: button {
  • // pins = "GP_1_4";
  • // bias-pull-up;
  • // };
  • // };
  • };
# === GPIO 유저스페이스 도구 (libgpiod) ===
# libgpiod는 /dev/gpiochipN chardev 기반의 현대적 유저스페이스 GPIO 라이브러리
# sysfs (/sys/class/gpio/export)는 deprecated — libgpiod v2 사용 권장

# GPIO 컨트롤러 목록
gpiodetect
# gpiochip0 [pinctrl-bcm2835] (54 lines)
# gpiochip1 [pca9555] (16 lines)

# GPIO 라인 정보 (방향, 활성 상태, 사용자)
gpioinfo gpiochip0
# gpiochip0 - 54 lines:
#   line   0: "ID_SDA"    unused   input  active-high
#   line   1: "ID_SCL"    unused   input  active-high
#   line   2: "SDA1"      "i2c1"   input  active-high [used]
#   line  18: "GPIO18"    unused   input  active-high

# GPIO 값 읽기 (라인 번호 지정)
gpioget gpiochip0 18
# 0 또는 1

# GPIO 값 쓰기 (출력)
gpioset gpiochip0 18=1     # GPIO18을 HIGH
gpioset gpiochip0 18=0     # GPIO18을 LOW

# 복수 GPIO 동시 제어
gpioset gpiochip0 17=1 18=0 27=1

# GPIO 라인 이름으로 접근 (gpio-line-names DT 속성 설정 시)
gpioget --by-name EXT_BTN0
gpioset --by-name EXT_LED0=1

# GPIO 이벤트 모니터링 (에지 감지)
gpiomon gpiochip0 18
# 18  1  1707564890.123456789  rising
# 18  0  1707564891.234567890  falling

# 특정 에지만 감지
gpiomon --rising-edge gpiochip0 18
gpiomon --falling-edge gpiochip0 18

# 복수 라인 동시 모니터링
gpiomon gpiochip0 17 18 27

# 타임아웃 설정 (밀리초)
gpioget --bias=pull-up gpiochip0 18    # 풀업 활성화하고 읽기
gpioget --bias=pull-down gpiochip0 18  # 풀다운 활성화하고 읽기
gpioget --active-low gpiochip0 18      # active-low로 해석
# === GPIO sysfs 디버깅 정보 ===

# 등록된 모든 GPIO 컨트롤러 목록
ls /sys/bus/gpio/devices/
# gpiochip0  gpiochip1

# GPIO 컨트롤러 상세 정보
cat /sys/class/gpio/gpiochip0/label
# pinctrl-bcm2835
cat /sys/class/gpio/gpiochip0/ngpio
# 54
cat /sys/class/gpio/gpiochip0/base
# 0

# debugfs GPIO 상태 (커널 디버깅에 유용)
cat /sys/kernel/debug/gpio
# gpiochip0: GPIOs 0-53, parent: 20200000.gpio, pinctrl-bcm2835:
#  gpio-2   (SDA1                ) in  hi IRQ
#  gpio-3   (SCL1                ) in  hi IRQ
#  gpio-18  (                    ) out lo
# gpiochip1: GPIOs 496-511, parent: 1-0020, pca9555:
#  gpio-496 (EXT_LED0            ) out lo
#  gpio-497 (EXT_LED1            ) out hi

# pinctrl 연동 상태
cat /sys/kernel/debug/pinctrl/pinctrl-bcm2835/gpio-ranges
# GPIO ranges handled:
#  0: pinctrl-bcm2835 GPIOS [0 - 53] PINS [0 - 53]
GPIO 주의사항:
  • 레거시 API 사용 금지gpio_request()/gpio_set_value()는 deprecated. 반드시 gpiod_* descriptor API 사용. sysfs /sys/class/gpio/export도 deprecated → /dev/gpiochipN chardev 사용
  • cansleep 구분 — I2C/SPI GPIO expander는 can_sleep=true. 반드시 gpiod_set_value_cansleep() 사용. hardirq 컨텍스트에서 호출 시 BUG. gpiod_set_value()는 MMIO 기반 GPIO만 (atomic)
  • Active Low — Device Tree에서 GPIO_ACTIVE_LOW 플래그 시 gpiolib이 논리 반전 처리. gpiod_set_value(gpio, 1)은 논리 HIGH → 실제 물리 핀 LOW 출력. 드라이버는 항상 논리 값으로만 사용
  • DT 속성 이름 — GPIO 소비자 속성은 반드시 xxx-gpios 형식 (xxx-gpio는 레거시). devm_gpiod_get(dev, "xxx", ...)에서 "xxx"만 지정
  • 오픈 드레인/소스 — LED 같은 경우 GPIO_OPEN_DRAIN 플래그나 GPIOD_OUT_LOW_OPEN_DRAIN으로 설정. 하드웨어가 지원하지 않으면 gpiolib이 SW 에뮬레이션
  • 디바운싱 — 기계식 스위치/버튼은 gpiod_set_debounce(gpio, usec)로 하드웨어 디바운싱 설정. 미지원 시 SW 디바운스(gpio-keys 드라이버의 debounce-interval DT 속성)
  • GPIO hog — 특정 GPIO를 부팅 시 고정 상태로 설정: DT에서 gpio-hog; gpios = <5 0>; output-high; → 드라이버 없이도 GPIO 상태 보장
GPIO hog Device Tree 예시:
&gpio1 {
    usb_pwr_en {
        gpio-hog;
        gpios = <5 GPIO_ACTIVE_HIGH>;
        output-high;           /* 부팅 시 HIGH 출력 (USB 전원 활성) */
        line-name = "USB_PWR_EN";
    };
    debug_led {
        gpio-hog;
        gpios = <10 GPIO_ACTIVE_LOW>;
        output-low;            /* 부팅 시 LOW 출력 (LED 꺼짐) */
        line-name = "DEBUG_LED";
    };
};

SATA (libata) 서브시스템

계층커널 코드역할
SCSI 상위층 drivers/scsi/ 블록 I/O 요청을 SCSI 명령으로 변환
libata 코어 drivers/ata/libata-core.c ATA 명령 세트, EH(에러 핸들링), 링크 관리
AHCI 드라이버 drivers/ata/ahci.c AHCI HBA 레지스터 제어, NCQ, FIS 전송
하드웨어 SATA PHY, 디스크/SSD
# SATA 링크 상태 확인
dmesg | grep -i ata
# ata1: SATA link up 6.0 Gbps (SStatus 133 SControl 300)

# AHCI 포트 정보
cat /sys/class/ata_port/ata1/port_no

# NCQ (Native Command Queuing) 지원 확인
hdparm -I /dev/sda | grep -i ncq
# Queue depth: 32

# SATA 전원 관리 (ALPM)
cat /sys/class/scsi_host/host0/link_power_management_policy
# max_performance | medium_power | min_power | med_power_with_dipm

버스 비교 요약

버스토폴로지최대 속도프로빙 방식주요 용도
PCI/PCIe트리 (Root Complex → Switch → Endpoint)PCIe 5.0: 32 GT/s/lanePCI enumeration (자동)GPU, NIC, NVMe, QAT
I2C멀티마스터 직렬 버스3.4 MbpsDevice Tree / ACPI센서, EEPROM, PMIC
SPI마스터-슬레이브 (CS 라인)100+ MbpsDevice TreeFlash, ADC, 디스플레이
GPIO점대점 디지털 라인MHz급 토글Device Tree / ACPI리셋, LED, 인터럽트
SATA점대점 (포트별 디바이스)6 Gbps (SATA III)AHCI 자동 감지HDD, SSD (레거시)
NVMePCIe 직접 연결PCIe 대역폭PCI enumeration고성능 SSD
USB트리 (Hub 계층)20 Gbps (USB 3.2)USB enumeration (핫플러그)주변장치 범용
MDIO직렬 버스 (PHY 전용)2.5 MHzDevice Tree이더넷 PHY 제어
버스 드라이버 개발 공통 원칙:
  • 반드시 devm_* managed resource API 사용 (메모리 누수 방지)
  • Device Tree / ACPI 바인딩을 정확히 정의하고 Documentation/devicetree/bindings/에 문서화
  • 프로브 순서(probe ordering)에 의존하지 말 것 — -EPROBE_DEFER 반환으로 의존성 해결
  • 전원 관리(PM) 콜백 반드시 구현 — suspend/resume/runtime_pm
  • 에러 경로에서 모든 리소스 정리 — devm_* 사용 시 자동 정리되지만, 순서 의존적 해제 시 주의

버스 서브시스템과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.