00 Device Tree nedir ve tarihi
ARM'ın board-specific kod patlaması ve Linus'un "f*cking pain" kararıyla başlayan mimari dönüşüm — donanım tanımını kernel binary'sinden ayırmanın hikayesi.
Cihaz tanımlaması sorunu
2011 yılına kadar Linux kernel ARM platformlarını arch/arm/mach-*/ dizinleri altında board bazında hardcoded C kodu ile destekliyordu. Her yeni board için ayrı bir board_init(), ayrı kaynak konfigürasyonları (IRQ numaraları, base adresler, pin tanımları) ve ayrı platform_data yapıları yazılıyordu. Bu yaklaşım on yılda yüzlerce farklı board dosyasının kernel ağacına girmesiyle sürdürülemez hale geldi.
BIOS vs Open Firmware vs Device Tree
| Mekanizma | Platform | Çalışma şekli |
|---|---|---|
| BIOS / UEFI + ACPI | x86 / x86-64 | Firmware tablolar aracılığıyla donanımı işletim sistemine açıklar |
| Open Firmware (IEEE 1275) | PowerPC, SPARC | Firmware'deki device tree yapısı — ARM'ın ilham aldığı sistem |
| Device Tree (FDT) | ARM, ARM64, RISC-V, MIPS | Ayrı .dtb dosyası — bootloader tarafından kernel'a aktarılır |
| Hardcoded C | Eski ARM (mach-*) | Donanım tanımı kernel kaynak koduna gömülü — değiştirilemez |
Linus'un "f*cking pain" kararı
Mart 2011'de Linus Torvalds, ARM maintainer'larına yazdığı e-postada: "Gaah. Guys, this whole ARM thing is a f*cking pain in the ass." diyerek mevcut durumu açıkça eleştirdi. Bir haftadan kısa sürede Russell King ARM maintainer'lığından ayrıldı ve yeni kural netleşti: mainline kernel'a kabul edilmek isteyen her yeni ARM board, hardcoded C platform data yerine Device Tree kullanmak zorundaydı. Bu tarih modern embedded Linux geliştirmenin dönüm noktasıdır.
DTS, DTB, DTSI ayrımı
Derleme ve boot zinciri
board.dts + soc.dtsi → dtc → board.dtb → U-Boot → kernel r2 register → of_* API → driver probe
Kernel'in DT'yi nasıl kullandığı
Bootloader (genellikle U-Boot), DTB binary'sini belleğe yükler ve ARM register r2 (veya AArch64'te x0) üzerinden kernel'a adresini geçirir. Kernel başlangıçta setup_machine_fdt() ile DTB'yi parse eder, of_* fonksiyonları ile node'ları tarar ve her node'un compatible string'ini driver veritabanı ile eşleştirir. Eşleşen driver'ın probe() fonksiyonu çağrılır.
Bu bölümde
- Hardcoded ARM mach-* kodu → 2011 Linus kararı → Device Tree zorunluluğu
- .dts (kaynak) → dtc → .dtb (binary) → U-Boot → kernel of_* API
- .dtsi: SoC paylaşımlı tanımlar; .dtbo: runtime overlay
- ARM, ARM64, RISC-V, MIPS, PowerPC'de DT kullanımı; x86'da ACPI tercih edilir
01 DTS sözdizimi
Node yapısı, property tipleri, #address-cells / #size-cells ve /dts-v1/ zorunlu başlık.
Node yapısı: node-name@unit-address
/* node-adı @ birim-adresi { property'ler; alt-node'lar; }; */
/* unit-address = reg property'nin ilk değeri (hex) */
uart0: serial@44E09000 {
compatible = "ti,am335x-uart", "ti,omap2-uart";
reg = <0x44E09000 0x1000>; /* base address, size */
status = "disabled"; /* SoC DTSI'da genellikle disabled */
};
Property tipleri
| Tip | Sözdizimi | Örnek |
|---|---|---|
| String | "değer" | compatible = "ti,am335x-uart" |
| String listesi | "s1", "s2" | clock-names = "fck", "ick" |
| u32 (32-bit) | <0x1234> | clock-frequency = <48000000> |
| u32 dizisi | <v1 v2 v3> | reg = <0x44E09000 0x1000> |
| u64 (64-bit) | /bits/ 64 <val> | reg = /bits/ 64 <0x100000000> |
| Byte array | [HH HH ...] | local-mac-address = [00 1A 2B 3C 4D 5E] |
| Boolean (flag) | Değersiz property | interrupt-controller; |
| Phandle | &label | clocks = <&uart_fck> |
Tam minimal DTS dosyası
/dts-v1/; /* zorunlu başlık */
/* Önişlemci include — C gibi */
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
/ { /* kök node */
#address-cells = <1>; /* reg'deki address: 1 × u32 */
#size-cells = <1>; /* reg'deki size: 1 × u32 */
model = "My Custom IoT Board";
compatible = "myvendor,myboard", "myvendor,mychip";
/* Bellek */
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; /* 256 MB @ 0x80000000 */
};
/* Seçilen UART (board seviyesi enable) */
&uart0 {
status = "okay";
};
};
#address-cells ve #size-cells
/* #address-cells=1, #size-cells=1 → reg = <address size> */
spi0@48030000 {
#address-cells = <1>; /* child'ın reg address'i 1 u32 */
#size-cells = <0>; /* SPI slave'de size yok */
reg = <0x48030000 0x400>;
flash@0 {
reg = <0>; /* chip select 0 — size-cells=0 olduğu için tek u32 */
};
};
/* 64-bit adreslemede #address-cells=2 */
/ {
#address-cells = <2>; /* 2 × u32 = 64-bit adres */
#size-cells = <2>;
pcie@40000000000 {
reg = <0x00000004 0x00000000 0x00000000 0x01000000>;
/* [addr-hi addr-lo size-hi size-lo] */
};
};
Comment ve include direktifi
/* C tarzı blok yorum */
// C++ tarzı satır yorum (dtc destekler)
/* SoC paylaşımlı DTSI'ı dahil et */
#include "am33xx.dtsi"
#include "am335x-bone-common.dtsi"
/* dt-bindings sabitlerini dahil et */
#include <dt-bindings/gpio/gpio.h> /* GPIO_ACTIVE_LOW, GPIO_ACTIVE_HIGH */
#include <dt-bindings/interrupt-controller/arm-gic.h> /* GIC_SPI, IRQ_TYPE_* */
Bu bölümde
node-name@unit-address: unit-address = reg'in ilk değeri (hex)- Property tipleri: string, u32, byte array, boolean, phandle
#address-cellsve#size-cells: alt node'larınregyorumunu belirler/dts-v1/;zorunlu başlık; C önişlemci#includedirektifi desteklenir
02 Temel property'ler
compatible, status, clocks, interrupts, pinctrl — bir peripheral node'unun olmazsa olmaz property'leri.
status — etkinleştirme mekanizması
SoC DTSI dosyaları tüm peripheral'ları genellikle status = "disabled" olarak tanımlar. Board .dts dosyasında kullanılacak peripheral'lar &node { status = "okay"; }; ile etkinleştirilir. Bu yaklaşım aynı SoC'un farklı board'larında farklı peripheral setlerinin çalışmasını sağlar.
/* SoC DTSI'da (am33xx.dtsi): */
uart0: serial@44E09000 {
compatible = "ti,am335x-uart";
reg = <0x44E09000 0x2000>;
status = "disabled"; /* SoC'ta varsayılan kapalı */
};
/* Board .dts'de (am335x-boneblack.dts): */
&uart0 {
status = "okay"; /* bu board'da aktif et */
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
};
clocks ve clock-names
uart0: serial@44E09000 {
compatible = "ti,am335x-uart";
reg = <0x44E09000 0x2000>;
/* clocks dizisi: phandle + clock ID çiftleri */
clocks = <&uart0_fck>, <&uart0_ick>;
clock-names = "fck", "ick";
/* Driver: clk_get(&dev, "fck") ile alır */
};
interrupts ve interrupt-parent
uart0: serial@44E09000 {
compatible = "ti,am335x-uart";
reg = <0x44E09000 0x2000>;
interrupt-parent = <&intc>; /* hangi interrupt controller */
interrupts = <72>; /* AM335x UART0 = IRQ 72 */
};
/* GIC kullanan ARM SoC için (AArch64 gibi): */
uart1: serial@FF110000 {
compatible = "snps,dw-apb-uart";
reg = <0xFF110000 0x100>;
interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>;
/* GIC_SPI=0, interrupt number, trigger tipi */
};
pinctrl-0 ve pinctrl-names
spi0: spi@48030000 {
compatible = "ti,omap2-mcspi";
reg = <0x48030000 0x400>;
status = "okay";
/* Pin durumları: state 0 = "default" adıyla eşlenir */
pinctrl-names = "default", "sleep";
pinctrl-0 = <&spi0_pins_default>;
pinctrl-1 = <&spi0_pins_sleep>;
/* Driver: pinctrl_select_state(p, "sleep") ile geçiş yapar */
};
dmas ve power-domains
mcasp0: mcasp@48038000 {
compatible = "ti,am33xx-mcasp-audio";
reg = <0x48038000 0x2000>;
/* DMA kanalları: phandle + request satırı */
dmas = <&edma 8>, <&edma 9>;
dma-names = "tx", "rx";
/* Güç yönetimi: PM domain otomatik enable/disable */
power-domains = <&prm_per>;
};
Bu bölümde
status = "okay": SoC DTSI'daki disabled node'u board .dts'de etkinleştirclocks+clock-names: driverclk_get(&dev, "fck")ile clock'u adresleinterrupts+interrupt-parent: IRQ numarası ve trigger tipi; GIC için 3 hücrelipinctrl-0+pinctrl-names: "default" / "sleep" pin durumları
03 CPU ve memory node'ları
/cpus hiyerarşisi ve /memory node'u — kernel'in işlemci ve RAM düzenini öğrendiği yer.
/cpus node'u
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
device_type = "cpu"; /* zorunlu */
compatible = "arm,cortex-a9";
reg = <0>; /* CPU ID */
/* Saat frekansı (sabit değer veya clock phandle) */
clock-frequency = <666000000>; /* 666 MHz */
/* CPU idle states */
enable-method = "psci";
/* L1/L2 cache referansı (isteğe bağlı) */
next-level-cache = <&L2>;
};
cpu@1 {
device_type = "cpu";
compatible = "arm,cortex-a9";
reg = <1>;
clock-frequency = <666000000>;
enable-method = "psci";
next-level-cache = <&L2>;
};
};
Timebase frequency (RISC-V)
cpus {
#address-cells = <1>;
#size-cells = <0>;
timebase-frequency = <10000000>; /* 10 MHz — RISC-V'e özgü */
cpu@0 {
device_type = "cpu";
compatible = "sifive,u74-mc", "riscv";
riscv,isa = "rv64imafdcsu";
mmu-type = "riscv,sv39";
reg = <0>;
};
};
/memory node'u
/* Tek RAM bölgesi */
memory@80000000 {
device_type = "memory"; /* zorunlu */
reg = <0x80000000 0x40000000>; /* 1 GB @ 0x80000000 */
};
/* Birden fazla RAM bölgesi (64-bit SoC) */
memory@00000000 {
device_type = "memory";
reg = <0x00000000 0x00000000 0x00000000 0x80000000>,
<0x00000008 0x00000000 0x00000000 0x80000000>;
/* address-cells=2: [addr-hi addr-lo] [size-hi size-lo] */
};
/* Bootloader aktarılan dinamik bellek bilgisi */
/* /chosen node'undan bootargs aracılığıyla da gelebilir */
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 ro";
stdout-path = "serial0:115200n8";
};
NUMA node tanımlaması
/* Çok soketli sistemlerde NUMA topology */
memory@0 {
device_type = "memory";
reg = <0x00000000 0x40000000>;
numa-node-id = <0>;
};
memory@40000000 {
device_type = "memory";
reg = <0x40000000 0x40000000>;
numa-node-id = <1>;
};
Bu bölümde
/cpus → cpu@N:device_type = "cpu", compatible, reg=CPU_ID, clock-frequencyenable-method = "psci": SMP CPU başlatma için ARM Power State Coordination Interface/memory@ADDR:device_type = "memory"zorunlu — kernel hangi belleği kullanacağını buradan öğrenir/chosen: bootargs, stdout-path — bootloader'dan kernel'e geçilen parametreler
04 Peripheral node'ları: UART, SPI, I2C
Gerçek SoC örnekleriyle UART, SPI controller + slave ve I2C controller + device tanımları.
UART node'u
uart0: serial@44E09000 {
compatible = "ti,am335x-uart", "ti,omap2-uart";
reg = <0x44E09000 0x2000>;
interrupt-parent = <&intc>;
interrupts = <72>;
clocks = <&dpll_per_m2_div4_wkupdm_ck>,
<&uart0_ick>;
clock-names = "fck", "ick";
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart0_pins_default>;
pinctrl-1 = <&uart0_pins_sleep>;
status = "disabled"; /* board .dts'de enable et */
};
SPI controller ve slave device
spi0: spi@48030000 {
compatible = "ti,omap2-mcspi";
reg = <0x48030000 0x400>;
interrupts = <65>;
clocks = <&dpll_per_m2_ck>;
clock-names = "fck";
status = "okay";
#address-cells = <1>; /* child reg = chip select */
#size-cells = <0>;
/* W25Q128 SPI NOR flash — chip select 0 */
flash@0 {
compatible = "winbond,w25q128", "jedec,spi-nor";
reg = <0>; /* CS0 */
spi-max-frequency = <50000000>; /* 50 MHz */
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "u-boot";
reg = <0x00000 0x80000>; /* 512 KB */
read-only;
};
partition@80000 {
label = "rootfs";
reg = <0x80000 0xF80000>; /* 15.5 MB */
};
};
};
};
I2C controller ve sensor
i2c0: i2c@44E0B000 {
compatible = "ti,omap4-i2c";
reg = <0x44E0B000 0x1000>;
interrupts = <70>;
clocks = <&dpll_per_m2_ck>;
clock-names = "fck";
clock-frequency = <400000>; /* 400 kHz Fast-mode */
status = "okay";
#address-cells = <1>; /* child reg = 7-bit I2C adresi */
#size-cells = <0>;
/* TMP102 sıcaklık sensörü — adres 0x48 */
tmp102@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupts = <&gpio1 17 IRQ_TYPE_LEVEL_LOW>;
};
/* EEPROM — adres 0x50 */
eeprom@50 {
compatible = "atmel,24c32";
reg = <0x50>;
pagesize = <32>;
};
};
GPIO controller ve consumer
gpio0: gpio@44E07000 {
compatible = "ti,omap4-gpio";
reg = <0x44E07000 0x1000>;
gpio-controller; /* bu node GPIO sağlayıcı */
#gpio-cells = <2>; /* [gpio-numarası, flags] */
interrupt-controller;
#interrupt-cells = <2>;
status = "okay";
};
/* GPIO LED consumer */
leds {
compatible = "gpio-leds";
heartbeat {
label = "beaglebone:green:heartbeat";
gpios = <&gpio1 21 GPIO_ACTIVE_LOW>;
linux,default-trigger = "heartbeat";
};
mmc0 {
label = "beaglebone:green:mmc0";
gpios = <&gpio1 16 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "mmc0";
};
};
Overlay pattern: &uart0 { status = "okay"; }; sözdizimi board .dts dosyasında SoC DTSI'dan gelen node'u override eder. Bu sayede SoC DTSI değiştirilmeden board-specific özelleştirme yapılabilir. Aynı mekanizma Device Tree Overlay (.dtbo) dosyalarında da kullanılır.
Bu bölümde
- UART: reg + interrupts + clocks + pinctrl — tam peripheral tanımı
- SPI controller'ın child'ları = slave device'lar — reg = chip select numarası
- I2C controller'ın child'ları = I2C device'lar — reg = 7-bit adres
gpio-controller+#gpio-cells = <2>: GPIO sağlayıcı; tüketici&gpio0 21 GPIO_ACTIVE_LOW
05 Interrupt sistemi
Interrupt controller hiyerarşisi, GIC tanımı ve iç içe interrupt controller (GPIO secondary IRQ).
Interrupt controller node
intc: interrupt-controller@10140000 {
compatible = "arm,versatile-vic";
reg = <0x10140000 0x1000>;
interrupt-controller; /* bu node IRQ controller */
#interrupt-cells = <1>; /* 1 hücre: interrupt numarası */
valid-mask = <0xFFFFFFFF>;
valid-wakeup-mask = <0x0000FFFF>;
};
GIC — ARM SoC standardı (AArch64)
gic: interrupt-controller@01C81000 {
compatible = "arm,gic-400", "arm,cortex-a15-gic";
#interrupt-cells = <3>; /* GIC: [type, irq, flags] */
interrupt-controller;
reg = <0x01C81000 0x1000>, /* GICD — distributor */
<0x01C82000 0x2000>; /* GICC — CPU interface */
interrupts = <GIC_PPI 9 0xf04>; /* GIC maintenance interrupt */
};
GIC interrupt cells formatı
/* GIC_SPI: Shared Peripheral Interrupt (harici donanım IRQ'ları) */
/* GIC_PPI: Private Peripheral Interrupt (timer, PMU gibi CPU'ya özel) */
/* Format: <GIC_SPI irq-number trigger-type> */
uart0: serial@FF010000 {
interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>;
/* GIC_SPI=0, irq=112, LEVEL_HIGH=4 */
};
timer@FF060000 {
interrupts = <GIC_PPI 13 0xf01>; /* PPI 13, all CPUs, edge */
};
/* IRQ trigger tipleri (IRQ_TYPE_*): */
/* IRQ_TYPE_EDGE_RISING = 1 */
/* IRQ_TYPE_EDGE_FALLING = 2 */
/* IRQ_TYPE_LEVEL_HIGH = 4 */
/* IRQ_TYPE_LEVEL_LOW = 8 */
İç içe interrupt controller: GPIO secondary
/* GPIO0 hem GPIO hem interrupt controller */
gpio0: gpio@44E07000 {
compatible = "ti,omap4-gpio";
reg = <0x44E07000 0x1000>;
/* Primary interrupt (GIC/INTC'ye bağlı) */
interrupt-parent = <&intc>;
interrupts = <96>;
/* Secondary interrupt controller (GPIO satırları) */
interrupt-controller;
#interrupt-cells = <2>; /* [gpio-pin, trigger-tipi] */
gpio-controller;
#gpio-cells = <2>;
};
/* Sensor: GPIO'dan gelen interrupt */
accelerometer@1c {
compatible = "nxp,mma8453";
reg = <0x1c>;
interrupt-parent = <&gpio0>; /* GPIO0 secondary IRQ */
interrupts = <7 IRQ_TYPE_EDGE_RISING>; /* pin 7 */
};
Bu bölümde
interrupt-controllerflag +#interrupt-cells: IRQ controller node'u tanımlar- GIC (Generic Interrupt Controller): 3 hücreli —
GIC_SPI irq_num IRQ_TYPE_* - GPIO iç içe IRQ: GPIO node'u hem
gpio-controllerheminterrupt-controller - Consumer'lar
interrupt-parentile hangi controller'ı kullandığını belirtir
06 Pinctrl ve GPIO
Pin multiplexing sistemi, pinctrl state tanımları, GPIO hog mekanizması ve gerçek AM335x pinctrl örneği.
Pinctrl node yapısı
am33xx_pinmux: pinmux@44E10800 {
compatible = "pinctrl-single";
reg = <0x44E10800 0x0238>;
#pinctrl-cells = <1>; /* 1 hücre: pad register offset */
pinctrl-single,bit-per-mux;
pinctrl-single,function-mask = <0x7F>;
/* UART0 pin grubu */
uart0_pins_default: uart0_pins_default {
pinctrl-single,pins = <
0x170 (PIN_INPUT_PULLUP | MUX_MODE0) /* UART0_RX */
0x174 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* UART0_TX */
>;
};
uart0_pins_sleep: uart0_pins_sleep {
pinctrl-single,pins = <
0x170 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* GPIO modunda */
0x174 (PIN_INPUT_PULLDOWN | MUX_MODE7)
>;
};
/* I2C0 pin grubu */
i2c0_pins: i2c0_pins {
pinctrl-single,pins = <
0x188 (PIN_INPUT_PULLUP | SLEWCTRL_SLOW | MUX_MODE0) /* I2C0_SDA */
0x18C (PIN_INPUT_PULLUP | SLEWCTRL_SLOW | MUX_MODE0) /* I2C0_SCL */
>;
};
};
Consumer'da pinctrl kullanımı
&uart0 {
status = "okay";
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart0_pins_default>; /* normal çalışma */
pinctrl-1 = <&uart0_pins_sleep>; /* suspend/sleep */
};
&i2c0 {
status = "okay";
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
};
GPIO hog — boot'ta otomatik GPIO set
gpio1: gpio@4804C000 {
compatible = "ti,omap4-gpio";
reg = <0x4804C000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
status = "okay";
/* GPIO hog: boot'ta bu GPIO otomatik set edilir */
/* Herhangi bir driver kullanmadan LOW/HIGH seviye */
phy-reset {
gpio-hog;
gpios = <16 GPIO_ACTIVE_LOW>;
output-low; /* reset aktif */
line-name = "ethernet-phy-reset";
};
wifi-enable {
gpio-hog;
gpios = <17 GPIO_ACTIVE_HIGH>;
output-high; /* WiFi aktif */
line-name = "wifi-enable";
};
};
gpio-line-names — okunabilir GPIO etiketleri
gpio0: gpio@44E07000 {
compatible = "ti,omap4-gpio";
reg = <0x44E07000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
status = "okay";
/* GPIO hatlarına okunabilir isim — gpiodetect / gpiomon ile görünür */
gpio-line-names =
"MDIO", /* GPIO0_0 */
"MDC", /* GPIO0_1 */
"", "", "", "", /* GPIO0_2..5 kullanılmıyor */
"UART0_RX", /* GPIO0_6 */
"UART0_TX"; /* GPIO0_7 */
/* gpioinfo → gpiochip0 0: MDIO [not used] ... */
};
Bu bölümde
- Pinctrl node: pad register offset + mux mode + pull config tanımı
- Consumer:
pinctrl-names = "default", "sleep"+pinctrl-0/1 = &grup - GPIO hog:
gpio-hog+output-high/output-low→ boot'ta driver olmadan GPIO set gpio-line-names:gpiodetectvegpiomonaraçlarında okunabilir etiketler
07 Clock tree
fixed-clock kaynaklardan PLL tanımına, clock consumer binding'lerine ve boot'ta assigned-clocks ile frekans ayarlamaya.
fixed-clock — kristal osilatör
/* 24 MHz kristal osilatör */
sys_clkin_ck: sys_clkin_ck {
compatible = "fixed-clock";
#clock-cells = <0>; /* cells=0: tek sabit frekans */
clock-frequency = <24000000>; /* 24 MHz */
clock-output-names = "sys_clkin_ck";
};
/* 32.768 kHz RTC osilatör */
clk_32768_ck: clk_32768_ck {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <32768>;
};
clk-divider ve clk-mux node'ları
/* Clock divider: giriş clock'u böl */
apb_pclk: clk@0 {
compatible = "fixed-factor-clock";
clocks = <&sys_clkin_ck>;
#clock-cells = <0>;
clock-div = <1>;
clock-mult = <1>;
};
/* Clock multiplexer: birden fazla kaynak arasında seçim */
uart_clk_mux: uart_clk_mux {
compatible = "gpio-mux-clock";
clocks = <&sys_clkin_ck>, <&dpll_per_m2_ck>;
#clock-cells = <0>;
select-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>;
};
PLL tanımı
dpll_core_ck: dpll_core_ck {
compatible = "ti,am3-dpll-core-clock";
#clock-cells = <0>;
clocks = <&sys_clkin_ck>, <&sys_clkin_ck>;
/* PLL parametreleri (M, N, M2 bölücüleri) */
ti,min-div = <1>;
ti,max-div = <128>;
ti,max-multiplier = <2047>;
ti,min-multiplier = <2>;
};
/* PLL çıkışı bölücüsü */
dpll_core_m4_ck: dpll_core_m4_ck {
compatible = "ti,divider-clock";
#clock-cells = <0>;
clocks = <&dpll_core_x2_ck>;
ti,max-div = <31>;
reg = <0x8>;
ti,index-starts-at-one;
};
assigned-clocks — boot'ta frekans ayarı
/* Boot zamanında clock frekanslarını ayarla */
/* Kernel CLK_OF_DECLARE ile bu property'leri işler */
&dpll_mpu_ck {
assigned-clocks = <&dpll_mpu_ck>;
assigned-clock-rates = <600000000>; /* 600 MHz CPU */
};
/* Peripheral clock frekansı (SPI örneği) */
&spi0 {
assigned-clocks = <&spi0_clk>;
assigned-clock-rates = <48000000>; /* 48 MHz SPI clock */
};
/* Consumer: clock'u isme göre al */
spi0: spi@48030000 {
clocks = <&dpll_per_m2_ck>, <&spi0_ick>;
clock-names = "fck", "ick";
/* Driver: devm_clk_get(dev, "fck"); clk_enable(fck); */
};
#clock-cells = <0> olan bir clock provider'dan yalnızca tek bir clock üretilir ve tüketici clocks = <&provider> şeklinde referans verir. #clock-cells = <1> durumunda ise provider birden fazla clock üretir ve tüketici clocks = <&pll CLK_UART> şeklinde hangi clock'u istediğini belirtmek zorundadır. Bu sayede tek bir PLL node'u birçok farklı çıkışı temsil edebilir.
Bu bölümde
fixed-clock: kristal osilatör,clock-frequencyile sabit frekans#clock-cells = <0>: tek çıkış;#clock-cells = <1>: çok çıkışlı (PLL)assigned-clocks+assigned-clock-rates: boot'ta frekans ayarla- Consumer:
clocks = <&pll CLK_UART>; driver:devm_clk_get(dev, "fck")
08 Overlay (DTBO)
Runtime'da mevcut DTB üzerine donanım tanımı eklemek — DTBO dosya yapısı, configfs yükleme ve BeagleBone cape sistemi.
Device Tree Overlay ne işe yarar
DTBO (Device Tree Blob Overlay), mevcut bir DTB'ye runtime'da yeni node veya property ekler — ya da mevcut node'u değiştirir. Bu sayede ana board DTB'sini değiştirmeden eklenti modülleri (cape, HAT, click board) için donanım tanımı yüklenebilir. Raspberry Pi HAT sistemi ve BeagleBone Cape Manager bu mekanizmayı kullanır.
.dtso dosya yapısı
/dts-v1/;
/plugin/; /* overlay direktifi */
#include <dt-bindings/interrupt-controller/irq.h>
/ {
compatible = "ti,beaglebone-black", "ti,beaglebone", "ti,am33xx";
/* fragment@0: I2C2 bus'a BME280 sensör ekle */
fragment@0 {
target = <&i2c2>; /* hedef node: i2c2 */
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
spi-max-frequency = <10000000>;
};
};
};
/* fragment@1: Pinctrl'ü I2C moduna al */
fragment@1 {
target = <&am33xx_pinmux>;
__overlay__ {
i2c2_overlay_pins: i2c2_overlay_pins {
pinctrl-single,pins = <
0x178 (PIN_INPUT_PULLUP | MUX_MODE3) /* SDA */
0x17C (PIN_INPUT_PULLUP | MUX_MODE3) /* SCL */
>;
};
};
};
};
dtc ile overlay derleme
# -@ flag'i: label'ları external symbol olarak saklar (overlay için zorunlu)
dtc -@ -I dts -O dtb -o i2c-sensor.dtbo i2c-sensor-overlay.dts
# Hataları görmek için:
dtc -@ -W all -I dts -O dtb \
-o i2c-sensor.dtbo i2c-sensor-overlay.dts
# Ana kernel'den overlay aware dtb derleme
make ARCH=arm dtbs
# Overlay dosyaları: arch/arm/boot/dts/overlays/*.dtbo
configfs overlay yükleme
# configfs DT overlay desteği kernel config'de aktif mi?
grep CONFIG_OF_OVERLAY /boot/config-$(uname -r)
# CONFIG_OF_OVERLAY=y
# configfs mount edilmiş mi?
mount | grep configfs
# configfs on /sys/kernel/config type configfs
# Overlay dizini oluştur ve .dtbo dosyasını yükle
mkdir /sys/kernel/config/device-tree/overlays/i2c-sensor
cat i2c-sensor.dtbo > /sys/kernel/config/device-tree/overlays/i2c-sensor/dtbo
# Yükleme başarılı mı?
cat /sys/kernel/config/device-tree/overlays/i2c-sensor/status
# applied
# I2C device görünüyor mu?
i2cdetect -y 2
# 76: UU → driver probe edildi
# Overlay kaldırma
rmdir /sys/kernel/config/device-tree/overlays/i2c-sensor
BeagleBone cape overlay sistemi
# /boot/uEnv.txt ile overlay yükleme (U-Boot)
# uEnv.txt:
dtb_overlay=/lib/firmware/BB-I2C2-BME280-00A0.dtbo
# Firmware dizinine kopyala
sudo cp i2c-sensor.dtbo /lib/firmware/BB-I2C2-BME280-00A0.dtbo
# Cape EEPROM'dan otomatik yükleme (BeagleBone özgü)
cat /proc/device-tree/chosen/overlay-names
# BB-I2C2-BME280
Bu bölümde
/plugin/;direktifi: overlay DTS dosyasını normal DTS'den ayırırfragment@N { target = <&node>; __overlay__ { ... }; }: hedef node'u genişletdtc -@: label'ları extern sembol olarak sakla — overlay için zorunlu- configfs:
/sys/kernel/config/device-tree/overlays/— runtime yükleme
09 Kernel'de DT okuma: of_ API
Device driver'larında DT property'lerini okumak için kullanılan of_* ve platform_* fonksiyonları.
Node bulma fonksiyonları
#include <linux/of.h>
#include <linux/of_device.h>
/* compatible string ile node bul */
struct device_node *node;
node = of_find_compatible_node(NULL, NULL, "ti,am335x-uart");
if (!node) {
pr_err("uart node not found\n");
return -ENODEV;
}
/* İsimle node bul (genellikle önerilmez) */
node = of_find_node_by_name(NULL, "uart0");
/* Alt node'ları döngüyle gezin */
struct device_node *child;
for_each_child_of_node(node, child) {
pr_info("child: %s\n", child->name);
}
of_node_put(node); /* kullanımdan sonra serbest bırak */
Property okuma fonksiyonları
#include <linux/of.h>
#include <linux/property.h>
static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
u32 freq;
const char *name;
u32 reg_array[4];
int ret;
/* u32 oku */
ret = of_property_read_u32(np, "clock-frequency", &freq);
if (ret)
dev_warn(dev, "no clock-frequency, using default\n");
/* string oku */
ret = of_property_read_string(np, "label", &name);
if (!ret)
dev_info(dev, "label: %s\n", name);
/* u32 dizisi oku */
ret = of_property_read_u32_array(np, "reg", reg_array,
ARRAY_SIZE(reg_array));
/* boolean property var mı? */
if (of_property_read_bool(np, "read-only"))
dev_info(dev, "device is read-only\n");
return 0;
}
of_get_named_gpio — GPIO okuma
#include <linux/of_gpio.h>
#include <linux/gpio/consumer.h>
static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct gpio_desc *reset_gpio;
int irq;
/* DT'den GPIO al (devm: driver remove'da otomatik serbest bırak) */
/* gpios = <&gpio1 5 GPIO_ACTIVE_LOW> — "reset-gpios" property */
reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(reset_gpio))
return PTR_ERR(reset_gpio);
/* GPIO'yu kullan */
gpiod_set_value(reset_gpio, 0); /* reset aktif */
udelay(100);
gpiod_set_value(reset_gpio, 1); /* reset serbest */
/* DT'den IRQ numarasını al (platform_get_irq) */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
return devm_request_irq(dev, irq, my_irq_handler,
IRQF_TRIGGER_RISING, "my-device", dev);
}
platform_get_resource — reg'den memory
#include <linux/platform_device.h>
#include <linux/io.h>
static int my_driver_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
/* 'reg' property'sinden ilk memory resource al */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
/* ioremap + devm (driver remove'da otomatik unmap) */
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
/* Kayıt oku/yaz */
u32 val = readl(base + REG_CONTROL);
writel(val | CTRL_ENABLE, base + REG_CONTROL);
return 0;
}
OF match table — compatible eşleştirmesi
/* DT ile driver eşleştirme tablosu */
static const struct of_device_id my_driver_of_match[] = {
{ .compatible = "myvendor,my-device-v1", .data = &my_dev_v1_cfg },
{ .compatible = "myvendor,my-device-v2", .data = &my_dev_v2_cfg },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);
static struct platform_driver my_driver = {
.probe = my_driver_probe,
.remove = my_driver_remove,
.driver = {
.name = "my-device",
.of_match_table = my_driver_of_match,
.pm = &my_driver_pm_ops,
},
};
Bu bölümde
of_find_compatible_node(): compatible string ile node bul; kullanımdan sonraof_node_put()of_property_read_u32(),of_property_read_string(): property değerlerini okudevm_gpiod_get(): DT'den GPIO al;platform_get_resource(): reg'den memory kaynağıof_device_idmatch table: kernel driver'ını compatible string'e göre kaydet
10 DTS derleme ve debug
dtc araçları, fdtget / fdtput, kernel içindeki /proc/device-tree/ ve dtc -W checks ile doğrulama.
dtc — device tree compiler
# DTS → DTB (derle)
dtc -I dts -O dtb -o output.dtb input.dts
# DTB → DTS (geri dönüştür / decompile)
dtc -I dtb -O dts -o decoded.dts output.dtb
# Overlay derleme (-@ zorunlu)
dtc -@ -I dts -O dtb -o my-overlay.dtbo my-overlay.dts
# Warning ve check seviyesi
dtc -W no-simple_bus_reg \ # belirli warning'i kapat
-W all \ # tüm warning'leri etkinleştir
-I dts -O dtb -o board.dtb board.dts
# Preprocessor (C header include için)
cpp -nostdinc -undef -x assembler-with-cpp \
-I /usr/include/ board.dts board-cpp.dts
dtc -I dts -O dtb -o board.dtb board-cpp.dts
Kernel içinden DTS derleme
# ARM için tüm board DTB'leri derle
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
# Belirli bir board DTB'sini derle
make ARCH=arm am335x-boneblack.dtb
# AArch64
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs
# Çıktı konumu
ls arch/arm/boot/dts/ti/omap/am335x-boneblack.dtb
# DTS doğrulaması (YAML binding check)
make ARCH=arm dtbs_check
# Tüm DTB'leri YAML binding şemalarına karşı doğrular
# Uyarılar: Documentation/devicetree/bindings/ eksik property'leri listeler
fdtdump, fdtget, fdtput
# fdtdump: DTB'nin tüm içeriğini DTS formatında yazdır
fdtdump board.dtb
fdtdump -s board.dtb # sorted output
# fdtget: belirli property değerini oku
fdtget board.dtb /memory reg
# 0x80000000 0x10000000
fdtget board.dtb /cpus/cpu@0 compatible
# arm,cortex-a9
# Tüm property'leri listele
fdtget -p board.dtb /cpus/cpu@0
# compatible
# device_type
# reg
# fdtput: property değerini değiştir (DTB üzerinde)
fdtput -t s board.dtb /chosen bootargs \
"console=ttyS0,115200 root=/dev/mmcblk0p2 ro quiet"
# Üretimde bootargs'ı binary DTB'ye gömmek için kullanılır
Kernel içindeki DT — /proc/device-tree
# Kernel tarafından okunan DT node'larını gez
ls /proc/device-tree/
# #address-cells compatible cpus memory@80000000 model ...
# Belirli property'yi oku
cat /proc/device-tree/compatible
# myvendor,myboardmyvendor,mychip (null-separated strings)
cat /proc/device-tree/model
# My Custom IoT Board
# memory node
cat /proc/device-tree/memory@80000000/reg | xxd
# 80 00 00 00 10 00 00 00 (big-endian: base=0x80000000, size=0x10000000)
# sysfs üzerinden (symlink)
ls /sys/firmware/devicetree/base/
# /proc/device-tree/ ile özdeş — ikisi birbirinin symlink'i
# Çalışan sistemin tüm DTB'sini dışa aktar ve decompile et
dtc -I fs -O dts /sys/firmware/devicetree/base > running-system.dts
dtc -W checks — doğrulama
# Tüm check'leri etkinleştir
dtc -W all -I dts -O dtb -o /dev/null board.dts
# WARNING: ...missing 'interrupt-parent' property
# WARNING: ...Node ...missing #address-cells
# Kernel binding doğrulaması
make ARCH=arm dtbs_check \
DT_SCHEMA_FILES=Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
# Belirli binding dosyasını doğrula
# dt-validate aracı (pip install dtschema)
dt-validate -s Documentation/devicetree/bindings/ board.dtb
# I2C device probe edildi mi?
dmesg | grep "i2c"
# i2c i2c-0: new_device: Instantiated device tmp102 at 0x48
# Driver probe başarılı mı?
ls /sys/bus/platform/devices/ | grep 44E09000
# 44E09000.serial
ls /sys/bus/platform/devices/44E09000.serial/driver
# ../../../bus/platform/drivers/omap-serial
dtc -I fs /sys/firmware/devicetree/base komutu, çalışan sistemin aktif DTB'sini (overlay'lar dahil) decompile ederek DTS formatına döndürür. Bu teknik özellikle aktif overlay'ların ne eklediğini veya U-Boot'un DTB üzerinde hangi değişiklikleri yaptığını anlamak için çok kullanışlıdır. U-Boot bazen bootargs ve stdout-path gibi property'leri kernel'a geçirmeden önce değiştirir.
Bu bölümde
dtc -I dts -O dtb: DTS → DTB derleme;-I dtb -O dts: ters dönüşümfdtget board.dtb /path property: binary DTB'den property okuma/proc/device-tree/ve/sys/firmware/devicetree/base/: çalışan sistemde DT debugdtc -I fs /sys/firmware/devicetree/base: aktif DTB'yi (overlay dahil) decompile et