BSP 드라이버 추가 — 보드별 Peripheral 통합 흐름
#한 줄 요약
BSP의 드라이버 작업 80%는 새 드라이버를 쓰는 게 아니라 DT에 노드를 추가하는 일입니다. 기존 mainline 드라이버는 이미 충분히 풍부하므로, 보드별 peripheral을 통합할 때는 먼저 어떤 드라이버가 이 칩을 이미 지원하는지 찾는 것이 첫걸음입니다.
새 보드에 EEPROM, ADC, 가속도계, RTC를 붙였다고 가정합시다. 이때 BSP 엔지니어가 새 C 코드를 짤 일은 거의 없습니다. Linux는 이미 수천 개의 디바이스 드라이버를 갖고 있고, 대부분의 표준 chip(24c256 EEPROM, ads1015 ADC, mpu6050 IMU 등)은 그대로 동작합니다. 적절한 DT 노드만 작성하면 됩니다. 이 글은 그 흐름을 정리합니다.
#첫 번째 질문 — 드라이버가 이미 있는가?
# 칩 이름으로 검색$ grep -r "24c256" drivers/drivers/misc/eeprom/at24.c: { "24c256", (kernel_ulong_t)&at24_data_24c256 },
$ ls drivers/iio/adc/ti-ads*.cdrivers/iio/adc/ti-ads1015.cdrivers/iio/adc/ti-ads124s08.cdrivers/iio/adc/ti-ads7950.c
# Documentation/devicetree/bindings/ 에서 binding 확인$ find Documentation/devicetree/bindings -name "*ads1015*"Documentation/devicetree/bindings/iio/adc/ti,ads1015.yaml거의 모든 흔한 chip은 이미 드라이버가 있습니다. 없는 것이 오히려 예외입니다.
binding 문서는 DT 노드를 어떻게 써야 하는지의 정답입니다. 새 노드를 작성하기 전에 반드시 읽어야 합니다.
#사례 1 — I2C EEPROM 추가
24c256 EEPROM을 I2C 버스에 추가합니다.
먼저 보드의 schematic을 확인합니다. EEPROM의 SDA/SCL이 어느 I2C 컨트롤러에 연결됐는지, 7-bit 주소(WP 핀 strap에 따라 결정)가 무엇인지 확인합니다.
EEPROM: AT24C256Connected to: I2C1Address: 0x50 (A2=A1=A0=0)VCC: 3.3VWP: tied low (write enabled)DT 노드는 다음과 같습니다.
&i2c1 { clock-frequency = <100000>; status = "okay";
eeprom@50 { compatible = "atmel,24c256"; reg = <0x50>; pagesize = <64>; };};부팅 후 확인:
$ ls /sys/bus/i2c/devices/0-0050 1-0048 ...
$ cat /sys/bus/i2c/devices/0-0050/eeprom | hexdump -C00000000 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|새 C 코드 한 줄도 안 쓰고 EEPROM이 동작합니다. 이것이 device tree의 가치입니다.
#사례 2 — I2C ADC 추가 (IIO subsystem)
TI ADS1015 4채널 ADC를 I2C에 붙입니다.
&i2c1 { adc@48 { compatible = "ti,ads1015"; reg = <0x48>; #address-cells = <1>; #size-cells = <0>;
channel@0 { reg = <0>; /* AIN0 */ ti,gain = <2>; /* PGA = ±2.048V */ ti,datarate = <4>; /* 1600 SPS */ };
channel@1 { reg = <1>; /* AIN1 */ ti,gain = <2>; ti,datarate = <4>; }; };};부팅 후 IIO sysfs로 읽기:
$ ls /sys/bus/iio/devices/iio:device0
$ cat /sys/bus/iio/devices/iio:device0/nameads1015
$ cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw12345
$ cat /sys/bus/iio/devices/iio:device0/in_voltage0_scale0.062500raw × scale = mV 변환은 사용자 공간에서 처리합니다.
#사례 3 — pinctrl과 GPIO
새 peripheral을 추가할 때 핀이 다른 기능과 충돌하지 않는지가 중요합니다. SoC 핀은 보통 여러 기능을 다중화하며, pinctrl이 이를 관리합니다.
&iomuxc { pinctrl_i2c1: i2c1grp { fsl,pins = < MX8MM_IOMUXC_I2C1_SCL_I2C1_SCL 0x400001c3 MX8MM_IOMUXC_I2C1_SDA_I2C1_SDA 0x400001c3 >; };
pinctrl_gpio_led: gpioledgrp { fsl,pins = < MX8MM_IOMUXC_NAND_CE0_B_GPIO3_IO1 0x19 >; };};
&i2c1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; /* ... */};
leds { compatible = "gpio-leds"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpio_led>;
status { label = "status"; gpios = <&gpio3 1 GPIO_ACTIVE_HIGH>; default-state = "off"; };};pinctrl-single,pins는 generic binding이고 i.MX는 fsl,pins, Rockchip은 rockchip,pins처럼 vendor 고유 binding을 씁니다. SoC 패밀리별 매크로 헤더(imx8mm-pinfunc.h 등)를 include해야 합니다.
확인:
$ cat /sys/kernel/debug/pinctrl/30330000.pinctrl/pinmux-functions | head$ cat /sys/kernel/debug/pinctrl/30330000.pinctrl/pinmux-pins | head#사례 4 — regulator binding
전원 공급에 PMIC가 있다면 regulator framework로 도메인을 관리합니다. EEPROM에 3.3V를 공급하는 LDO를 모델링하고 디바이스 노드가 이를 참조하게 만듭니다.
&i2c2 { pmic@25 { compatible = "nxp,pca9450"; reg = <0x25>;
regulators { buck1_reg: BUCK1 { regulator-name = "BUCK1"; regulator-min-microvolt = <600000>; regulator-max-microvolt = <2187500>; regulator-always-on; };
ldo3_reg: LDO3 { regulator-name = "LDO3"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; regulator-always-on; }; }; };};
&i2c1 { eeprom@50 { compatible = "atmel,24c256"; reg = <0x50>; vcc-supply = <&ldo3_reg>; /* 이 줄로 LDO3에 의존 */ };};vcc-supply가 있으면 EEPROM probe 시 regulator를 enable하고, remove 시 disable합니다. runtime PM과 결합되면 전력 절감 효과가 큽니다.
확인:
$ cat /sys/kernel/debug/regulator/regulator_summary regulator use open bypass voltage current min max--------------------------------------------------------------------------------- LDO3 1 1 0 3300mV 0mV 3300mV 3300mV eeprom@50 3300mV 0mV 3300mV 3300mV#사례 5 — SPI flash 추가
NOR flash를 SPI에 붙입니다.
&spi1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_spi1>; cs-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; status = "okay";
flash@0 { compatible = "jedec,spi-nor"; reg = <0>; spi-max-frequency = <50000000>; spi-tx-bus-width = <1>; spi-rx-bus-width = <1>;
partitions { compatible = "fixed-partitions"; #address-cells = <1>; #size-cells = <1>;
partition@0 { label = "u-boot-env"; reg = <0x0 0x40000>; };
partition@40000 { label = "config"; reg = <0x40000 0x1c0000>; }; }; };};부팅 후 /dev/mtd0, /dev/mtd1로 노출됩니다.
$ cat /proc/mtddev: size erasesize namemtd0: 00040000 00010000 "u-boot-env"mtd1: 001c0000 00010000 "config"
$ mtd_debug read /dev/mtd0 0 1024 dump.bin#사례 6 — MMC slot
SD/eMMC slot도 거의 DT만으로 동작합니다.
&usdhc2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usdhc2>; cd-gpios = <&gpio2 12 GPIO_ACTIVE_LOW>; /* card detect */ vmmc-supply = <®_usdhc2_vmmc>; bus-width = <4>; no-mmc; /* SD 전용 */ no-sdio; status = "okay";};vmmc-supply는 SD 카드 전원 LDO. cd-gpios는 카드 삽입 감지 핀. bus-width = <4>는 4-bit data.
#새 드라이버를 작성해야 하는 경우
다음 조건이 모두 만족하면 새 드라이버가 필요합니다.
- Vendor proprietary IP라서 기존 드라이버가 없음.
- 같은 카테고리의 generic framework(IIO, GPIO, regulator 등)가 없거나 IP 동작이 너무 다름.
- binding 문서를 새로 작성할 만큼 재사용 가능함.
이 경우 다음 절차입니다.
drivers/<subsystem>/<vendor>_<chip>.c작성.- Kconfig·Makefile 등록.
Documentation/devicetree/bindings/<subsystem>/<vendor>,<chip>.yaml작성.- DT 노드 작성.
- probe·remove·suspend·resume 구현.
devm_*자원 관리.- 검토 후 upstream 제안.
최소 골격은 다음과 같습니다.
#include <linux/module.h>#include <linux/of.h>#include <linux/platform_device.h>
struct mychip_data { void __iomem *base; int irq; struct clk *clk;};
static int mychip_probe(struct platform_device *pdev){ struct mychip_data *priv; struct resource *res;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->base)) return PTR_ERR(priv->base);
priv->irq = platform_get_irq(pdev, 0); if (priv->irq < 0) return priv->irq;
priv->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk);
clk_prepare_enable(priv->clk); platform_set_drvdata(pdev, priv); return 0;}
static int mychip_remove(struct platform_device *pdev){ struct mychip_data *priv = platform_get_drvdata(pdev); clk_disable_unprepare(priv->clk); return 0;}
static const struct of_device_id mychip_of_match[] = { { .compatible = "myvendor,mychip-v1" }, { }};MODULE_DEVICE_TABLE(of, mychip_of_match);
static struct platform_driver mychip_driver = { .driver = { .name = "mychip", .of_match_table = mychip_of_match, }, .probe = mychip_probe, .remove = mychip_remove,};module_platform_driver(mychip_driver);
MODULE_LICENSE("GPL v2");MODULE_DESCRIPTION("MyVendor MyChip driver");devm_* 함수는 디바이스 lifetime에 자원을 묶어 줍니다. probe 실패나 remove 시 자동으로 해제됩니다.
#DT binding 문서 작성
새 드라이버에는 binding 문서가 따라옵니다. YAML 형식입니다.
# Documentation/devicetree/bindings/misc/myvendor,mychip.yaml%YAML 1.2---$id: http://devicetree.org/schemas/misc/myvendor,mychip.yaml#$schema: http://devicetree.org/meta-schemas/core.yaml#
title: MyVendor MyChip controller
maintainers: - Sang-Deok Yoon <hawking90a@gmail.com>
properties: compatible: enum: - myvendor,mychip-v1
reg: maxItems: 1
interrupts: maxItems: 1
clocks: maxItems: 1
required: - compatible - reg - interrupts - clocks
additionalProperties: false
examples: - | mychip@10000000 { compatible = "myvendor,mychip-v1"; reg = <0x10000000 0x1000>; interrupts = <0 42 4>; clocks = <&clks 23>; };validation:
make dt_binding_check DT_SCHEMA_FILES=myvendor,mychip.yamlmake dtbs_check DT_SCHEMA_FILES=myvendor,mychip.yaml#커널 모듈 로드 순서
빌트인 드라이버는 link order대로 init합니다. 모듈은 modprobe 또는 udev에 의해 부팅 후 로드됩니다.
순서 의존성을 명시적으로 표현하는 방법:
/* 이 드라이버가 다른 subsystem을 *먼저* 필요로 함 */module_init(mychip_init); /* 일반 */late_initcall(mychip_late_init); /* 늦게 */subsys_initcall(myframework_init); /* subsystem 단계 */DT의 phandle 참조(clocks = <&clks 23>)가 있으면 deferred probe로 의존성 자동 해결됩니다. 의존 디바이스가 아직 probe 안 됐으면 EPROBE_DEFER(-517) 반환하고 나중에 재시도합니다.
# probe 실패한 디바이스 확인$ cat /sys/kernel/debug/devices_deferred30890000.serial consumers: 30330000.pinctrl#흔한 실수
- binding 문서 확인 없이 DT 노드 작성: 속성 이름·단위·예제가 정답입니다. 무시하면 probe 실패.
status = "disabled"노드를 enable 안 함: 기본 DT에status = "disabled"로 적힌 노드는 보드 DTS에서&node { status = "okay"; }로 켜야 합니다.- regulator/clock 누락: 디바이스가 처음 동작은 하지만 suspend 후 동작 불가.
vcc-supply,clocks명시. - pinctrl 충돌: 같은 핀을 두 디바이스가 잡으면 후순위가 probe 실패. dmesg에
unable to claim pin이 보임. - out-of-tree 드라이버 남발: 모든 vendor 수정사항을 out-of-tree로 두면 kernel 업그레이드마다 rebase 지옥. 가능한 한 upstream 또는 in-tree로.
#정리
- 새 chip 통합의 첫걸음은 이미 있는 드라이버 찾기이며, 대부분의 표준 chip은 mainline에 있습니다.
- DT 노드만 추가하면 EEPROM, ADC, IMU, RTC, flash, MMC 등이 동작합니다.
Documentation/devicetree/bindings/의 YAML 문서가 정답이며, 새 노드를 쓰기 전에 반드시 읽습니다.- pinctrl, regulator, clock binding은 보드의 거의 모든 디바이스가 의존하는 공통 기반입니다.
- 새 드라이버를 짤 때는 platform_driver, of_match, devm_* 자원 관리, EPROBE_DEFER를 익혀 둡니다.
- binding 문서는
make dt_binding_check로 schema validation합니다. - 빌트인 vs 모듈 결정은 Ch 8의 기준을 따릅니다.
- out-of-tree 드라이버는 upstream 머지 전 임시 수단으로만 씁니다.
#다음 편 예고
Ch 13: Power Management에서는 suspend/resume, runtime PM, regulator framework, cpuidle/cpufreq를 살펴봅니다.
#관련 항목
- Ch 7: Device Tree 작성
- Ch 8: Linux 커널 설정
- Ch 13: Power Management
- Ch 14: Thermal과 watchdog
- Modern Embedded Recipes — driver 패턴
- Practical RTOS Internals — interrupt 처리 비교
BSP Development · 12 of 21
- 1BSP의 본질 분해 — 새 보드 부팅을 위한 코드의 자리
- 2SoC 데이터시트 읽기 — Pin Mux·Clock·Memory Map 파악법
- 3새 보드 Device Tree 설계 — node·property·phandle 작성 흐름
- 4Pin Mux와 Clock Tree 분석 — 보드 부팅의 첫 두 관문
- 5DDR 매개변수 결정 — 보드별 Timing·Training 추출
- 6U-Boot 새 보드 포팅 — defconfig·board.c·DTS 작성 흐름
- 7TF-A·TrustZone 통합 — BL31·secure world·SMC 흐름 적용
- 8Linux 커널 BSP 설정 — defconfig·Kconfig·DT 통합
- 9Multi-core SMP Bring-up — PSCI·Secondary CPU 깨우기
- 10첫 부팅 추적 — 0%부터 login prompt까지의 단계 분석
- 11부트로그 디버깅 — earlyprintk·loglevel·serial 추적
- 12BSP 드라이버 추가 — 보드별 Peripheral 통합 흐름
- 13BSP Power Management — Suspend/Resume·Runtime PM·Regulator
- 14BSP Thermal과 Watchdog — Trip Point·Cooling Device·Hardware Reset
- 15BSP 부트 시간 최적화 — Bootchart·initcall_debug·Parallel Init
- 16BSP RootFS 통합 — Buildroot·Yocto와 보드별 패키지 묶기
- 17BSP 이미지 패키징 — Flash Layout·Partition·GPT 설계
- 18BSP OTA와 Field Recovery — A/B 슬롯·롤백·BootCount
- 19BSP Stability Testing — Stress·Soak·Power Cycle 시나리오
- 20BSP 양산 환경 구축 — CI/CD·재현 가능 빌드·서명
- 21BSP 유지보수 — 업스트림 기여·커널 버전업·LTS 전략
관련 글
이 글을 참조하는 글 (5)
- Buildroot 패키지 시스템 분석 — .mk와 Config.in 동작 추적— Buildroot Practical
- BSP Thermal과 Watchdog — Trip Point·Cooling Device·Hardware Reset— BSP Development
- BSP Power Management — Suspend/Resume·Runtime PM·Regulator— BSP Development
- 부트로그 디버깅 — earlyprintk·loglevel·serial 추적— BSP Development
- 첫 부팅 추적 — 0%부터 login prompt까지의 단계 분석— BSP Development