커널 버스 서브시스템 심화
Linux 커널 버스 서브시스템을 장치 열거, 식별, 바인딩, 전원 상태 전이의 공통 규약 관점에서 심층 분석합니다. PCI/PCIe, USB, SPI/I2C, AMBA, MFD, Regmap, MDIO, SDIO의 버스별 특성과 driver-model 접점, probe/defer/remove 순서, 버스별 주소 체계와 리소스 할당, Device Tree/ACPI 기술 정보 해석, 버스 오류 복구와 재탐색 전략, 성능 병목 관찰 지점까지 멀티버스 환경에서 안정적으로 드라이버를 운영하는 방법을 다룹니다.
전제 조건: 디바이스 드라이버와 인터럽트 문서를 먼저 읽으세요.
버스/열거/프로브 경로는 초기화 순서와 자원 등록 규칙이 핵심이므로, 장치 발견부터 바인딩까지 흐름을 먼저 고정해야 합니다.
일상 비유: 이 주제는 터미널 입출고 게이트 운영과 비슷합니다.
차량(디바이스)이 들어오면 게이트 규칙(버스 규약)에 맞춰 배정하고 점검하듯이, 드라이버도 바인딩 규약을 정확히 따라야 합니다.
핵심 요약
- 초기화 순서 — 탐색, 바인딩, 자원 등록 순서를 점검합니다.
- 제어/데이터 분리 — 빠른 경로와 설정 경로를 분리 설계합니다.
- IRQ/작업 분할 — 즉시 처리와 지연 처리를 구분합니다.
- 안전 한계 — 전원/열/타이밍 임계값을 함께 관리합니다.
- 운영 복구 — 오류 시 재초기화와 롤백 경로를 준비합니다.
단계별 이해
- 장치 수명주기 확인
probe부터 remove까지 흐름을 점검합니다. - 비동기 경로 설계
IRQ, 워크큐, 타이머 역할을 분리합니다. - 자원 정합성 검증
DMA/클록/전원 참조를 교차 확인합니다. - 현장 조건 테스트
연결 끊김/복구/부하 상황을 재현합니다.
관련 표준: 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-IOV —
pci_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 체크 바이트 (마지막에 추가)
*/
#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) 서브시스템
| 특성 | I2C | SMBus 3.0 | I3C (Basic) | I3C (HDR) |
|---|---|---|---|---|
| 최대 속도 | 3.4 Mbps (Hs) | 1 Mbps | 12.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, SCL | SDA, SCL | SDA, 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 디바이스 드라이버 개발 === */
#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-gpiosDT 속성). GPIO CS는 소프트웨어 제어라 속도 저하 가능 - Quad/Dual SPI —
spi-rx-bus-width/spi-tx-bus-widthDT 속성 설정 필수. 컨트롤러 드라이버가SPI_RX_QUAD/SPI_TX_QUAD지원해야 함 - 전이중 vs 반이중 — 대부분의 SPI Flash는 명령/주소 전송 후 데이터 수신 (반이중 방식).
spi_transfer에서tx_buf와rx_buf를 동시 설정하면 진정한 전이중. Flash 프로토콜에서는 각 phase를 별도spi_transfer로 분리
GPIO 서브시스템
/* === 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/gpiochipNchardev 사용 - 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-intervalDT 속성) - 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/lane | PCI enumeration (자동) | GPU, NIC, NVMe, QAT |
| I2C | 멀티마스터 직렬 버스 | 3.4 Mbps | Device Tree / ACPI | 센서, EEPROM, PMIC |
| SPI | 마스터-슬레이브 (CS 라인) | 100+ Mbps | Device Tree | Flash, ADC, 디스플레이 |
| GPIO | 점대점 디지털 라인 | MHz급 토글 | Device Tree / ACPI | 리셋, LED, 인터럽트 |
| SATA | 점대점 (포트별 디바이스) | 6 Gbps (SATA III) | AHCI 자동 감지 | HDD, SSD (레거시) |
| NVMe | PCIe 직접 연결 | PCIe 대역폭 | PCI enumeration | 고성능 SSD |
| USB | 트리 (Hub 계층) | 20 Gbps (USB 3.2) | USB enumeration (핫플러그) | 주변장치 범용 |
| MDIO | 직렬 버스 (PHY 전용) | 2.5 MHz | Device Tree | 이더넷 PHY 제어 |
버스 드라이버 개발 공통 원칙:
- 반드시
devm_*managed resource API 사용 (메모리 누수 방지) - Device Tree / ACPI 바인딩을 정확히 정의하고
Documentation/devicetree/bindings/에 문서화 - 프로브 순서(probe ordering)에 의존하지 말 것 —
-EPROBE_DEFER반환으로 의존성 해결 - 전원 관리(PM) 콜백 반드시 구현 —
suspend/resume/runtime_pm - 에러 경로에서 모든 리소스 정리 —
devm_*사용 시 자동 정리되지만, 순서 의존적 해제 시 주의
관련 문서
버스 서브시스템과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.