Tüm eğitimler
TEKNİK REHBER GÖMÜLÜ LİNUX CLK / CCF 2026

CCF — Linux Common Clock Framework
Saat Ağacı, Sürücü Yazımı & DT Bağlaması

clock tree modeli, consumer/provider API'si, PLL sürücüsü tasarımı ve debugfs ile hata ayıklama — eksiksiz CCF rehberi.

00 CCF nedir — clock tree ve provider/consumer modeli

Linux Common Clock Framework (CCF), çekirdek içindeki tüm clock kaynaklarını hiyerarşik bir ağaç yapısında modelleyen ve tek bir tutarlı API ile yöneten alt sistemdir.

Embedded SoC'larda onlarca — bazen yüzlerce — clock kaynağı bulunur: kristal osilatörler, PLL'ler, divider'lar, mux'lar ve gate'ler. CCF öncesinde her mimarinin kendi clock yönetim kodu vardı; bu durum kod tekrarına ve taşıma güçlüğüne yol açıyordu. CCF (drivers/clk/) bu problemi çözmek için Linux 3.4'te birleşik bir framework sunar.

  [XO 24 MHz] ──► [PLL A 1200 MHz] ──► [DIV /4] ──► [CPU CLK 300 MHz]
                                   └──► [DIV /2] ──► [BUS CLK 600 MHz]
  [XO 24 MHz] ──► [PLL B 800 MHz]  ──► [MUX]    ──► [UART CLK]
                                         ▲
  [RTC 32 kHz] ─────────────────────────┘

  Her düğüm: struct clk_hw  (provider perspektifi)
  Tüketiciler: struct clk * (consumer handle)
    

Temel kavramlar

Clock providerBir clock kaynağı sağlayan sürücü; clk_hw_register*() ile CCF'e kaydolur
Clock consumerBir clock kaynağını kullanan sürücü; clk_get()/devm_clk_get() ile handle alır
struct clk_hwProvider tarafındaki temel yapı; sürücü bu struct'ı gömülü tutar
struct clkConsumer'a döndürülen opak handle; clk_ API ile kullanılır
struct clk_coreCCF'in iç temsili; kullanım sayısını, parent'ı ve flags'leri tutar
clk_opsProvider'ın uyguladığı callback kümesi: enable, disable, set_rate vb.

Clock hiyerarşisi ve referans sayımı

CCF'de her clock'un bir ya da birden fazla parent'ı olabilir (mux clock). clk_prepare_enable() çağrıldığında CCF kök'e kadar tüm parent'ları otomatik etkinleştirir. clk_disable_unprepare() ise referans sayacını düşürür; sıfıra inen clock ve etkilenen parent'lar devre dışı bırakılır. Bu mekanizma güç tasarrufu için kritiktir.

