임베디드 Linux에서 가장 많이 쓰이는 I2C/SPI/GPIO 서브시스템을 보드 초기화부터 드라이버 운영까지 실무 관점으로 정리합니다. I2C/SPI 전송 모델과 버스 arbitration, GPIO descriptor 기반 안전한 제어 패턴, Device Tree 바인딩 및 pinctrl 상호작용, regmap을 활용한 레지스터 추상화, IRQ-capable GPIO 처리, 전원관리와 슬립 복귀 시 상태 복원, 로직 분석기와 tracepoint를 이용한 타이밍 문제 진단까지 하드웨어 근접 드라이버 개발에 필요한 핵심을 다룹니다.
전제 조건: 디바이스 드라이버와 인터럽트 문서를 먼저 읽으세요. 버스/열거/프로브 경로는 초기화 순서와 자원 등록 규칙이 핵심이므로, 장치 발견부터 바인딩까지 흐름을 먼저 고정해야 합니다.
일상 비유: 이 주제는 터미널 입출고 게이트 운영과 비슷합니다. 차량(디바이스)이 들어오면 게이트 규칙(버스 규약)에 맞춰 배정하고 점검하듯이, 드라이버도 바인딩 규약을 정확히 따라야 합니다.
1. 핵심 요약
초기화 순서 — 탐색, 바인딩, 자원 등록 순서를 점검합니다.
제어/데이터 분리 — 빠른 경로와 설정 경로를 분리 설계합니다.
IRQ/작업 분할 — 즉시 처리와 지연 처리를 구분합니다.
안전 한계 — 전원/열/타이밍 임계값을 함께 관리합니다.
운영 복구 — 오류 시 재초기화와 롤백 경로를 준비합니다.
2. 단계별 이해
1. 장치 수명주기 확인
probe부터 remove까지 흐름을 점검합니다.
2. 비동기 경로 설계
IRQ, 워크큐, 타이머 역할을 분리합니다.
3. 자원 정합성 검증
DMA/클록/전원 참조를 교차 확인합니다.
4. 현장 조건 테스트
연결 끊김/복구/부하 상황을 재현합니다.
관련 표준: I2C-bus specification (NXP UM10204), SPI (Motorola/de facto), MIPI I3C (MIPI Alliance HCI 1.0) — 이 문서에서 다루는 버스 프로토콜의 기반 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
3. I2C 프로토콜 기초
I2C (Inter-Integrated Circuit)는 Philips(현 NXP)가 1982년 개발한 2-wire 직렬 버스입니다. 센서, EEPROM, RTC, PMIC 등 저속 주변장치 연결에 널리 사용됩니다.
신호선역할특성
신호선
역할
특성
SCL
Serial Clock
마스터가 생성, 오픈 드레인
SDA
Serial Data
양방향 데이터, 오픈 드레인
3.1 신호 프로토콜
I2C 통신의 기본 단위는 START 조건으로 시작하여 STOP 조건으로 끝나는 트랜잭션입니다:
조건
SDA 상태
SCL 상태
의미
START (S)
HIGH → LOW
HIGH
트랜잭션 시작
STOP (P)
LOW → HIGH
HIGH
트랜잭션 종료
Repeated START (Sr)
HIGH → LOW
HIGH
STOP 없이 재시작
ACK
LOW (수신측)
9번째 클럭
수신 확인
NACK
HIGH (수신측)
9번째 클럭
수신 거부 / 마지막 바이트
3.2 주소 체계
I2C는 7비트(표준)와 10비트(확장) 주소를 지원합니다. 7비트 주소의 경우 첫 번째 바이트는 [A6:A0 | R/W] 형식으로, 최하위 비트가 방향을 나타냅니다 (0 = Write, 1 = Read).
예약 주소: 0x00 (General Call), 0x01 (CBUS), 0x02 (다른 버스 형식), 0x03 (미래 용도), 0x04-0x07 (Hs-mode 마스터 코드), 0x78-0x7B (10비트 주소 prefix), 0x7C-0x7F (미래 용도)는 슬레이브 주소로 사용할 수 없습니다.
속도 모드
클럭
주파수 용도
Standard Mode (Sm)
100 kHz
일반 센서, EEPROM
Fast Mode (Fm)
400 kHz
가속도계, 터치 컨트롤러
Fast Mode Plus (Fm+)
1 MHz
고속 센서
High Speed Mode (Hs)
3.4 MHz
고속 메모리
4. Linux I2C 서브시스템
커널 I2C 서브시스템은 drivers/i2c/ 디렉토리에 구현되어 있으며, 다음 핵심 구조체로 구성됩니다:
구 조 체
역 할
헤 더
i2c_adapter
I2C 버스 컨트롤러 (마스터)
<linux/i2c.h>
i2c_algorithm
전송 알고리즘 (HW 접근 방법)
<linux/i2c.h>
i2c_client
I2C 버스 상의 슬레이브 디바이스
<linux/i2c.h>
i2c_driver
I2C 디바이스 드라이버
<linux/i2c.h>
i2c_msg
단일 I2C 메시지 (주소+데이터)
<linux/i2c.h>
4.1 i2c_adapter와 i2c_algorithm
i2c_adapter는 물리적 I2C 컨트롤러를 나타내며, i2c_algorithm을 통해 실제 하드웨어 전송을 수행합니다:
structi2c_algorithm{
int(*master_xfer)(structi2c_adapter*adap,
structi2c_msg*msgs,intnum);
int(*master_xfer_atomic)(structi2c_adapter*adap,
structi2c_msg*msgs,intnum);
int(*smbus_xfer)(structi2c_adapter*adap,
u16addr,unsignedshortflags,
charread_write,u8command,
intsize,unioni2c_smbus_data*data);
u32(*functionality)(structi2c_adapter*adap);
}
;
master_xfer vs smbus_xfer: master_xfer는 raw I2C 메시지를 전송하며, smbus_xfer는 SMBus 프로토콜에 최적화된 전송을 수행합니다. 대부분의 어댑터는 master_xfer만 구현하고, 커널이 SMBus 호출을 I2C 메시지로 에뮬레이션합니다.
4.2 i2c_client와 i2c_driver
i2c_client는 특정 어댑터의 특정 주소에 위치한 디바이스를 나타내며, i2c_driver가 이를 제어합니다:
structi2c_driver{
int(*probe)(structi2c_client*client);
void(*remove)(structi2c_client*client);
void(*shutdown)(structi2c_client*client);
structdevice_driverdriver;
conststructi2c_device_id*id_table;
};
5. I2C 드라이버 작성
실제 I2C 센서 드라이버 예제를 통해 작성 방법을 살펴봅니다. 아래는 가상의 온도 센서 드라이버입니다:
SPI_MEM_OP_DATA_IN(len, buf,4) /* 데이터 수신, 4-wire */
);
ret=spi_mem_exec_op(spi_mem,&op);
SPI NOR 프레임워크: drivers/mtd/spi-nor/의 SPI NOR 프레임워크는 spi-mem 위에서 동작하며, JEDEC 표준 명령어 셋을 자동으로 처리합니다. 새 Flash 칩 지원은 벤더별 파일에 파라미터만 추가하면 됩니다.
11.2 SPI Device Tree 바인딩
&spi1{
status="okay";
#address-cells =<1>;
#size-cells =<0>;
adc@0{
compatible ="vendor,my-adc";
reg =<0>;/* chip select 0 */
spi-max-frequency =<10000000>; /* 10 MHz */
spi-cpol;/* CPOL=1 (Mode 2 or 3) */
spi-cpha; /* CPHA=1 (Mode 1 or 3) */
/* spi-cpol + spi-cpha = Mode 3 */
};
flash@1{
compatible ="jedec,spi-nor";
reg =<1>;
spi-max-frequency =<50000000>;
spi-rx-bus-width =<4>;/* quad read */
spi-tx-bus-width =<4>;/* quad write */
m25p,fast-read;
};
};
12. GPIO 개요
GPIO (General-Purpose Input/Output)는 소프트웨어로 제어 가능한 범용 디지털 핀입니다. LED, 버튼, 리셋 라인, 칩 셀렉트, 인터럽트 입력 등 다양한 용도로 사용됩니다.
Linux GPIO 서브시스템은 drivers/gpio/에 구현되며, 크게 두 가지 API가 있습니다:
API헤더상태특징
API
헤 더
상 태
특 징
Legacy (integer-based)
<linux/gpio.h>
Deprecated
gpio_request(), gpio_direction_input()
Descriptor-based (gpiod)
<linux/gpio/consumer.h>
현재 표준
gpiod_get(), gpiod_set_value()
Legacy API 사용 금지:새 코드에서 gpio_request(), gpio_free(), gpio_get_value() 등 정수 기반 legacy API를 사용하지 마세요. 커널 메인라인에서는 legacy GPIO API를 사용하는 새 드라이버를 받아들이지 않습니다.
gpiod_set_value vs gpiod_set_raw_value: gpiod_set_value()는 Device Tree의 GPIO_ACTIVE_LOW 플래그를 자동 반영합니다. gpiod_set_raw_value()는 물리적 라인 레벨을 직접 제어합니다. 일반적으로 gpiod_set_value()를 사용하세요.
GPIO expander는 I2C 또는 SPI를 통해 GPIO 핀 수를 확장하는 디바이스입니다. 커널에서는 일반 GPIO 컨트롤러와 동일한 gpio_chip 인터페이스로 통합됩니다.
디바이스
인터페이스
GPIO 수
인터럽트
커널 드라이버
MCP23017
I2C
16
지원
gpio-mcp23s08
MCP23S17
SPI
16
지원
gpio-mcp23s08
PCA9555
I2C
16
지원
gpio-pca953x
PCA9535
I2C
16
지원
gpio-pca953x
PCF8574
I2C
8
지원
gpio-pcf857x
TCA6424A
I2C
24
지원
gpio-pca953x
16.1 GPIO Expander Device Tree예시
&i2c1{
gpio_exp: gpio-expander@20{
compatible ="nxp,pca9555";
reg =<0x20>;
gpio-controller;
#gpio-cells =<2>;
interrupt-parent =<&gpio1>;
interrupts =<12 IRQ_TYPE_EDGE_FALLING>;
interrupt-controller;
#interrupt-cells =<2>;
};
};
/* GPIO expander의 핀을 다른 디바이스에서 참조 */
my_led: led-controller {
compatible ="gpio-leds";
led-status {
gpios =<&gpio_exp 3 GPIO_ACTIVE_HIGH>;
label ="status";
linux,default-trigger ="heartbeat";
};
};
can_sleep 플래그: I2C/SPI 기반 GPIO expander는 버스 전송이 필요하므로 gpio_chip.can_sleep = true로 설정됩니다. 이 경우 인터럽트 컨텍스트에서 gpiod_get_value()를 호출할 수 없으며, 반드시 gpiod_get_value_cansleep()을 사용해야 합니다.
17. regmap: 레지스터 추상화 API
17.0 regmap 소개
임베디드 시스템이나 커널 드라이버 개발을 공부하시다 보면 반드시 마주치게 되는 개념이 바로 Regmap(Register Map)입니다.
regmap은 리눅스 커널에서 다양한 버스(I2C, SPI, MMIO)를 사용하는 장치 위의 레지스터 접근을 하나의 공통된 인터페이스로 통합 추상화하는 프레임워크입니다. 드라이버 코드에서 버스별 전송 함수 호출을 제거하고, 캐싱, 범위 검사, endian 변환 등 공통 기능을 투명하게 제공합니다.
1. Regmap이 필요한 이유
하드웨어 제어의 핵심은 레지스터에 값을 읽고 쓰는 것입니다. 그런데 장치마다 통신 방식이 다르면 드라이버 코드가 복잡해집니다.
기존 방식:I2C 장치용 드라이버는i2c_master_send()를 쓰고, SPI 장치는spi_write()를 써야 했습니다. 통신 방식이 바뀌면 드라이버 코드 자체를 대대적으로 수정해야 했죠.
Regmap 방식:드라이버는 그저regmap_write()하나만 호출하면 됩니다. 하단에서 Regmap이 해당 장치가 I2C인지 SPI인지 판단해서 적절한 통신을 수행합니다.
2. Regmap의 주요 장점
단순히 코드만 깔끔해지는 게 아니라, 강력한 기능들을 내장하고 있습니다.
추상화 (Abstraction):프로토콜에 상관없이 동일한 API(regmap_read,regmap_write)를 사용합니다.
캐싱 (Caching):레지스터 값을 로컬 메모리에 저장해둡니다. 매번 느린 I2C/SPI 통신을 하지 않고 메모리에서 바로 읽어올 수 있어 성능이 향상됩니다.
원자성 보장 (Locking):여러 프로세스가 동시에 레지스터에 접근할 때 발생할 수 있는 데이터 꼬임(Race condition)을 방지하기 위해 자체적으로 락(Lock) 기능을 제공합니다.
디버깅 용이성:debugfs를 통해 커널의 레지스터 상태를 파일 형태로 쉽게 확인할 수 있습니다.
3. 핵심 API 구조
개발자가 주로 사용하는 함수는 다음과 같습니다.
함수명
설명
regmap_init_i2c()
I2C 장치를 위한 regmap 초기화
regmap_read()
특정 레지스터에서 값 읽기
regmap_write()
특정 레지스터에 값 쓰기
regmap_update_bits()
특정 비트만 골라서 수정 (Read-Modify-Write 과정을 한 번에 처리)
4. 실제 동작 흐름
설정(Config):레지스터의 비트 수, 주소 범위, 캐시 방식 등을regmap_config구조체에 정의합니다.
enumregmap_endianval_format_endian_default; /* 버스 기본 엔디안 */
};
버스 타입별 초기화 함수:devm_regmap_init_i2c(), devm_regmap_init_spi(), devm_regmap_init_mmio() 외에도 devm_regmap_init_spi_avmm() (SPI Avalon-MM), devm_regmap_init_spmi_base/ext() (SPMI), devm_regmap_init_w1() (1-Wire), devm_regmap_init_sdw() (SoundWire), devm_regmap_init_slimbus() (SLIMbus) 등 다양한 버스를 지원합니다. fast_io = true인 버스(MMIO 등)는 mutex 대신 spinlock을 사용하여 원자적 컨텍스트에서도 접근 가능합니다.
17.6 regmap_field: 비트 필드 추상화
regmap_field는 레지스터 내 특정 비트 필드를 독립적인 객체로 추상화합니다. 비트 마스크/시프트 연산을 캡슐화하여 드라이버 코드의 가독성과 유지보수성을 높입니다.
#include<linux/regmap.h>
/* 레지스터 필드 정의 매크로 */
/* REG_FIELD(reg, lsb, msb) - 레지스터 주소와 비트 범위 지정 */
regmap_field vs regmap_update_bits: 직접 regmap_update_bits(regmap, 0x04, 0x7000, 0x5000)로 작성하면 매직 넘버가 코드 전체에 흩어집니다. regmap_field를 사용하면 필드 정의가 한곳에 집중되고, 드라이버 코드는 regmap_field_write(f_mode, 5)처럼 의미가 명확해집니다. IIO, regulator, clock 등 커널 서브시스템 드라이버에서 널리 사용됩니다.
17.7 레지스터 접근 테이블
콜백 함수 대신 regmap_access_table을 사용하면 테이블 기반으로 레지스터 접근 권한을 정의할 수 있습니다. 레지스터가 많은 디바이스에서 더 간결합니다.
접근 제어 우선순위: *_table과 *_reg() 콜백을 동시에 설정할 수 없습니다. precious 레지스터는 FIFO 포트처럼 읽기 자체가 부작용을 유발하는 레지스터입니다. debugfs의 register dump에서 자동 제외되어 디버깅 시 의도치 않은 데이터 손실을 방지합니다. no_ranges 필드를 사용하면 "이 범위를 제외한 나머지 전부"와 같은 역전 논리도 표현할 수 있습니다.
17.8 레지스터 윈도우와 페이지 매핑
일부 디바이스는 레지스터 공간이 커서, 페이지 레지스터를 통해 윈도우 방식으로 접근합니다. regmap_range_cfg는 이러한 페이지 기반 레지스터 접근을 투명하게 처리합니다.
regmap_read(regmap,0x185,&val); /* 자동으로: page=1 선택 → 0x85 읽기 */
regmap_write(regmap,0x281,0x42); /* 자동으로: page=2 선택 → 0x81 쓰기 */
실제 사용 사례: TI TAS2770/TAS2781 오디오 앰프, Maxim MAX77686 PMIC, NXP PCA9685 PWM 컨트롤러 등 레지스터 수가 256개를 초과하는 I2C/SPI 디바이스에서 활용됩니다. regmap이 페이지 전환을 자동 관리하므로 드라이버 코드에서 페이지 선택 로직이 완전히 제거됩니다.
17.9 다중 레지스터 연산
여러 레지스터를 원자적으로 읽거나 쓸 때 사용하는 고급 API입니다.
/* 다중 레지스터 쓰기 (시퀀스 보장) */
staticconststructreg_sequenceinit_seq[] ={
{0x01,0x0000}, /* CONFIG = 0 (리셋) */
REG_SEQ0(0x01,0x0000), /* 동일, delay_us = 0 */
{0x02,0x1234,1000}, /* DATA = 0x1234, 1ms 지연 후 다음 */
regmap_bulk_read/write: 연속 레지스터를 val_bits 단위로 읽기/쓰기. 엔디안 변환 적용
regmap_raw_read/write: 연속 레지스터를 바이트 스트림으로 전송. 엔디안 변환 없음. val_bits > 8일 때만 사용 가능
regmap_noinc_read/write: 주소 증가 없이 동일 레지스터에 반복 접근. FIFO 포트 등에 사용. regmap_config.read_flag_mask 설정 필요할 수 있음
17.10 regmap 캐시 심화
regmap 캐시는 레지스터 값의 로컬 복사본을 유지하여 불필요한 버스 트랜잭션을 줄입니다. 전원 관리와 긴밀하게 연동되며, 캐시 상태 관리를 통해 resume 시 효율적인 레지스터 복원을 수행합니다.
/*
* regmap 캐시 상태 머신 (위 SVG 참고)
*/
/* 캐시 제어 API */
/* 캐시 전용 모드: 버스 접근 차단, 캐시만 갱신 */
regcache_cache_only(regmap,true); /* suspend 시 */
regmap_write(regmap,0x01,0x42); /* 캐시에만 기록, dirty 마킹 */
regcache_cache_only(regmap,false); /* resume 시 */
/* 캐시 → HW 동기화: dirty 레지스터만 하드웨어에 기록 */
ret=regcache_sync(regmap);
/* 특정 범위만 동기화 */
ret=regcache_sync_region(regmap,0x00,0x0F);
/* 캐시 전체를 dirty로 마킹 (resume 시 전체 복원 강제) */
regcache_mark_dirty(regmap);
/* 캐시 무효화: 캐시된 값 폐기, 다음 읽기 시 HW 접근 */
ret=regcache_drop_region(regmap,0x00,0x0F);
/* 캐시 바이패스: 일시적으로 캐시 우회, HW 직접 접근 */
regcache_cache_bypass(regmap,true);
regmap_read(regmap,0x00,&val); /* HW에서 직접 읽기 */
regcache_cache_bypass(regmap,false);
캐시 타입
자료 구조
적합한 경우
메모리 사용
REGCACHE_NONE
없음
캐시 불필요 (volatile 위주)
0
REGCACHE_FLAT
배열
연속/조밀한 레지스터 맵
O(max_register)
REGCACHE_RBTREE
Red-Black Tree
희소(sparse) 레지스터 맵
O(사용 레지스터 수)
REGCACHE_MAPLE
Maple Tree
v6.4+, 범위 기반 최적화
O(사용 레지스터 수)
캐시 타입 선택 가이드: max_register가 작고 대부분의 레지스터를 사용하면 REGCACHE_FLAT이 가장 빠릅니다 (O(1) 접근). 레지스터 주소가 넓게 분산되어 있으면 REGCACHE_RBTREE나 REGCACHE_MAPLE이 메모리 효율적입니다. REGCACHE_MAPLE은 v6.4에서 추가되었으며, 연속 범위 탐색이 rbtree보다 캐시 친화적입니다.
17.11 regmap 전원 관리 연동
regmap 캐시는 시스템 suspend/resume과 runtime PM에서 핵심적인 역할을 합니다. 전원 차단 시 캐시 전용 모드로 전환하고, 복원 시 dirty 레지스터만 하드웨어에 동기화하여 resume 시간을 최소화합니다.
/* 시스템 suspend/resume */
staticintmy_suspend(structdevice*dev)
{
structmy_device*mydev=dev_get_drvdata(dev);
/* 디바이스 비활성화 */
regmap_update_bits(mydev->regmap,0x01,BIT(0),0);
/* 캐시 전용 모드: 이후 접근은 캐시에만 기록 */
regcache_cache_only(mydev->regmap,true);
/* 전체 캐시를 dirty로 마킹: resume 시 전체 복원 */
regcache_mark_dirty(mydev->regmap);
return0;
}
staticintmy_resume(structdevice*dev)
{
structmy_device*mydev=dev_get_drvdata(dev);
intret;
/* 캐시 전용 모드 해제: 버스 접근 재개 */
regcache_cache_only(mydev->regmap,false);
/* dirty 레지스터만 HW에 동기화 */
ret=regcache_sync(mydev->regmap);
if(ret)
dev_err(dev,"regcache sync failed: %d\\n",ret);
returnret;
}
/* Runtime PM과 regmap 연동 */
staticintmy_runtime_suspend(structdevice*dev)
{
structmy_device*mydev=dev_get_drvdata(dev);
regcache_cache_only(mydev->regmap,true);
regcache_mark_dirty(mydev->regmap);
/* 레귤레이터/클록 비활성화 */
regulator_disable(mydev->vdd);
clk_disable_unprepare(mydev->clk);
return0;
}
staticintmy_runtime_resume(structdevice*dev)
{
structmy_device*mydev=dev_get_drvdata(dev);
intret;
/* 레귤레이터/클록 활성화 */
ret=clk_prepare_enable(mydev->clk);
if(ret)
returnret;
ret=regulator_enable(mydev->vdd);
if(ret){
clk_disable_unprepare(mydev->clk);
returnret;
}
/* HW 안정화 대기 */
usleep_range(1000,1500);
regcache_cache_only(mydev->regmap,false);
ret=regcache_sync(mydev->regmap);
returnret;
}
staticDEFINE_RUNTIME_DEV_PM_OPS(my_pm_ops,
my_runtime_suspend, my_runtime_resume,NULL);
PM 연동 주의사항:
regcache_mark_dirty()는 반드시 regcache_cache_only(true) 이후에 호출해야 합니다. 순서가 바뀌면 dirty 마킹 후 HW 동기화가 시도되어 전원 차단된 디바이스에 버스 접근이 발생할 수 있습니다
resume 시 regcache_cache_only(false)는 반드시 HW가 준비된 이후에 호출하세요
디바이스가 소프트 리셋이 아닌 완전 전원 차단을 거치면, POR(Power-On Reset) 기본값이 적용되므로 mark_dirty로 전체 복원이 필요합니다
17.12 regmap debugfs 디버깅
regmap은 자동으로 debugfs 인터페이스를 생성하여 런타임에 레지스터 값을 검사할 수 있습니다. CONFIG_DEBUG_FS와 CONFIG_REGMAP이 활성화되어야 합니다.
# regmap debugfs 위치
/sys/kernel/debug/regmap/
# 디바이스별 디렉토리 예시
/sys/kernel/debug/regmap/0-001a/ # I2C bus 0, addr 0x1a
# 레지스터 값 확인
$ cat /sys/kernel/debug/regmap/0-001a/registers
00: 0042
01: 001f
02: 0000
03: abcd
...
# 접근 권한 확인
$ cat /sys/kernel/debug/regmap/0-001a/access
00: RV # R=readable, V=volatile
01: RW # R=readable, W=writable
02: RWv # v=volatile(소문자 = 캐시 안 함)
0a: RWP # P=precious(debugfs dump 제외)
# 캐시 상태 확인
$ cat /sys/kernel/debug/regmap/0-001a/cache_only
N # Y=캐시 전용 모드 활성
항 목
의 미
name
regmap 이름
range
레지스터 주소 범위
registers
레지스터 덤프 (precious 제외)
access
읽기/쓰기/volatile/precious 권한 정보
cache_only
캐시 전용 모드 상태
/* debugfs에서 regmap 이름 지정 */
staticconststructregmap_configmy_config={
.name ="main", /* debugfs 디렉토리에 이름 표시 */
.reg_bits =8,
.val_bits =16,
/* ... */
};
/* MFD 등에서 여러 regmap이 있을 때 구분에 유용:
* /sys/kernel/debug/regmap/0-001a-main/
* /sys/kernel/debug/regmap/0-001a-gpio/
*/
디버깅 팁: registers 파일에 값을 쓰면 런타임에 레지스터를 변경할 수 있습니다: echo "01 abcd" > registers. 이는 프로토타이핑 시 매우 유용하지만, precious 레지스터는 dump에서 자동 제외되므로 FIFO 데이터가 의도치 않게 소비되는 문제를 방지합니다. regmap 이름을 지정하면 ftrace의 regmap 이벤트에서도 구분하여 필터링할 수 있습니다: echo 'name == "main"' > /sys/kernel/debug/tracing/events/regmap/filter
17.13 MFD 디바이스와 regmap 공유
Multi-Function Device(MFD)에서는 하나의 regmap을 여러 서브 디바이스가 공유합니다. 부모 MFD 드라이버가 regmap을 생성하고, 자식 드라이버가 dev_get_regmap()으로 접근합니다.
성능 분석 패턴: regmap_cache_sync 이벤트로 resume 시 동기화되는 레지스터 수를 확인하고, regmap_hw_read_start/done 페어로 버스 지연을 측정할 수 있습니다. 캐시 적중률이 낮다면 volatile_reg 설정을 검토하세요. 불필요하게 volatile로 마킹된 레지스터가 성능 병목을 유발할 수 있습니다.
GPIO 서브시스템과 밀접하게 연동되며, 하나의 물리 핀이 GPIO, I2C SDA, SPI MOSI 등 여러 기능 중 하나로 설정될 수 있습니다.
18.1 pinctrl 핵심 개념
개 념
설 명
예 시
Pin Group
함께 설정되는 핀 그룹
i2c1_pins: {SDA, SCL}
Function
핀 그룹이 수행하는 기능
i2c, spi, gpio, uart
pinmux
핀과 기능의 매핑
PA9 → I2C1_SDA
pinconf
핀 전기적 특성 설정
풀업, 드라이브 강도, 슬루율
State
디바이스 상태별 핀 설정
default, sleep, idle
18.2 Device Tree pinctrl 바인딩
/* SoC pinctrl 노드에서 핀 설정 정의 */
&pinctrl{
i2c1_default: i2c1-default-pins {
pins ="PA9","PA10";
function ="i2c1";
bias-pull-up;
drive-open-drain;
};
i2c1_sleep: i2c1-sleep-pins {
pins ="PA9","PA10";
function ="gpio";
bias-high-impedance;
};
spi1_default: spi1-default-pins {
mosi-sck-pins {
pins ="PB3","PB5";
function ="spi1";
bias-disable;
drive-push-pull;
slew-rate =<1>; /* high speed */
};
miso-pin {
pins ="PB4";
function ="spi1";
bias-pull-down;
};
};
user_led_pin: user-led-pin {
pins ="PC13";
function ="gpio";
drive-push-pull;
output-low;
};
};
/* 디바이스 노드에서 pinctrl 상태 참조 */
&i2c1{
pinctrl-names ="default","sleep";
pinctrl-0=<&i2c1_default>;
pinctrl-1=<&i2c1_sleep>;
status="okay";
};
&spi1{
pinctrl-names ="default";
pinctrl-0=<&spi1_default>;
status="okay";
};
pinctrl 자동 전환: 디바이스가 pm_runtime_suspend()에 들어가면 커널이 자동으로 "sleep"상태의 핀 설정을 적용하고, resume 시 "default"로 복원합니다. 이 동작은 pinctrl-names에 "default"와 "sleep"이 정의되어 있을 때 활성화됩니다.
19. Device Tree 통합: 공통 바인딩 패턴
I2C, SPI, GPIO 서브시스템의 Device Tree 바인딩에서 공통적으로 사용되는 패턴을 정리합니다.
19.1 공통 프로퍼티
프로퍼티적용 대상설명
프 로 퍼 티
적 용 대 상
설 명
compatible
모든 디바이스
드라이버 매칭 문자열 (vendor,device)
reg
I2C: 슬레이브 주소, SPI: CS 번호
버스별 주소/식별자
interrupts
인터럽트 사용 디바이스
IRQ 스펙
interrupt-parent
인터럽트 사용 디바이스
IRQ 컨트롤러 phandle
status
모든 노드
"okay", "disabled"
*-gpios
GPIO 사용 디바이스
GPIO specifier
*-supply
전원 사용 디바이스
regulator phandle
pinctrl-*
핀 설정 필요 디바이스
pinctrl 상태
19.2 종합 예제: I2C + SPI + GPIO 연동
실제 임베디드 보드에서 I2C 센서, SPI Flash, GPIO LED/버튼을 함께 사용하는 Device Tree 예제:
I2C 디바이스가 감지되지 않을 때 체크리스트: (1) i2cdetect로 주소 응답 확인, (2) dmesg | grep i2c로 어댑터 등록 확인, (3) Device Tree의 reg 속성이 실제 하드웨어 주소와 일치하는지 확인, (4) pinctrl 설정이 올바른지 확인 (SDA/SCL 핀이 I2C 기능으로 mux 되었는지), (5) 풀업 저항이 있는지 확인 (오픈 드레인 버스에 외부 풀업 필요).
CAN Bus와 Linux SocketCAN을 실시간 제어 네트워크 관점에서 심층 분석합니다. 클래식 CAN과 CAN FD 프레임 구조, 비트 타이밍과 오류 프레임 처리, net_device 기반 드라이버 모델, SocketCAN RAW/BCM/ISOTP 소켓 활용, 버스 오프 복구와 상태 모니터링, timestamp/queue 설정을 통한 지연시간 관리, 차량·산업 장비 환경에서의 진단 프레임 운용, candump/cansniffer/iproute2 기반 디버깅 절차까지 안정적 필드 운영을 위한 핵심 내용을 다룹니다.
전제 조건: 네트워크 스택과 디바이스 드라이버 문서를 먼저 읽으세요. 특수 패브릭은 일반 이더넷과 다른 전송 제약과 하드웨어 모델을 가지므로, 버스 특성을 먼저 고정하고 접근해야 합니다.
일상 비유: 이 주제는 전용 철도 노선 운영과 비슷합니다. 일반 도로 규칙으로는 설명되지 않는 전용 신호/차량 규격이 있듯이, CAN/RDMA는 별도 운용 규칙이 필요합니다.
CAN(Controller Area Network)은 Robert Bosch GmbH가 1980년대에 개발한 멀티마스터 시리얼 버스 프로토콜입니다.
3.1 CAN 특성
멀티마스터 — 모든 노드가 버스 마스터 역할 가능
메시지 우선순위 — CAN ID로 결정 (낮은 ID = 높은 우선순위)
브로드캐스트 — 모든 노드가 모든 메시지 수신
에러 검출 — CRC, ACK, Bit Monitoring, Frame Check
Fault Confinement — 결함 노드 자동 격리
전송 속도 — 10 Kbps ~ 1 Mbps (CAN 2.0), 최대 8 Mbps (CAN FD)
3.2 CAN 계층
계 층
설 명
Application Layer
CANopen, J1939, UDS, ISO-TP
Data Link Layer
CAN 2.0A/B (Standard/Extended ID)
Physical Layer
CAN High/Low 차동 신호 (ISO 11898)
4. CAN 통신과 RS-485 의 비교
차량 제어나 산업 현장에서 가장 흔히 비교되는 두 통신 방식, CAN (Controller Area Network)과RS-485의 물리적 특성을 비교해 드릴게요.
두 방식 모두 '차동 신호(Differential Signaling)'를 사용하여 노이즈에 강하다는 공통점이 있지만, 그 내부 동작은 꽤 다릅니다.
4.1 하드웨어 구성 및 신호 방식
가장 큰 차이는 신호가 전압으로 어떻게 표현되느냐입니다.
CAN 통신:신호가 **Dominant(논리 0)**와Recessive(논리 1)상태로 나뉩니다. 두 선(CAN_H, CAN_L)의 전압 차이가 발생하면 Dominant, 없으면 Recessive입니다.
RS-485:두 선(A, B) 사이의 전압 극성(+/-)에 따라 데이터를 구분합니다.
항목
CAN (ISO 11898)
RS-485 (TIA/EIA-485)
신호선
CAN_H, CAN_L (2선)
A, B (2선 / 4선 Full-duplex 가능)
전압 상태
Dominant / Recessive
Differential Voltage (+ / -)
종단 저항
양 끝단 120Ω
양 끝단 120Ω
전송 방식
반이중 (Half-Duplex)
반이중 또는 전이중 (Full-Duplex)
4.2 물리 계층의 핵심 차이
1. 중재(Arbitration) 능력
CAN의 가장 똑똑한 점은충돌 방지입니다. 두 노드가 동시에 데이터를 보내면, 전압 특성상 Dominant(0)가 Recessive(1)를 이깁니다. 덕분에 데이터 충돌 없이 우선순위가 높은 메시지가 먼저 지나갑니다. 반면, RS-485는 두 노드가 동시에 보내면 신호가 깨져버립니다(Collision). 이를 막기 위해 소프트웨어적으로 "누구 차례인지" 관리하는 마스터-슬레이브 구조가 필수적입니다.
2. 연결 토폴로지
CAN:멀티-마스터 구조입니다. 어떤 노드든 원할 때 데이터를 보낼 수 있습니다.
RS-485:주로 1개의 마스터가 여러 슬레이브를 관리하는 구조에 최적화되어 있습니다.
4.3 사양 및 성능 비교
특성
CAN
RS-485
최대 속도
1 Mbps (CAN FD는 최대 5~8Mbps)
10 Mbps (거리에 따라 다름)
최대 거리
40m (1Mbps 기준) ~ 1km (50kbps)
최대 1.2km (100kbps 기준)
노드 수
통상 32~127개 (트랜시버 성능에 따름)
최대 32개 (Unit Load에 따라 256개까지)
결함 허용
매우 높음 (에러 감지 및 자동 재전송)
낮음 (사용자가 에러 처리를 구현해야 함)
4.4 요약: 무엇을 선택해야 할까요?
CAN을 선택해야 하는 경우:* 시스템의 안전성과 신뢰성이 최우선일 때 (자동차, 엘리베이터).
여러 노드가 마스터의 허락 없이 자유롭게 데이터를 보내야 할 때.
하드웨어 차원에서 자동 에러 검출이 필요할 때.
RS-485를 선택해야 하는 경우:
단순히 긴 거리(1km 이상)에 데이터를 저렴하게 보내고 싶을 때.
전송 속도가 매우 빨라야 할 때 (단거리 10Mbps).
기존의 시리얼(UART) 프로토콜을 그대로 활용하고 싶을 때.
5. SocketCAN
SocketCAN은 CAN을 Linux 네트워크 서브시스템에 통합하여 BSD 소켓 API로 CAN 통신을 제공합니다.
5.1 SocketCAN 아키텍쳐
6. CAN Frame
6.1 CAN 2.0 프레임 구조
#include<linux/can.h>
structcan_frame{
canid_tcan_id; /* 11-bit (Standard) or 29-bit (Extended) */
__u8 can_dlc;/* Data Length Code (0~8) */
__u8 __pad;/* padding */
__u8 __res0;/* reserved */
__u8 __res1;/* reserved */
__u8 data[8];/* CAN payload (0~8 bytes) */
};
/* CAN ID 플래그 */
#defineCAN_EFF_FLAG0x80000000U /* Extended Frame Format */