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 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 helper | Tanımı |
|---|---|---|
| fixed-rate | clk_hw_register_fixed_rate() | Sabit frekanslı kristal/osilatör |
| fixed-factor | clk_hw_register_fixed_factor() | Parent'ın sabit çarpan/böleni |
| divider | clk_hw_register_divider() | Programlanabilir bölen (register'dan) |
| mux | clk_hw_register_mux() | Birden fazla parent arasında seçim |
| gate | clk_hw_register_gate() | Enable/disable için tek bit kontrolü |
| composite | clk_hw_register_composite() | mux + divider + gate birleşimi |
| pll | Özel clk_ops (elle yazılır) | Tam programlanabilir PLL |
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
// 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.
// 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
// 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
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;
}
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
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ı
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
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
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;
}
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
#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
/* 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
| Flag | Tanımı |
|---|---|
| CLK_SET_RATE_PARENT | set_rate isteği parent'a da iletilir |
| CLK_IGNORE_UNUSED | Consumer yoksa bile clock disable edilmez |
| CLK_IS_CRITICAL | Sistem kritik — hiçbir zaman disable edilmez |
| CLK_GET_RATE_NOCACHE | Her sorguda donanımdan okunur, önbellek kullanılmaz |
| CLK_SET_RATE_GATE | set_rate öncesi clock disable edilmeli |
| CLK_SET_PARENT_GATE | set_parent öncesi clock disable edilmeli |
| CLK_DUTY_CYCLE_PARENT | duty 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
/* 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
/* 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
/*
* 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
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
/* 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: 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
/ {
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
&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
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;
}
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.
// 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.
#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ı
| Aksiyon | Tetiklenme zamanı | Dönüş değeri |
|---|---|---|
| PRE_RATE_CHANGE | set_rate çağrısından önce; bloke edebilir | NOTIFY_BAD → değişimi iptal et |
| POST_RATE_CHANGE | set_rate başarıyla tamamlandıktan sonra | NOTIFY_OK |
| ABORT_RATE_CHANGE | set_rate başarısız olduğunda rollback için | NOTIFY_OK |
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ı
# 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
# 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ı
# 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
| Belirti | Olası neden | Kontrol |
|---|---|---|
| -EPROBE_DEFER döngüsü | Provider sürücüsü yüklenmemiş | dmesg | grep "clock provider"; modprobe sırası |
| clk_get: -ENOENT | clock-names eşleşmiyor | DTS clock-names vs devm_clk_get() argümanı |
| rate 0 dönüyor | recalc_rate 0 döndürüyor | parent_rate sıfır mı? register init edilmemiş mi? |
| PLL lock timeout | parent clock yetersiz veya VCO aralığı dışı | PLL_CON1 lock bit; N/R/OD hesabı kontrol |
| Clock disable edilemiyor | CLK_IS_CRITICAL flag | clk_flags dosyası; clk_init_data flags'e bak |
| Rate değişmiyor | CLK_SET_RATE_GATE — önce disable gerek | clk_disable_unprepare → clk_set_rate → enable |
Kernel config gereksinimleri
# 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ğı