Clock türüKernel helperTanımı
fixed-rateclk_hw_register_fixed_rate()Sabit frekanslı kristal/osilatör
fixed-factorclk_hw_register_fixed_factor()Parent'ın sabit çarpan/böleni
dividerclk_hw_register_divider()Programlanabilir bölen (register'dan)
muxclk_hw_register_mux()Birden fazla parent arasında seçim
gateclk_hw_register_gate()Enable/disable için tek bit kontrolü
compositeclk_hw_register_composite()mux + divider + gate birleşimi
pllÖzel clk_ops (elle yazılır)Tam programlanabilir PLL
ÖNEMLİ

CCF başlık dosyaları: <linux/clk.h> (consumer API), <linux/clk-provider.h> (provider API). Consumer sürücüler yalnızca clk.h'ı include etmeli; provider implementasyonu gereksiz yere consumer koduna sızmamalıdır.

Bu bölümde

  • CCF: tüm clock kaynaklarını tek hiyerarşik ağaçta modelleyen birleşik framework
  • Provider (clk_hw) ve consumer (clk *) ayrımı — iki farklı include dosyası
  • clk_prepare_enable → parent'ları otomatik etkinleştirme zinciri
  • Yedi temel clock türü: fixed-rate, fixed-factor, divider, mux, gate, composite, PLL

01 Consumer API — clk_get, clk_prepare, clk_enable, clk_set_rate

Consumer sürücüler clock ağacından nasıl handle alır, enable/disable zinciri nasıl çalışır ve frekans ayarı nasıl yapılır?

Handle alma

consumer_driver.c — handle alma
// Yöntem 1: devm_clk_get — probe() içinde kullanın, otomatik cleanup
struct clk *uart_clk = devm_clk_get(&pdev->dev, "uart");
if (IS_ERR(uart_clk))
    return PTR_ERR(uart_clk);

// Yöntem 2: clk_get — manuel release gerektirir
struct clk *bus_clk = clk_get(&pdev->dev, "bus");
if (IS_ERR(bus_clk)) {
    dev_err(&pdev->dev, "bus clock alinamadi: %ld\n", PTR_ERR(bus_clk));
    return PTR_ERR(bus_clk);
}

// Yöntem 3: of_clk_get_by_name — DT node'undan isimle al
struct clk *ref = of_clk_get_by_name(np, "ref");

// NULL-safe helper — clock isteğe bağlıysa
struct clk *opt = devm_clk_get_optional(&pdev->dev, "optional-clk");
// PTR_ERR değil NULL döner — clock yoksa hata değildir

prepare / enable iki aşaması

CCF, iki aşamalı bir enable mekanizması kullanır. prepare aşaması uyku bağlamında (sleepable) çalışır ve PLL lock bekleme gibi uzun işlemleri kapsar. enable aşaması atomik bağlamda çalışır ve donanımda clock'u açar.

consumer_driver.c — prepare/enable
// Aşama 1: prepare (may sleep — uyku bağlamında çağrılabilir)
int ret = clk_prepare(uart_clk);
if (ret)
    return ret;

// Aşama 2: enable (atomic — interrupt handler'da da çağrılabilir)
ret = clk_enable(uart_clk);
if (ret) {
    clk_unprepare(uart_clk);
    return ret;
}

// Birleşik form — en yaygın kullanım (may sleep)
ret = clk_prepare_enable(uart_clk);
if (ret)
    return ret;

/* ... sürücü çalışıyor ... */

// Kapatma — her zaman enable'ın tersi sırayla
clk_disable_unprepare(uart_clk);

// Sadece disable (atomic), unprepare ayrıca
clk_disable(uart_clk);
clk_unprepare(uart_clk);

Frekans sorgulama ve ayarlama

consumer_driver.c — rate API
// Mevcut frekansı oku (Hz cinsinden)
unsigned long rate = clk_get_rate(uart_clk);
dev_info(&pdev->dev, "UART clock: %lu Hz\n", rate);

// Donanımın desteklediği en yakın frekansı sor
long actual = clk_round_rate(uart_clk, 115200UL * 16);
dev_info(&pdev->dev, "En yakin rate: %ld Hz\n", actual);

// Frekansı ayarla (may sleep — PLL relock sürebilir)
int ret = clk_set_rate(uart_clk, 1843200UL);  // 1.8432 MHz
if (ret)
    dev_warn(&pdev->dev, "rate ayarlanamadi: %d\n", ret);

// Parent ayarla (mux clock için)
struct clk *new_parent = devm_clk_get(&pdev->dev, "pll_b");
ret = clk_set_parent(uart_clk, new_parent);

Tam consumer örneği — probe içinde

my_uart.c — probe() clock yönetimi
struct my_uart_priv {
    void __iomem  *base;
    struct clk    *clk;
    unsigned long  clk_rate;
};

static int my_uart_probe(struct platform_device *pdev)
{
    struct my_uart_priv *priv;
    int ret;

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

    priv->base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);

    priv->clk = devm_clk_get(&pdev->dev, "uart");
    if (IS_ERR(priv->clk))
        return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk),
                             "uart clock alinamadi\n");

    ret = clk_prepare_enable(priv->clk);
    if (ret)
        return dev_err_probe(&pdev->dev, ret,
                             "clock enable edilemedi\n");

    // devm ile otomatik cleanup için notifier kaydedelim
    ret = devm_add_action_or_reset(&pdev->dev,
        (void(*)(void*))clk_disable_unprepare, priv->clk);
    if (ret)
        return ret;

    priv->clk_rate = clk_get_rate(priv->clk);
    platform_set_drvdata(pdev, priv);
    return 0;
}
clk_get() / devm_clk_get()Device node'undan isimle clock handle alır; -EPROBE_DEFER deferral'ı destekler
clk_prepare()Uyku bağlamı; PLL lock, VCO ayarlama gibi uzun işlemler burada yapılır
clk_enable()Atomik bağlam; donanımda gate bitini açar
clk_set_rate()Frekans ayarı; CLK_SET_RATE_GATE flag varsa önce disable gerektirir
clk_round_rate()Desteklenen en yakın frekansı sorgular, ayarlama yapmaz

Bu bölümde

  • devm_clk_get() → IS_ERR() kontrolü → -EPROBE_DEFER akışı
  • prepare (may sleep) + enable (atomic) iki aşaması; clk_prepare_enable() kısayolu
  • clk_get_rate / clk_round_rate / clk_set_rate — frekans yönetimi
  • devm_add_action_or_reset ile otomatik clk_disable_unprepare cleanup

02 struct clk_ops — driver tarafı callback'leri

Clock provider sürücüsünün CCF'e sunduğu callback fonksiyonları kümesi olan clk_ops, clock'un tüm davranışını tanımlar.

struct clk_ops (include/linux/clk-provider.h), bir clock'un nasıl enable/disable edileceğini, frekansının nasıl hesaplanacağını ve ayarlanacağını tanımlayan function pointer dizisidir. Sürücü bu struct'ı doldurur; CCF consumer isteklerini bu callback'lere yönlendirir.

clk_ops yapısı — önemli alanlar

include/linux/clk-provider.h — clk_ops (sadeleştirilmiş)
struct clk_ops {
    /* Hazırlık — may sleep */
    int   (*prepare)(struct clk_hw *hw);
    void  (*unprepare)(struct clk_hw *hw);
    int   (*is_prepared)(struct clk_hw *hw);

    /* Enable/disable — atomic */
    int   (*enable)(struct clk_hw *hw);
    void  (*disable)(struct clk_hw *hw);
    int   (*is_enabled)(struct clk_hw *hw);

    /* Frekans hesaplama */
    unsigned long  (*recalc_rate)(struct clk_hw *hw,
                                   unsigned long parent_rate);
    long           (*round_rate)(struct clk_hw *hw,
                                  unsigned long rate,
                                  unsigned long *parent_rate);
    int            (*determine_rate)(struct clk_hw *hw,
                                      struct clk_rate_request *req);
    int            (*set_rate)(struct clk_hw *hw,
                               unsigned long rate,
                               unsigned long parent_rate);

    /* Parent yönetimi */
    u8   (*get_parent)(struct clk_hw *hw);
    int  (*set_parent)(struct clk_hw *hw, u8 index);

    /* Faz ve başlangıç değeri */
    int   (*get_phase)(struct clk_hw *hw);
    int   (*set_phase)(struct clk_hw *hw, int degrees);
    void  (*init)(struct clk_hw *hw);
    void  (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};

recalc_rate — mevcut frekansın hesaplanması

my_div_clk.c — recalc_rate
static unsigned long my_div_recalc_rate(struct clk_hw *hw,
                                         unsigned long parent_rate)
{
    struct my_div_clk *div = to_my_div_clk(hw);
    u32 reg_val = readl(div->base + DIV_REG);
    u32 divisor = FIELD_GET(DIV_RATIO_MASK, reg_val) + 1;

    /* parent_rate / divisor — CCF bölme işlemini doğru yapar */
    return DIV_ROUND_UP_ULL((u64)parent_rate, divisor);
}

round_rate — istenen frekansa en yakın değer

my_div_clk.c — round_rate
static long my_div_round_rate(struct clk_hw *hw,
                               unsigned long rate,
                               unsigned long *parent_rate)
{
    struct my_div_clk *div = to_my_div_clk(hw);
    unsigned long best_rate = 0;
    u32 d;

    /* Mümkün tüm bölücüleri tara, en yakını bul */
    for (d = 1; d <= div->max_div; d++) {
        unsigned long r = DIV_ROUND_UP_ULL((u64)*parent_rate, d);
        if (r <= rate && r > best_rate)
            best_rate = r;
    }
    return best_rate ? best_rate : DIV_ROUND_UP_ULL((u64)*parent_rate,
                                                      div->max_div);
}

set_rate — donanımda frekansı uygula

my_div_clk.c — set_rate
static int my_div_set_rate(struct clk_hw *hw,
                            unsigned long rate,
                            unsigned long parent_rate)
{
    struct my_div_clk *div = to_my_div_clk(hw);
    u32 divisor, reg_val;

    if (!rate)
        return -EINVAL;

    divisor = DIV_ROUND_UP(parent_rate, rate);
    if (divisor > div->max_div)
        divisor = div->max_div;

    reg_val = readl(div->base + DIV_REG);
    reg_val = (reg_val & ~DIV_RATIO_MASK) |
              FIELD_PREP(DIV_RATIO_MASK, divisor - 1);
    writel(reg_val, div->base + DIV_REG);
    return 0;
}
recalc_rateDonanımdan mevcut frekansı oku ve döndür; boot sırasında da çağrılır
round_rateİstenen frekansa en yakın desteklenen değeri döndür; parent_rate değiştirilebilir
determine_rateround_rate'in gelişmiş versiyonu; birden fazla parent'ı değerlendirmek için
set_rateDonanıma rate'i uygula; round_rate sonucuyla çağrılır
enable / disableGate bitini aç/kapat; spinlock içinde çalışmalı (atomic)

Bu bölümde

  • clk_ops: sürücünün CCF'e sunduğu function pointer tablosu
  • recalc_rate: donanım register'larından mevcut bölücüyü okuyarak frekans hesaplar
  • round_rate: istenen frekansa en yakın donanım değerini arar
  • set_rate: donanıma divisor yazar; round_rate sonucuyla çağrılır
  • to_container_of pattern ile clk_hw'den özel struct'a ulaşım

03 clk_register ve clk_hw_register — clock provider kayıt

Clock provider sürücüsünün CCF'e clk_hw_register*() ile nasıl kaydolacağı, init_data yapısı ve provider tablo mekanizması.

Temel kayıt API'si

provider_driver.c — clk_hw_register
#include <linux/clk-provider.h>

/* Özel clock struct'ımız */
struct my_clk {
    struct clk_hw    hw;         /* İLK alan — to_my_clk() için */
    void  __iomem   *reg_base;
    spinlock_t       lock;
};

/* container_of helper */
#define to_my_clk(hw) container_of(hw, struct my_clk, hw)

static const struct clk_ops my_clk_ops = {
    .recalc_rate  = my_clk_recalc_rate,
    .round_rate   = my_clk_round_rate,
    .set_rate     = my_clk_set_rate,
    .enable       = my_clk_enable,
    .disable      = my_clk_disable,
    .is_enabled   = my_clk_is_enabled,
};

static int my_clk_provider_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct my_clk  *mclk;
    struct clk_init_data init = {};
    const char *parent_names[] = { "xo_24mhz" };
    int ret;

    mclk = devm_kzalloc(dev, sizeof(*mclk), GFP_KERNEL);
    if (!mclk)
        return -ENOMEM;

    mclk->reg_base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(mclk->reg_base))
        return PTR_ERR(mclk->reg_base);

    spin_lock_init(&mclk->lock);

    /* clk_init_data: clock'un meta verisi */
    init.name         = "my_uart_clk";
    init.ops          = &my_clk_ops;
    init.parent_names = parent_names;
    init.num_parents  = ARRAY_SIZE(parent_names);
    init.flags        = CLK_SET_RATE_PARENT;

    mclk->hw.init = &init;

    /* Kaydol — devm_ versiyonu otomatik unregister yapar */
    ret = devm_clk_hw_register(dev, &mclk->hw);
    if (ret)
        return dev_err_probe(dev, ret, "clk kaydi basarisiz\n");

    /* DT consumer'lara provider tablosunu sun */
    return devm_of_clk_add_hw_provider(dev,
                                        of_clk_hw_simple_get,
                                        &mclk->hw);
}

Çoklu clock sağlayan provider

multi_clk_provider.c — clk_hw_onecell_data
/* Birden fazla clock sunan SoC CMU (Clock Management Unit) */
#define MY_CLK_UART0    0
#define MY_CLK_SPI0     1
#define MY_CLK_I2C0     2
#define MY_CLK_COUNT    3

static int my_cmu_probe(struct platform_device *pdev)
{
    struct clk_hw_onecell_data *data;
    int ret;

    data = devm_kzalloc(&pdev->dev,
                struct_size(data, hws, MY_CLK_COUNT),
                GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    data->num = MY_CLK_COUNT;

    /* Her clock'u kaydet */
    data->hws[MY_CLK_UART0] = my_register_uart_clk(&pdev->dev);
    data->hws[MY_CLK_SPI0]  = my_register_spi_clk(&pdev->dev);
    data->hws[MY_CLK_I2C0]  = my_register_i2c_clk(&pdev->dev);

    /* Herhangi biri başarısız mı? */
    for (int i = 0; i < MY_CLK_COUNT; i++) {
        if (IS_ERR(data->hws[i]))
            return PTR_ERR(data->hws[i]);
    }

    /* Provider tablosunu DT'ye bağla */
    return devm_of_clk_add_hw_provider(&pdev->dev,
                                        of_clk_hw_onecell_get,
                                        data);
}

clk_init_data flags tablosu

FlagTanımı
CLK_SET_RATE_PARENTset_rate isteği parent'a da iletilir
CLK_IGNORE_UNUSEDConsumer yoksa bile clock disable edilmez
CLK_IS_CRITICALSistem kritik — hiçbir zaman disable edilmez
CLK_GET_RATE_NOCACHEHer sorguda donanımdan okunur, önbellek kullanılmaz
CLK_SET_RATE_GATEset_rate öncesi clock disable edilmeli
CLK_SET_PARENT_GATEset_parent öncesi clock disable edilmeli
CLK_DUTY_CYCLE_PARENTduty cycle bilgisini parent'tan al

Bu bölümde

  • clk_hw: sürücünün gömülü tuttuğu temel struct; container_of ile erişim
  • clk_init_data: isim, ops, parent_names ve flags — boot sırasında kullanılır
  • devm_clk_hw_register() + devm_of_clk_add_hw_provider() — tam devm akışı
  • Çoklu clock için clk_hw_onecell_data + of_clk_hw_onecell_get

04 Hazır clock türleri — fixed-rate, divider, mux, gate

CCF'in sunduğu hazır clock helper'ları çoğu donanım için yeterlidir; özel ops yazmaktan kaçınmak için bunları kullanın.

fixed-rate clock

fixed_rate_example.c
/* 24 MHz kristal — sabit, değiştirilemez */
struct clk_hw *xo_hw;
xo_hw = clk_hw_register_fixed_rate(dev, "xo_24mhz",
                                    NULL,     /* parent yok */
                                    0,        /* flags */
                                    24000000UL); /* 24 MHz */
if (IS_ERR(xo_hw))
    return PTR_ERR(xo_hw);

fixed-factor clock

fixed_factor_example.c
/* xo_24mhz * 50 / 1 = 1200 MHz PLL çıkışı simülasyonu */
struct clk_hw *pll_hw;
pll_hw = clk_hw_register_fixed_factor(dev, "pll_fixed",
                                       "xo_24mhz",
                                       0,    /* flags */
                                       50,   /* mult */
                                       1);   /* div */

divider clock

divider_example.c
/*
 * Register-kontrollü bölücü:
 *   reg + offset adresindeki bitler[7:4] bölücüyü taşır
 *   CLK_DIVIDER_ONE_BASED: register değeri doğrudan bölücüdür (0 geçersiz)
 */
struct clk_hw *div_hw;
div_hw = clk_hw_register_divider(dev, "uart_div",
                                  "pll_fixed",      /* parent */
                                  CLK_SET_RATE_PARENT,
                                  reg_base + 0x10,  /* register adresi */
                                  4,                 /* bit offset */
                                  4,                 /* bit width */
                                  CLK_DIVIDER_ONE_BASED,
                                  &my_lock);          /* spinlock */

/* Tablo-tabanlı divider (log2 değerleri için) */
static const struct clk_div_table uart_div_table[] = {
    { .val = 0, .div = 1 },
    { .val = 1, .div = 2 },
    { .val = 2, .div = 4 },
    { .val = 3, .div = 8 },
    { /* sentinel */ }
};
div_hw = clk_hw_register_divider_table(dev, "uart_div2",
                                        "pll_fixed", 0,
                                        reg_base + 0x14, 0, 2,
                                        0, uart_div_table, &my_lock);

mux clock

mux_example.c
static const char * const uart_mux_parents[] = {
    "xo_24mhz",
    "pll_fixed",
    "uart_div",
};

struct clk_hw *mux_hw;
mux_hw = clk_hw_register_mux(dev, "uart_mux",
                               uart_mux_parents,
                               ARRAY_SIZE(uart_mux_parents),
                               CLK_SET_RATE_PARENT,
                               reg_base + 0x20,  /* register */
                               8,                 /* bit offset */
                               2,                 /* bit width — 4 seçenek */
                               0,                 /* flags */
                               &my_lock);

gate clock

gate_example.c
/* Tek bit enable/disable gate */
struct clk_hw *gate_hw;
gate_hw = clk_hw_register_gate(dev, "uart0_gate",
                                "uart_mux",        /* parent */
                                CLK_SET_RATE_PARENT,
                                reg_base + 0x30,   /* gate register */
                                3,                  /* bit 3 */
                                0,                  /* flags (0: active high) */
                                &my_lock);

/* CLK_GATE_SET_TO_DISABLE flag: 1 yazınca disable olur (ters mantık) */
gate_hw = clk_hw_register_gate(dev, "uart0_gate_inv",
                                "uart_mux", 0,
                                reg_base + 0x34, 0,
                                CLK_GATE_SET_TO_DISABLE,
                                &my_lock);

composite clock — mux + divider + gate birleşimi

composite_example.c
/* Composite: tek register üzerinde mux + divider + gate */
struct clk_hw *comp_hw;
comp_hw = clk_hw_register_composite(dev, "uart_comp",
    uart_mux_parents, ARRAY_SIZE(uart_mux_parents),
    mux_hw,   &clk_mux_ops,       /* mux */
    NULL,     NULL,                /* rate (NULL = parent pasla) */
    gate_hw,  &clk_gate_ops,      /* gate */
    CLK_SET_RATE_PARENT, &my_lock);

Bu bölümde

  • clk_hw_register_fixed_rate: sabit kaynak — parent yok, ops yok
  • clk_hw_register_divider: register tabanlı veya lookup tablo bölücüsü
  • clk_hw_register_mux: birden fazla parent arasında register bit seçimi
  • clk_hw_register_gate: tek bit enable/disable; CLK_GATE_SET_TO_DISABLE ters mantık
  • clk_hw_register_composite: tek çağrıyla mux+divider+gate zincirleme

05 Device Tree clock binding — clock-names, clocks özelliği

DT binding standardı: clock provider'ın #clock-cells değeri ve consumer'ın clocks/clock-names çifti.

Provider tarafı DT

my-soc.dtsi — clock provider node
/ {
    cmu: clock-controller@10400000 {
        compatible = "myvendor,mysoc-cmu";
        reg = <0x10400000 0x1000>;
        clocks = <&xo_clk>;
        clock-names = "ref";
        #clock-cells = <1>;   /* 1: indeks ile erişim */
    };

    /* Tekli clock: #clock-cells = 0 */
    uart0_clk: clock@10500000 {
        compatible = "myvendor,mysoc-clk";
        reg = <0x10500000 0x10>;
        clocks = <&cmu 2>;   /* CMU'nun indeks 2 clock'u */
        clock-names = "parent";
        #clock-cells = <0>;
    };

    /* Sabit kristal — fixed-clock generic binding */
    xo_clk: oscillator {
        compatible = "fixed-clock";
        #clock-cells = <0>;
        clock-frequency = <24000000>;
        clock-output-names = "xo_24mhz";
    };
};

Consumer tarafı DT

my-board.dts — consumer node
&uart0 {
    status = "okay";

    /*
     * clocks: phandle listesi — <&provider index>
     * clock-names: sürücünün devm_clk_get() ile kullandığı isimler
     *   sıra birebir eşleşmeli
     */
    clocks = <&cmu 0   /* uart_clk */
              &cmu 15>; /* apb_clk  */
    clock-names = "uart", "apb";
};

&spi0 {
    status = "okay";
    clocks = <&cmu 5>;
    clock-names = "spi";
};

&i2c0 {
    status = "okay";
    /* Tekil clock: indeks yok */
    clocks = <&uart0_clk>;
    clock-names = "i2c";
};

Kernel sürücüsünde DT binding okuma

uart_driver.c — clock DT okuma
static int my_uart_probe(struct platform_device *pdev)
{
    struct clk *uart_clk, *apb_clk;
    int ret;

    /* clock-names = "uart" eşleşmesi */
    uart_clk = devm_clk_get(&pdev->dev, "uart");
    if (IS_ERR(uart_clk))
        return dev_err_probe(&pdev->dev, PTR_ERR(uart_clk),
                             "uart clock alinamadi\n");

    /* clock-names = "apb" eşleşmesi */
    apb_clk = devm_clk_get(&pdev->dev, "apb");
    if (IS_ERR(apb_clk))
        return dev_err_probe(&pdev->dev, PTR_ERR(apb_clk),
                             "apb clock alinamadi\n");

    /* Bulk enable — birden fazla clock için daha temiz */
    static const struct clk_bulk_data clks[] = {
        { .id = "uart" },
        { .id = "apb"  },
    };
    ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(clks),
                             (struct clk_bulk_data *)clks);
    if (ret)
        return ret;

    ret = clk_bulk_prepare_enable(ARRAY_SIZE(clks), clks);
    if (ret)
        return ret;

    return 0;
}
#clock-cells = <0>Tekli clock sağlayan node; consumer sadece phandle yazar: clocks = <&xo_clk>
#clock-cells = <1>Çoklu clock; consumer phandle + indeks yazar: clocks = <&cmu 5>
clock-namesclocks listesindeki her girişin sürücü tarafından kullanılan ismi
clock-output-namesProvider'ın sunduğu clock isimlerinin listesi (eski binding)
assigned-clocks / assigned-clock-ratesDT'de frekansı önceden ayarlama — probe'dan önce uygulanır

Bu bölümde

  • #clock-cells = 0: tekil, 1: indeksli çoklu clock provider
  • clocks + clock-names çifti; sıra eşleşmeli, devm_clk_get("isim") ile alınır
  • fixed-clock: generic DT binding, sürücü gerekmez
  • devm_clk_bulk_get + clk_bulk_prepare_enable — çok clock için temiz API
  • assigned-clocks/rates: DT'de önceden frekans atama

06 PLL sürücüsü yazımı — ARM SoC örneği

Gerçek bir ARM SoC PLL'inin CCF sürücüsü: VCO hesaplama, lock bekleme ve power-down/up dizisi.

PLL'ler CCF'in hazır helper'larıyla modellenemez; tam özel clk_ops gerektirir. Tipik bir integer-N PLL formülü: F_out = F_ref * N / (R * OD) — burada N feedback bölücüsü, R referans bölücüsü, OD çıkış bölücüsüdür.

mysoc_pll.c — tam PLL sürücüsü
// SPDX-License-Identifier: GPL-2.0
// MySOC PLL Clock Driver
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>

/* PLL register offset'leri */
#define PLL_CON0       0x00
#define PLL_CON1       0x04
#define PLL_CON2       0x08

/* CON0 bit alanları */
#define PLL_N_MASK     GENMASK(15, 8)    /* feedback N */
#define PLL_R_MASK     GENMASK(23, 16)   /* reference R */
#define PLL_OD_MASK    GENMASK(3,  0)    /* output divider */
#define PLL_ENABLE     BIT(31)
#define PLL_POWERDOWN  BIT(30)

/* CON1 */
#define PLL_LOCK       BIT(0)           /* lock status read-only */

#define PLL_LOCK_TIMEOUT_US  1000

struct mysoc_pll {
    struct  clk_hw   hw;
    void   __iomem  *base;
    struct  spinlock_t lock;
};

#define to_mysoc_pll(hw) container_of(hw, struct mysoc_pll, hw)

/* Frekans hesaplama yardımcısı: F_out = F_ref * N / (R * OD) */
static unsigned long mysoc_pll_calc_rate(unsigned long parent_rate,
                                          u32 n, u32 r, u32 od)
{
    u64 rate = (u64)parent_rate * n;
    do_div(rate, r * od);
    return (unsigned long)rate;
}

static unsigned long mysoc_pll_recalc_rate(struct clk_hw *hw,
                                             unsigned long parent_rate)
{
    struct mysoc_pll *pll = to_mysoc_pll(hw);
    u32 con0 = readl(pll->base + PLL_CON0);
    u32 n  = FIELD_GET(PLL_N_MASK,  con0);
    u32 r  = FIELD_GET(PLL_R_MASK,  con0);
    u32 od = FIELD_GET(PLL_OD_MASK, con0);

    if (!n || !r || !od)
        return 0;

    return mysoc_pll_calc_rate(parent_rate, n, r, od);
}

static long mysoc_pll_round_rate(struct clk_hw *hw,
                                   unsigned long rate,
                                   unsigned long *prate)
{
    /* Basitleştirilmiş: N'i ayarla, R=1, OD=1 sabitle */
    u64 n = DIV_ROUND_CLOSEST_ULL((u64)rate, *prate);

    n = clamp_val(n, 1, 255);
    return (long)(*prate * n);
}

static int mysoc_pll_set_rate(struct clk_hw *hw,
                               unsigned long rate,
                               unsigned long parent_rate)
{
    struct mysoc_pll *pll = to_mysoc_pll(hw);
    unsigned long    flags;
    u32  con0, n;
    int  timeout = PLL_LOCK_TIMEOUT_US;

    n = DIV_ROUND_CLOSEST(rate, parent_rate);
    n = clamp_val(n, 1, 255);

    spin_lock_irqsave(&pll->lock, flags);

    /* 1. Power-down */
    con0 = readl(pll->base + PLL_CON0);
    con0 |= PLL_POWERDOWN;
    writel(con0, pll->base + PLL_CON0);
    udelay(5);

    /* 2. N değerini yaz */
    con0 = (con0 & ~PLL_N_MASK) | FIELD_PREP(PLL_N_MASK, n);
    /* R=1, OD=1 sabitle */
    con0 = (con0 & ~PLL_R_MASK)  | FIELD_PREP(PLL_R_MASK,  1);
    con0 = (con0 & ~PLL_OD_MASK) | FIELD_PREP(PLL_OD_MASK, 1);
    writel(con0, pll->base + PLL_CON0);

    /* 3. Power-up */
    con0 &= ~PLL_POWERDOWN;
    writel(con0, pll->base + PLL_CON0);

    spin_unlock_irqrestore(&pll->lock, flags);

    /* 4. Lock bekleme — spin_lock dışında (may sleep) */
    while (!(readl(pll->base + PLL_CON1) & PLL_LOCK)) {
        if (--timeout <= 0)
            return -ETIMEDOUT;
        udelay(1);
    }
    return 0;
}

static int mysoc_pll_prepare(struct clk_hw *hw)
{
    struct mysoc_pll *pll = to_mysoc_pll(hw);
    u32 con0;
    int timeout = PLL_LOCK_TIMEOUT_US;

    con0 = readl(pll->base + PLL_CON0);
    con0 |= PLL_ENABLE;
    con0 &= ~PLL_POWERDOWN;
    writel(con0, pll->base + PLL_CON0);

    while (!(readl(pll->base + PLL_CON1) & PLL_LOCK)) {
        if (--timeout <= 0)
            return -ETIMEDOUT;
        udelay(1);
    }
    return 0;
}

static void mysoc_pll_unprepare(struct clk_hw *hw)
{
    struct mysoc_pll *pll = to_mysoc_pll(hw);
    u32 con0 = readl(pll->base + PLL_CON0);
    con0 &= ~PLL_ENABLE;
    con0 |= PLL_POWERDOWN;
    writel(con0, pll->base + PLL_CON0);
}

static int mysoc_pll_is_prepared(struct clk_hw *hw)
{
    struct mysoc_pll *pll = to_mysoc_pll(hw);
    return !!(readl(pll->base + PLL_CON0) & PLL_ENABLE);
}

static const struct clk_ops mysoc_pll_ops = {
    .prepare      = mysoc_pll_prepare,
    .unprepare    = mysoc_pll_unprepare,
    .is_prepared  = mysoc_pll_is_prepared,
    .recalc_rate  = mysoc_pll_recalc_rate,
    .round_rate   = mysoc_pll_round_rate,
    .set_rate     = mysoc_pll_set_rate,
};

Bu bölümde

  • PLL formülü: F_out = F_ref × N / (R × OD) — do_div ile 64-bit bölme
  • prepare/unprepare: PLL enable + lock bekleme (may sleep bağlamı)
  • set_rate: power-down → N yaz → power-up → lock bekleme dizisi
  • spin_lock_irqsave ile register R-M-W koruması
  • clamp_val ile N'i geçerli aralığa sınırlama

07 clk_notifier — frekans değişikliği bildirimleri

Consumer sürücüler, clock frekansı değişmeden önce ve sonra bildirim almak için clk_notifier_register() kullanır.

Bazı sürücülerin clock frekansı değiştiğinde baud rate, DMA burst size veya timeout değerlerini güncellemesi gerekir. CCF'in notifier mekanizması bu ihtiyacı karşılar: consumer, clk_notifier_register() ile bir callback kaydeder; CCF frekans değişikliği öncesinde (PRE_RATE_CHANGE) ve sonrasında (POST_RATE_CHANGE) bu callback'i çağırır.

uart_notifier.c — clk_notifier kullanımı
#include <linux/clk.h>
#include <linux/notifier.h>

struct my_uart_priv {
    void            __iomem *base;
    struct clk             *clk;
    struct notifier_block   clk_nb;
    unsigned int            baud;
};

static int my_uart_clk_notify(struct notifier_block *nb,
                               unsigned long         action,
                               void                 *data)
{
    struct clk_notifier_data *cnd = data;
    struct my_uart_priv *priv =
        container_of(nb, struct my_uart_priv, clk_nb);

    switch (action) {
    case PRE_RATE_CHANGE:
        /*
         * Frekans değişmeden önce TX/RX'i durdur.
         * Yeni frekans: cnd->new_rate
         * Eski frekans: cnd->old_rate
         */
        dev_dbg(NULL, "CLK: %lu -> %lu Hz, UART durdurluyor\n",
                cnd->old_rate, cnd->new_rate);
        my_uart_stop_tx(priv);
        break;

    case POST_RATE_CHANGE:
        /* Yeni frekansa göre baud divisor'ü güncelle */
        {
            u32 div = DIV_ROUND_CLOSEST(cnd->new_rate,
                                         priv->baud * 16);
            writel(div, priv->base + UART_BRD_REG);
            my_uart_start_tx(priv);
        }
        break;

    case ABORT_RATE_CHANGE:
        /* set_rate başarısız — eski frekansı geri yükle */
        {
            u32 div = DIV_ROUND_CLOSEST(cnd->old_rate,
                                         priv->baud * 16);
            writel(div, priv->base + UART_BRD_REG);
            my_uart_start_tx(priv);
        }
        break;
    }
    return NOTIFY_OK;
}

static int my_uart_probe(struct platform_device *pdev)
{
    struct my_uart_priv *priv = /* ... alloc ... */
    priv->clk_nb.notifier_call = my_uart_clk_notify;

    int ret = clk_notifier_register(priv->clk, &priv->clk_nb);
    if (ret)
        return ret;

    /* devm cleanup için — remove'da çağrılır */
    return devm_add_action_or_reset(&pdev->dev,
        my_uart_unregister_notifier, priv);
}

static void my_uart_unregister_notifier(void *data)
{
    struct my_uart_priv *priv = data;
    clk_notifier_unregister(priv->clk, &priv->clk_nb);
}

Notifier aksiyonları

AksiyonTetiklenme zamanıDönüş değeri
PRE_RATE_CHANGEset_rate çağrısından önce; bloke edebilirNOTIFY_BAD → değişimi iptal et
POST_RATE_CHANGEset_rate başarıyla tamamlandıktan sonraNOTIFY_OK
ABORT_RATE_CHANGEset_rate başarısız olduğunda rollback içinNOTIFY_OK
DİKKAT

PRE_RATE_CHANGE callback'inden NOTIFY_BAD döndürmek frekans değişimini iptal eder. Bu mekanizma, örneğin yeni frekansın desteklenen minimum altında olduğu durumlarda kullanılabilir. Gereksiz NOTIFY_BAD dönüşü sistem performansını ciddi ölçüde kısıtlayabilir.

Bu bölümde

  • clk_notifier_register(clk, nb) — clock başına ayrı notifier zinciri
  • PRE: değişim öncesi durdurma; POST: yeni rate ile yeniden yapılandırma
  • ABORT: set_rate başarısız olduğunda eski değerleri geri yükleme
  • PRE'den NOTIFY_BAD → değişimi veto etme mekanizması

08 Hata ayıklama — debugfs clk/, clk_summary, clk_dump

/sys/kernel/debug/clk/ altındaki debugfs arayüzü, çalışan sistemde tüm clock ağacını gözlemlemenin birincil yoludur.

debugfs clock dosyaları

bash — debugfs clk/ keşfi
# debugfs mount (genellikle otomatik mount edilir)
mount -t debugfs none /sys/kernel/debug

# Tüm clock'ları listele
ls /sys/kernel/debug/clk/

# Belirli bir clock'un frekansını oku
cat /sys/kernel/debug/clk/uart0_gate/clk_rate
# Çıktı: 1843200

# Enable durumu
cat /sys/kernel/debug/clk/uart0_gate/clk_enable_count
# Çıktı: 1 (enable edilmiş)

cat /sys/kernel/debug/clk/uart0_gate/clk_prepare_count
# Çıktı: 1

# Parent clock
cat /sys/kernel/debug/clk/uart0_gate/clk_parent
# Çıktı: uart_mux

# Flags
cat /sys/kernel/debug/clk/uart0_gate/clk_flags
# Çıktı: 8 (CLK_SET_RATE_PARENT = BIT(3))

clk_summary — tüm ağacı tek seferde gör

bash — clk_summary çıktısı
# Tam clock ağacı özeti (hiyerarşik)
cat /sys/kernel/debug/clk/clk_summary

# Örnek çıktı:
#                      enable  prepare  protect                  rate   accuracy   phase
# clock                 count    count    count        rate       (Hz)  (degrees)
# --------------------------------------------------------------------------------
# xo_24mhz                  1        1        0    24000000          0         0
#    pll_a               1        1        0  1200000000          0         0
#       cpu_div          1        1        0   300000000          0         0
#          cpu_gate      1        1        0   300000000          0         0
#       bus_div          1        1        0   600000000          0         0
#    pll_b               0        0        0   800000000          0         0
#       uart_mux         1        1        0   800000000          0         0
#          uart_div      1        1        0    50000000          0         0
#             uart0_gate 1        1        0    50000000          0         0

clk_dump — JSON formatında çıktı

bash — clk_dump JSON çıktısı
# JSON formatında clock ağacı
cat /sys/kernel/debug/clk/clk_dump | python3 -m json.tool | head -40

# Örnek çıktı (kısaltılmış):
# {
#   "xo_24mhz": {
#     "enable_count": 1,
#     "prepare_count": 1,
#     "rate": 24000000,
#     "children": {
#       "pll_a": { ... }
#     }
#   }
# }

Yaygın sorunlar ve çözümleri

BelirtiOlası nedenKontrol
-EPROBE_DEFER döngüsüProvider sürücüsü yüklenmemişdmesg | grep "clock provider"; modprobe sırası
clk_get: -ENOENTclock-names eşleşmiyorDTS clock-names vs devm_clk_get() argümanı
rate 0 dönüyorrecalc_rate 0 döndürüyorparent_rate sıfır mı? register init edilmemiş mi?
PLL lock timeoutparent clock yetersiz veya VCO aralığı dışıPLL_CON1 lock bit; N/R/OD hesabı kontrol
Clock disable edilemiyorCLK_IS_CRITICAL flagclk_flags dosyası; clk_init_data flags'e bak
Rate değişmiyorCLK_SET_RATE_GATE — önce disable gerekclk_disable_unprepare → clk_set_rate → enable

Kernel config gereksinimleri

Kconfig — CCF hata ayıklama
# CCF temel
CONFIG_COMMON_CLK=y

# debugfs clock arayüzü
CONFIG_DEBUG_FS=y

# Clock rate hata ayıklama mesajları (verbose)
# CONFIG_COMMON_CLK_DEBUG — ayrı seçenek (bazı kernel versiyonlarında)

# Hazır clock helper'ları
CONFIG_COMMON_CLK_FIXED_RATE=y
CONFIG_COMMON_CLK_FIXED_FACTOR=y
CONFIG_COMMON_CLK_DIVIDER=y
CONFIG_COMMON_CLK_MUX=y
CONFIG_COMMON_CLK_GATE=y

Bu bölümde

  • /sys/kernel/debug/clk/<clock>/: rate, enable_count, prepare_count, parent, flags
  • clk_summary: tüm ağacı hiyerarşik tablo olarak göster
  • clk_dump: JSON formatı, araçlarla işlemeye uygun
  • -EPROBE_DEFER: provider modülü yüklenmemiş veya DT phandle hatalı
  • CLK_IS_CRITICAL / CLK_SET_RATE_GATE: beklenmedik davranışların en yaygın kaynağı