Common Clock Framework (CCF)

리눅스 Common Clock Framework를 "SoC 안의 클록 트리를 추상화하는 API" 수준이 아니라, provider 등록부터 consumer 사용, rate/parent negotiation, prepare/enable 락 규칙, bootloader handoff, assigned-clocks 초기화, runtime PM·reset·regulator 시퀀스, debugfs와 tracepoint 기반 디버깅까지 이어지는 전체 모델로 정리합니다. clk_core, clk_hw, clk_ops, composite clock, clk_summary, clk_ignore_unused, notifier, bulk API, exclusivity API 같은 실전 포인트를 자세히 다룹니다.

전제 조건: Device Tree, 전원 관리, Reset Controller, Regulator 프레임워크, 디바이스 드라이버 문서를 먼저 읽으세요. CCF는 거의 모든 SoC 드라이버의 초기화 순서와 연결되며, 단독 주제가 아니라 probe, suspend, DVFS, reset 시퀀스와 함께 읽어야 의미가 살아납니다.
일상 비유: 클록 프레임워크는 건물 전체의 배전반과 변압기, 타이머 릴레이를 합쳐 놓은 제어실과 비슷합니다. 기준 발진기에서 출발한 신호가 PLL, mux, divider, gate를 거쳐 각 방(디바이스)에 배분되고, 어느 회로는 끄면 안 되고, 어느 회로는 특정 부모를 고른 뒤에만 안전하게 바꿀 수 있습니다.

핵심 요약

  • CCF — SoC 내부 클록 제공자와 소비자를 공통 모델로 연결하는 프레임워크입니다.
  • 소비자 핸들은 opaque, provider는 명시적 — 소비자는 struct clk를, 제공자는 clk_hwclk_ops를 다룹니다.
  • 트리 구조 — root, PLL, mux, divider, gate, composite clock이 부모-자식 관계로 연결됩니다.
  • prepare vs enable — sleep 가능한 준비 단계와 atomic context에서도 호출 가능한 빠른 gate 단계가 분리됩니다.
  • rate negotiation — leaf 소비자의 요구가 부모 재선택, parent rate 조정, 형제 노드 영향으로 이어질 수 있습니다.
  • 부팅과 PM 영향 — bootloader가 남긴 클록 상태, assigned-clocks, unused clock disable, runtime PM, reset sequencing이 모두 실제 동작에 영향을 줍니다.

단계별 이해

  1. 소비자 이름 찾기
    드라이버가 어떤 clock-namesdevm_clk_get() 이름으로 클록을 가져오는지 먼저 확인합니다.
  2. 트리 구조 복원
    root, PLL, mux, divider, gate 순서로 실제 하드웨어 경로를 복원하고 공유 부모가 있는지 봅니다.
  3. on/off와 rate 변경 분리
    clk_prepare_enable() 문제와 clk_set_rate()/clk_set_parent() 문제를 같은 것으로 보지 않습니다.
  4. 부팅 초기 상태 확인
    bootloader가 이미 켜 둔 클록인지, assigned-clocks가 초기 parent/rate를 바꾸는지 확인합니다.
  5. debugfs와 trace로 검증
    clk_summary, tracepoint, dmesg를 이용해 enable count, 실제 rate, parent, orphan 여부를 확인합니다.

CCF가 필요한 이유와 책임 경계

과거에는 많은 SoC 드라이버가 MMIO 레지스터를 직접 만져 gate 비트와 divider 값을 바꾸는 식으로 클록을 다뤘습니다. 하지만 SoC가 복잡해지면서 하나의 PLL이 CPU, GPU, UART, 디스플레이, 멀티미디어 블록을 동시에 먹이고, parent selection과 rate 변경이 전력 정책과 연결되며, 부팅 초기에 firmware가 설정해 둔 값과 커널이 나중에 설정한 값이 섞이기 시작했습니다. 이 복잡성을 각 드라이버가 제멋대로 처리하면 공유 자원 충돌과 유지보수 지옥이 생깁니다. CCF는 이 문제를 공통 트리 모델과 공통 API로 정리합니다.

또한 CCF는 이름이 비슷한 다른 "clock" 계층과 책임이 다릅니다. 특히 clocksource/timekeeper, cpufreq, regulator, reset과 자주 혼동됩니다.

서브시스템질문주요 자료구조대표 API비고
clocksource/timekeeper지금 몇 ns인가?struct clocksourceclocksource_register_hz()시간 계산용입니다.
clockevent언제 인터럽트를 낼까?struct clock_event_deviceclockevents_config_and_register()타이머 이벤트용입니다.
CCF어느 장치에 어떤 주파수를 공급할까?struct clk, struct clk_hwclk_set_rate(), clk_prepare_enable()신호 분배와 전력 제어용입니다.
cpufreqCPU policy를 몇 MHz로 둘까?struct cpufreq_policydev_pm_opp_set_rate()내부적으로 CCF를 사용할 수 있습니다.
regulator전압 레일을 몇 uV로 둘까?struct regulatorregulator_set_voltage()DVFS에서 CCF와 함께 움직입니다.
resetIP 블록을 언제 reset 해제할까?struct reset_controlreset_control_deassert()클록 시퀀스와 강하게 묶입니다.
CCF는 시간 계산 프레임워크가 아니라 신호 분배 프레임워크다 clocksource/timekeeper 현재 시각 계산 clockevent 타이머 인터럽트 예약 Common Clock Framework PLL, mux, divider, gate, parent rate와 enable 제어 cpufreq / OPP 정책 계층 reset 순서 제어 regulator 전압 레일 runtime PM 전원 상태
경계 원칙: CCF는 "얼마나 시간이 지났는가"를 계산하지 않고, "어떤 회로에 어떤 빈도의 토글 신호를 넣을 것인가"를 제어합니다. 그래서 clocksource/timekeeper와 이름만 비슷할 뿐 목적은 다릅니다.

핵심 객체 모델: struct clk, clk_core, clk_hw, clk_ops

CCF는 consumer와 provider를 일부러 다른 타입으로 분리합니다. 소비자 드라이버는 opaque handle인 struct clk만 보고, 제공자 드라이버는 struct clk_hw를 자신만의 하드웨어 구조체에 embed한 뒤 clk_ops로 콜백을 구현합니다. 프레임워크 내부에서는 struct clk_core가 topology와 accounting을 들고 있습니다.

객체누가 주로 만지는가역할
struct clkconsumer 드라이버opaque consumer handle입니다. enable, set_rate 같은 API 진입점입니다.
struct clk_core프레임워크 내부부모/자식 관계, 이름, ops, 참조 카운트, notifier 같은 공통 accounting을 잡습니다.
struct clk_hwprovider 드라이버provider의 실제 하드웨어 구조체와 프레임워크를 잇는 고리입니다.
struct clk_opsprovider 드라이버enable/disable, recalc_rate, determine_rate, set_parent 같은 하드웨어별 동작을 구현합니다.
struct clk_init_dataprovider 드라이버이름, 부모 이름/포인터, flags, ops를 묶어 초기 등록에 사용합니다.

공식 커널 문서는 struct clk_core가 topology를, clk_hwclk_ops가 하드웨어 특화 부분을 담당한다고 설명합니다. 즉 provider는 "레지스터를 어떻게 만질지"만 알면 되고, enable count나 notifier list 같은 공통 bookkeeping은 프레임워크가 맡습니다.

/* 간소화한 개념도: 실제 헤더의 일부만 발췌 */
struct clk_core {
    const char *name;
    const struct clk_ops *ops;
    struct clk_hw *hw;
    struct clk_core *parent;
    struct clk_core **parents;
    u8 num_parents;
    /* enable_count, notifier, flags, rate 관련 bookkeeping ... */
};

struct clk_ops {
    int  (*prepare)(struct clk_hw *hw);
    void (*unprepare)(struct clk_hw *hw);
    int  (*enable)(struct clk_hw *hw);
    void (*disable)(struct clk_hw *hw);
    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_parent)(struct clk_hw *hw, u8 index);
    u8  (*get_parent)(struct clk_hw *hw);
    int (*set_rate)(struct clk_hw *hw,
                    unsigned long rate,
                    unsigned long parent_rate);
    int (*set_rate_and_parent)(struct clk_hw *hw,
                               unsigned long rate,
                               unsigned long parent_rate,
                               u8 index);
    unsigned long (*recalc_accuracy)(struct clk_hw *hw,
                                           unsigned long parent_accuracy);
    int (*get_phase)(struct clk_hw *hw);
    int (*set_phase)(struct clk_hw *hw, int degrees);
    void (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};
consumer와 provider 사이를 잇는 객체 모델 consumer driver devm_clk_get() clk_prepare_enable() struct clk consumer handle API entry point internal link struct clk_core name, parent, parents[] enable/prepare counting flags, notifier, topology hw + ops pointer clk_hw provider side embedded object clk_ops enable / set_rate get_parent / debug_init

이 분리는 매우 중요합니다. provider는 gate 비트, divider 필드, PLL lock polling 같은 하드웨어 세부사항만 관리하고, consumer는 "이 클록이 몇 MHz여야 하는가"라는 의도만 표현합니다. 그래서 같은 consumer 코드가 다른 SoC에서도 비교적 동일하게 유지될 수 있습니다.

provider가 만드는 클록 토폴로지

CCF의 provider는 대개 clock-controller 드라이버입니다. 이 드라이버는 SoC 내부의 발진기, PLL, mux, divider, gate, composite clock을 트리 형태로 등록합니다. 소비자 드라이버는 트리 전체를 몰라도 leaf 이름이나 phandle만 알면 됩니다.

클록 종류대표 기능주요 콜백실전 주의점
fixed-rate고정 발진기recalc_rateroot나 외부 oscillator를 모델링할 때 씁니다.
PLL배수/주파수 합성recalc_rate, determine_rate, set_ratelock 대기, 안정화 시간, parent 제약이 중요합니다.
mux부모 선택get_parent, set_parent부모 전환 중 glitch 없는지 확인해야 합니다.
divider분주recalc_rate, round_rate/determine_rate, set_rate정수 분주 한계 때문에 요청 값이 반올림됩니다.
gateon/offenable, disable, is_enabled공유 레지스터가 rate 비트와 섞여 있으면 자체 락이 필요합니다.
compositemux+divider+gate 묶음복합실제 SoC는 composite clock으로 표현되는 경우가 많습니다.

하나의 UART가 48MHz를 필요로 한다고 해서, 항상 leaf gate만 켜면 끝나는 것은 아닙니다. parent mux를 바꿔야 할 수도 있고, divider를 조정해야 할 수도 있으며, 경우에 따라 상위 PLL rate가 바뀌어 다른 형제 노드도 영향을 받을 수 있습니다.

하나의 shared parent가 여러 leaf 소비자에 영향을 주는 트리 구조 OSC 24MHz PLL0 1200MHz bus mux UART path mux/div → uart0_gate → uart0 요청: 48MHz SPI path mux/div → spi1_gate → spi1 요청: 100MHz GPU path mux/div → gpu_gate → gpu 요청: 600MHz
#include <linux/clk-provider.h>

struct my_gate {
    struct clk_hw hw;
    void __iomem *reg;
    u8 bit_idx;
    spinlock_t *lock;
};

#define to_my_gate(_hw) container_of(_hw, struct my_gate, hw)

static int my_gate_enable(struct clk_hw *hw)
{
    struct my_gate *g = to_my_gate(hw);
    unsigned long flags;
    u32 val;

    spin_lock_irqsave(g->lock, flags);
    val = readl(g->reg);
    val |= BIT(g->bit_idx);
    writel(val, g->reg);
    spin_unlock_irqrestore(g->lock, flags);
    return 0;
}

static const struct clk_ops my_gate_ops = {
    .enable = my_gate_enable,
    .disable = my_gate_disable,
    .is_enabled = my_gate_is_enabled,
};

공식 커널 문서도 provider 구현은 clk_hw를 자신의 하드웨어 구조체에 embed하고, container_of()로 다시 되돌아가 MMIO 레지스터를 만지는 패턴을 기본으로 설명합니다. 중요한 점은 enable count, notifier count, topology bookkeeping을 provider가 직접 관리하지 않는다는 것입니다.

rate negotiation: recalc, round, determine, set_parent

CCF에서 가장 까다로운 부분은 "얼마나 빠르게 돌릴 것인가"보다 "그 속도를 어떤 부모와 어떤 divider 조합으로 만들 것인가"입니다. 실제 하드웨어는 임의의 모든 주파수를 만들 수 없기 때문에, 요청 rate는 rounding되거나 parent가 바뀌거나, 심지어 상위 PLL까지 손을 대야 할 수 있습니다.

콜백역할핵심 포인트
recalc_rate현재 하드웨어 상태로 실제 rate 계산debugfs와 consumer가 보는 rate의 기준이 됩니다.
round_rate요청 rate를 가장 가까운 값으로 반올림공식 문서 기준으로 rate 변경 가능 clock은 round_rate 또는 determine_rate 중 하나가 필요합니다.
determine_rate부모 선택까지 포함한 rate 탐색단순 rounding보다 복잡한 트리 탐색에 적합합니다.
set_rate실제 레지스터에 rate 반영parent가 이미 정해졌다는 가정에서 동작하는 경우가 많습니다.
get_parent / set_parent현재 부모 읽기 / 변경glitch-free 전환 조건과 gate 상태 제약을 반영해야 합니다.
set_rate_and_parentrate와 parent를 원자적으로 같이 변경분리 변경 시 glitch가 나거나 중간 상태가 위험할 때 중요합니다.
공유 부모 주의: leaf 소비자의 clk_set_rate()는 leaf 하나만 건드리는 것처럼 보이지만, 실제로는 공유 parent의 divider나 PLL을 움직일 수 있습니다. 공식 커널 API에도 이 때문에 notifier, exclusivity, rate negotiation 콜백이 존재합니다.
leaf 소비자의 rate 요청이 parent와 PLL까지 전파되는 흐름 consumer clk_set_rate(uart, 48MHz) uart divider recalc / round / determine parent mux pll_uart vs pll_periph shared PLL 1200MHz → 960MHz? 가능한 결정 1 parent 유지, divider만 조정 형제 노드 영향 없음 가능한 결정 2 parent를 다른 PLL로 바꿈 glitch-free reparent 조건 필요 가능한 결정 3 shared PLL rate 자체를 조정 형제 소비자 영향 가능
static int my_div_determine_rate(struct clk_hw *hw,
                                  struct clk_rate_request *req)
{
    unsigned long best_parent = req->best_parent_rate;
    unsigned long best_rate = 0;

    /* 실제 구현은 가능한 parent/div 조합을 훑으며 오차가 가장 적은 후보를 고른다 */
    /* req->best_parent_hw / req->best_parent_rate / req->rate 채움 */

    req->best_parent_rate = best_parent;
    req->rate = best_rate;
    return 0;
}

공식 커널 문서는 rate-changing clock은 round_rate 또는 determine_rate 중 하나가 필요하다고 설명합니다. 단순 divider처럼 부모가 하나인 경우엔 round_rate만으로 충분할 수 있지만, 부모 선택까지 포함하는 복잡한 트리라면 determine_rate가 더 적합합니다.

flags와 제약 조건

CCF의 실제 동작은 콜백만이 아니라 등록 시 지정하는 flags에도 크게 좌우됩니다. 아래는 provider 드라이버에서 자주 보는 flags의 의미입니다.

flag의미언제 쓰는가
CLK_SET_RATE_PARENT자식의 rate 요청이 부모 rate 변경까지 전파될 수 있음을 표시leaf divider가 혼자 해결할 수 없을 때
CLK_IS_CRITICALunused clock disable 때 꺼지면 안 되는 클록인터커넥트, 시스템 버스, 콘솔 같은 필수 경로
CLK_IGNORE_UNUSEDunused clock disable 대상에서 제외bootloader/firmware handoff가 복잡한 경로
CLK_SET_RATE_GATEgate가 켜져 있는 동안 rate 변경이 안전하지 않음rate 변경 전에 clock off가 필요한 하드웨어
CLK_SET_PARENT_GATEparent 변경이 gate 상태에서만 안전glitch-free reparent가 안 되는 mux
CLK_OPS_PARENT_ENABLE특정 ops 수행 중 parent를 enable해야 함부모가 꺼져 있으면 자식 register access가 불가능한 구조

flags를 잘못 주면 디버깅이 아주 어려워집니다. 예를 들어 필수 bus clock에 CLK_IS_CRITICAL 또는 동등한 보호가 빠지면, late init 단계의 unused clock disable이 예상치 못하게 시스템을 굳혀 버릴 수 있습니다. 반대로 아무거나 critical로 마크하면 진짜로 누수가 있는 clock enable imbalance를 놓칠 수 있습니다.

prepare lock, enable lock, 그리고 문맥 제약

공식 커널 문서가 특히 강조하는 부분이 락입니다. CCF는 전역적으로 두 개의 락 그룹을 사용합니다. enable lock은 spinlock이고 .enable, .disable에 걸립니다. prepare lock은 mutex이고 그 외 다른 연산들에 걸립니다. 이 구조 때문에 enable/disable은 sleep하면 안 되고, prepare류와 rate/parent 변경 연산은 sleep 가능 컨텍스트에서 수행됩니다.

그룹대표 API락 종류sleep 가능 여부의미
enable 그룹clk_enable(), clk_disable(), is_enabled 관련spinlock불가빠른 gate 토글과 atomic context 지원
prepare 그룹clk_prepare(), clk_set_rate(), clk_set_parent()mutex가능PLL lock 대기, 버스 접근, 복잡한 rate 탐색 허용
CCF 락 모델: enable은 빠르게, prepare는 느리지만 유연하게 prepare lock (mutex) clk_prepare / clk_unprepare clk_set_rate / clk_set_parent determine_rate / round_rate sleep 가능, PLL lock polling 가능 I2C/PMIC/syscon 접근 가능 atomic context 호출 금지 enable lock (spinlock) clk_enable / clk_disable .enable / .disable callback sleep 불가 MMIO bit set/clear 같은 빠른 경로 atomic context 호출 가능 공유 register가 rate 비트와 섞이면 driver 자체 락 필요 같은 하드웨어 register를 두 그룹이 공유할 수 있음
공식 문서의 핵심 포인트: enable 그룹과 prepare 그룹이 서로 다른 락에 걸리므로, driver는 두 그룹이 공유하는 자원에 대해 자체 락을 추가로 고려해야 합니다. 예를 들어 같은 register가 enable bit와 divider field를 동시에 들고 있다면, provider 내부 락 없이는 race가 납니다.

공식 문서는 또한 CCF가 재진입 가능(reentrant)하다고 설명합니다. 즉 하나의 .set_rate 구현이 내부에서 다른 clock API를 다시 호출할 수 있습니다. 이런 중첩 호출은 provider 구현자가 의도적으로 설계하는 경우가 많지만, 락 순서와 recursion 경로를 잘못 잡으면 deadlock이나 예상치 못한 parent churn으로 이어질 수 있습니다.

provider 등록 패턴

clock-controller 드라이버는 probe 시에 자신의 하드웨어 클록들을 등록하고, DT provider로 노출합니다. 단일 clock 하나를 내놓을 수도 있지만, SoC CCU/CRU 드라이버는 보통 수십에서 수백 개의 clk_hw를 배열로 관리합니다.

#include <linux/clk-provider.h>

struct my_ccu {
    void __iomem *base;
    spinlock_t lock;
    struct clk_hw_onecell_data *onecell;
};

static int my_ccu_probe(struct platform_device *pdev)
{
    struct my_ccu *ccu;

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

    spin_lock_init(&ccu->lock);
    ccu->base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(ccu->base))
        return PTR_ERR(ccu->base);

    ccu->onecell = devm_kzalloc(&pdev->dev,
                                    struct_size(ccu->onecell, hws, 128),
                                    GFP_KERNEL);
    if (!ccu->onecell)
        return -ENOMEM;

    ccu->onecell->num = 128;
    ccu->onecell->hws[5] =
        clk_hw_register_gate(&pdev->dev, "uart0_gate", "uart0_div",
                             0, ccu->base + 0x120, 4, 0, &ccu->lock);
    /* divider/mux/pll/composite도 같은 식으로 채움 */

    return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get,
                                       ccu->onecell);
}

단일 onecell provider는 인덱스 기반 phandle을 받는 DT와 잘 맞습니다. 더 복잡한 controller는 composite helper나 커스텀 provider 함수를 사용할 수 있습니다. 핵심은 provider probe가 끝난 뒤에야 consumer의 devm_clk_get()가 성공한다는 점입니다.

등록 방식언제 적합한가장점주의점
clk_hw_register_gate() 류 helper단순 gate/divider/mux코드가 짧고 검증된 helper 사용특수 하드웨어 동작은 커스텀 ops가 필요합니다.
composite helpermux+divider+gate가 한 블록에 묶인 경우실제 SoC register layout과 잘 맞음중간 단계 디버깅이 어려울 수 있습니다.
custom clk_hw + custom opsPLL, fractional divider, vendor 특수 로직유연함race, rounding, lock 대기를 직접 설계해야 합니다.
of_clk_add_hw_provider / onecellDT consumer가 phandle로 참조하는 경우DT와 자연스럽게 연결됨provider probe ordering이 중요합니다.

consumer API와 드라이버 사용 패턴

일반 디바이스 드라이버는 CCF를 opaque 리소스처럼 사용합니다. 가장 기본 흐름은 devm_clk_get()으로 클록을 얻고, 필요 시 clk_prepare_enable(), clk_set_rate(), clk_disable_unprepare()를 균형 있게 호출하는 것입니다.

static int mydev_probe(struct platform_device *pdev)
{
    struct mydev *priv;
    int ret;

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

    priv->core_clk = devm_clk_get(&pdev->dev, "core");
    if (IS_ERR(priv->core_clk))
        return dev_err_probe(&pdev->dev, PTR_ERR(priv->core_clk),
                             "failed to get core clock\n");

    ret = clk_set_rate(priv->core_clk, 50000000);
    if (ret)
        return dev_err_probe(&pdev->dev, ret, "failed to set rate\n");

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

    ret = mydev_hw_init(priv);
    if (ret) {
        clk_disable_unprepare(priv->core_clk);
        return ret;
    }

    return 0;
}

실전에서는 클록이 하나가 아닌 경우가 많으므로 bulk API도 중요합니다.

static struct clk_bulk_data my_clks[] = {
    { .id = "apb" },
    { .id = "core" },
    { .id = "bus" },
};

ret = devm_clk_bulk_get(dev, ARRAY_SIZE(my_clks), my_clks);
if (ret)
    return ret;

ret = clk_bulk_prepare_enable(ARRAY_SIZE(my_clks), my_clks);
if (ret)
    return ret;
consumer API용도언제 쓰는가
devm_clk_get()필수 클록 하나 얻기대표적인 기본 패턴
devm_clk_get_optional()선택적 클록 얻기하드웨어 변종마다 있는/없는 클록
clk_prepare_enable()prepare + enable 묶음일반적인 on 시퀀스
clk_disable_unprepare()disable + unprepare 묶음off 시퀀스
clk_set_rate()원하는 rate 요청정확한 peripheral timing이 필요할 때
clk_get_rate()실제 반영 rate 확인rounding 이후 실제 값 확인
clk_set_parent()parent 변경드라이버가 parent를 직접 고를 필요가 있을 때
clk_bulk_prepare_enable()다수 클록 일괄 enableAPB/core/bus 등 여러 클록이 필요한 IP
consumer 설계 원칙: "부모가 뭔지 모르니 무조건 set_parent부터 하자"는 접근은 좋지 않습니다. 먼저 clk_set_rate()만으로 목적을 달성할 수 있는지 보고, parent 고정이 정말 필요한 경우에만 노출하는 편이 소비자 드라이버 재사용성에 유리합니다.

Device Tree 바인딩, clocks, assigned-clocks

Device Tree에서 consumer는 보통 clocksclock-names로 provider를 참조합니다. phandle entries는 순서가 명확해야 하고, clock이 둘 이상이면 이름도 함께 주는 것이 일반적입니다. 공식 DT binding 지침은 phandle 배열은 명시적으로 ordered해야 하며, 이름을 줄 때는 suffix를 붙이지 말라고 권고합니다.

ccu: clock-controller@ff760000 {
    compatible = "vendor,soc-ccu";
    reg = <0x0 0xff760000 0x0 0x1000>;
    #clock-cells = <1>;
};

uart0: serial@10000000 {
    compatible = "vendor,my-uart";
    reg = <0x0 0x10000000 0x0 0x100>;
    clocks = <&ccu 5>, <&ccu 6>;
    clock-names = "baud", "apb";
};

spi1: spi@10010000 {
    compatible = "vendor,my-spi";
    reg = <0x0 0x10010000 0x0 0x1000>;
    clocks = <&ccu 17>, <&ccu 18>;
    clock-names = "core", "bus";
};

부팅 초기에 default parent와 rate를 미리 잡아야 할 때는 assigned-clocks, assigned-clock-parents, assigned-clock-rates를 씁니다.

&ccu {
    assigned-clocks = <&ccu 5>, <&ccu 17>;
    assigned-clock-parents = <&pll_uart>, <&pll_periph>;
    assigned-clock-rates = <48000000>, <100000000>;
};
DT 속성의미실전 주의점
clocksprovider phandle + index순서가 ABI입니다. 이름 없이 순서만 믿는 코드는 유지보수가 어렵습니다.
clock-namesconsumer가 의미를 붙이는 이름공식 지침상 suffix를 붙이지 않는 편이 좋습니다.
#clock-cellsprovider phandle 뒤 인자 수index 기반 onecell인지, 더 복잡한 cell encoding인지 결정합니다.
assigned-clocksboot-time 초기 설정할 clockdriver가 probe되기 전에 기본 상태를 잡는 데 유용합니다.
assigned-clock-parents초기 parent 선택모든 clock에 항상 필요한 것은 아닙니다.
assigned-clock-rates초기 rate 설정다른 driver가 나중에 바꾸면 유지 보장이 없습니다.
assigned-clocks의 한계: 공식 커널 문서의 카메라 센서 가이드도 assigned-clocks를 권장 방식으로 소개하면서, 다른 드라이버가 직접 또는 간접으로 rate를 바꿀 수 있어 신뢰성 한계가 있다고 지적합니다. 즉 assigned-clocks는 "초기값 설정"에는 좋지만, 장기 불변성을 보장하는 메커니즘은 아닙니다.
DT의 phandle과 clock-names가 consumer를 provider에 연결한다 provider node ccu: clock-controller@... #clock-cells = <1> index 5 = uart0_gate index 17 = spi1_core consumer node clocks = <&ccu 5>, <&ccu 6> clock-names = "baud", "apb" devm_clk_get(dev, "baud") 순서와 이름이 ABI assigned-clocks boot-time 초기 parent/rate 다른 driver가 이후 바꿀 수 있음 초기값이지 영구 계약은 아님 provider probe 이전에는 consumer가 -EPROBE_DEFER를 받을 수 있음 clock provider 등록 순서가 늦으면 "clock not found"처럼 보이지만 실제로는 deferred probe일 수 있다

bootloader handoff, orphan, unused clock disable

CCF 문서를 이론적으로만 읽으면 놓치기 쉬운 부분이 부팅 초기 상태입니다. 많은 플랫폼에서 bootloader나 firmware는 이미 console, DRAM, interconnect, UART, storage용 클록을 켜 둡니다. 커널은 provider가 등록되면 그 상태를 해석하고, late init 단계에서 unused clock을 끄기도 합니다. 이 과정에서 "driver는 아무 enable도 안 했는데 장치는 되더라" 같은 착시가 생깁니다.

현상왜 생기는가대응
driver가 enable 안 해도 부팅 직후는 동작bootloader가 이미 켜 둔 clock에 의존unused clock disable 이후에도 살아남는지 확인합니다.
late init 이후 갑자기 장치가 죽음CCF가 unused clock을 정리했기 때문consumer가 prepare/enable 균형을 맞추는지 확인합니다.
provider probe 전 consumer가 ENOENT처럼 보임실제로는 -EPROBE_DEFER 경로일 수 있음dev_err_probe()로 에러를 전파하고 재시도를 허용합니다.
일부 clock이 orphan처럼 보임부모 provider가 아직 등록되지 않았거나 DT topology가 잘못됨provider order와 parent 이름/인덱스를 재점검합니다.

공식 CCF 문서는 개발 중 bootloader 의존성을 임시로 회피하려면 clk_ignore_unused bootarg를 쓸 수 있다고 설명합니다. 다만 이 옵션은 진짜 버그를 감추기 쉬우므로 원인 분석이 끝나면 제거하는 편이 좋습니다.

# unused clock disable 추적
tp_printk trace_event=clk:clk_disable

# 개발 중 임시 우회
clk_ignore_unused
개발용 우회일 뿐: clk_ignore_unused는 probe 누락, error path imbalance, 잘못된 runtime PM 시퀀스를 숨길 수 있습니다. "이 옵션을 켜니 부팅된다"는 것은 흔히 consumer enable 누락의 강한 증거이지, 해결책이 아닙니다.

runtime PM, reset, regulator와의 시퀀스

대부분의 장치에서 clock enable은 독립 동작이 아닙니다. 실제 하드웨어는 전압 레일이 먼저 살아야 하고, 어떤 장치는 reset line을 deassert하기 전에 parent clock이 안정되어 있어야 하며, runtime suspend 때는 register retention 여부에 따라 reset을 유지할지 말지 판단해야 합니다.

전형적 bring-up 순서이유
1. runtime PM resume 또는 power domain on레지스터 접근 가능 상태를 확보
2. regulator enable전압 레일 안정화
3. 필요한 clock prepare/enableIP가 internal state machine을 돌릴 수 있게 함
4. reset deassert정상 clock 하에서 reset 해제
5. rate/parent 최종 조정 및 레지스터 초기화장치 동작 조건 확정

물론 하드웨어에 따라 순서는 달라질 수 있습니다. 중요한 것은 "모든 장치가 같은 순서를 쓴다"가 아니라, driver가 그 장치의 데이터시트 요구를 CCF API와 reset/regulator/runtime PM API 조합으로 정확히 표현해야 한다는 점입니다.

static int mydev_runtime_resume(struct device *dev)
{
    struct mydev *priv = dev_get_drvdata(dev);
    int ret;

    ret = regulator_bulk_enable(priv->num_supplies, priv->supplies);
    if (ret)
        return ret;

    ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
    if (ret)
        goto err_disable_regs;

    ret = reset_control_deassert(priv->rst);
    if (ret)
        goto err_disable_clks;

    return 0;

err_disable_clks:
    clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
err_disable_regs:
    regulator_bulk_disable(priv->num_supplies, priv->supplies);
    return ret;
}

DVFS 계열 driver에서는 regulator와 CCF가 더 촘촘하게 엮입니다. 주파수 상승 시 보통 전압을 먼저 올리고 그 다음 clk_set_rate()를 호출해야 하고, 주파수 하강 시에는 반대로 rate를 먼저 내린 뒤 전압을 낮추는 순서가 필요합니다. 이런 작업은 Regulator 프레임워크와 OPP 계층이 조율하는 경우가 많습니다.

고급 API: notifier, bulk, phase, exclusivity

기본 API만으로도 많은 driver가 충분히 동작하지만, CCF에는 복잡한 장치용 고급 기능도 있습니다.

API용도어떤 때 유용한가
clk_notifier_register()rate change 전/후 notifiershared parent 변경을 미리 알고 버퍼/타이밍을 조정해야 하는 장치
clk_bulk_*다수 clock 일괄 관리bus/core/iface clock을 한 번에 다루는 장치
clk_set_phase() / clk_get_phase()phase shift 제어샘플링 위상 조정이 필요한 PHY, 고속 I/O
clk_get_accuracy()accuracy 정보 조회정밀도 제약이 있는 consumer
clk_rate_exclusive_get()provider rate에 대한 exclusivity 확보다른 consumer의 set_rate가 glitch를 유발하면 안 되는 경로
static int my_clk_notifier(struct notifier_block *nb,
                           unsigned long event, void *data)
{
    struct clk_notifier_data *cnd = data;

    switch (event) {
    case PRE_RATE_CHANGE:
        /* FIFO watermark, divider shadow register 등 사전 조정 */
        break;
    case POST_RATE_CHANGE:
        /* 새 실제 rate 기준으로 timing 재설정 */
        break;
    }

    return NOTIFY_OK;
}
shared clock 보호: 공식 kernel API는 clk_rate_exclusive_get()을 제공합니다. shared parent의 rate 변경이 다른 consumer를 깨뜨릴 수 있는데, 특정 장치가 그 rate를 잠시 독점해야 한다면 이런 API를 검토할 가치가 있습니다.

debugfs, tracepoint, 관측 포인트

CCF 디버깅의 시작점은 거의 항상 clk_summary입니다. 여기에 이름, 실제 rate, prepare/enable count, parent 관계가 나오므로, driver가 기대하는 트리와 실제 트리를 맞춰 볼 수 있습니다. 여기에 tracepoint와 dmesg, DT dump를 조합하면 대부분의 문제를 좁힐 수 있습니다.

# debugfs mount
mount -t debugfs none /sys/kernel/debug

# 전체 클록 트리 요약
cat /sys/kernel/debug/clk/clk_summary

# 특정 leaf 확인
grep -E 'uart|spi|gpu|disp' /sys/kernel/debug/clk/clk_summary

# DT의 clock-names 확인
grep -R "clock-names" /sys/firmware/devicetree/base 2>/dev/null

# tracepoint로 disable 추적
echo 1 > /sys/kernel/tracing/events/clk/enable/enable
echo 1 > /sys/kernel/tracing/events/clk/disable/enable
echo 1 > /sys/kernel/tracing/events/clk/set_rate/enable
cat /sys/kernel/tracing/trace_pipe
관찰 대상왜 중요한가주로 보는 곳
실제 raterounding 이후 요청값과 다를 수 있음clk_summary, clk_get_rate()
enable/prepare counterror path imbalance를 찾기 좋음clk_summary
parent 관계예상치 못한 reparent를 확인clk_summary, provider debug
unused disable 시점bootloader 의존성을 드러냄tracepoint, boot log
deferred probeprovider order 문제를 드러냄dev_err_probe(), dmesg

다음과 같은 체크리스트를 기억해 두면 좋습니다.

흔한 실패 패턴

증상가능한 원인먼저 볼 것일반적인 수정 방향
failed to get clockprovider 미등록, DT 이름 불일치, phandle 인덱스 오류clock-names, provider probe 로그, -EPROBE_DEFERdev_err_probe() 사용, DT 이름 정렬
요청한 50MHz가 실제 48MHz정수 divider 한계, roundingclk_get_rate(), provider round_rate()tolerance 허용 또는 parent 변경 전략 개선
한 장치 rate 변경 후 다른 장치가 깨짐shared parent/PLL 영향트리 구조와 shared parentexclusive rate, parent 분리, 정책 재설계
atomic context에서 sleep warningclk_prepare()류를 잘못된 문맥에서 호출call trace, prepare/enable 구분atomic path는 enable만, prepare는 sleepable path로 이동
부팅 후 late init에서 장치 정지unused clock disableclk_ignore_unused 유무에 따른 차이consumer enable 균형 수정 또는 critical/ignore-unused 재검토
runtime suspend/resume 후 register access hangresume 순서 잘못됨, reset deassert 타이밍 오류PM 콜백 순서, clock on/off 시점regulator, clock, reset 순서 정리
가장 흔한 오해: "클록 문제니까 provider 쪽에서만 보자"는 접근은 자주 실패합니다. 실제 원인은 DT clock-names 오타, runtime PM imbalance, bootloader handoff, reset 순서 문제인 경우가 많습니다.

끝까지 따라가는 상태 전이 예제

아래는 UART driver가 probe되는 동안 CCF와 reset/regulator/runtime PM이 어떤 순서로 맞물리는지 따라가는 예제입니다.

시점사건CCF에서 일어나는 일다른 서브시스템실패 시 흔한 증상
T0bootloader가 UART console용 clock를 켜 둠provider 아직 미등록이거나 커널이 상태만 이어받음firmware handoffdriver 버그가 잠시 가려짐
T1clock-controller probeprovider가 clk_hw와 DT provider 등록MMIO, reset, maybe sysconprovider 실패 시 consumer가 defer
T2UART probedevm_clk_get("baud"), clk_set_rate(48MHz)DT parseclock not found, rate rounding
T3runtime resume / power onclk_prepare_enable()regulator on, reset deassertwrong order면 bus hang
T4late unused clock disable사용하지 않는 leaf가 꺼짐late initconsumer enable imbalance면 장치 정지
T5runtime suspendclk_disable_unprepare()reset 유지/해제, regulator offresume 후 stale rate/parent
provider 등록부터 UART suspend/resume까지의 상태 전이 T0 bootloader on hidden dependency T1 provider probe of_clk_add_provider T2 consumer get/set_rate 48MHz request T3 prepare + enable reset deassert T4 unused disable imbalances exposed T5 runtime suspend/resume disable/unprepare → restore 핵심은 "클록 API 호출 자체"보다 provider 등록 순서, bootloader handoff, reset/regulator/PM 시퀀스, late unused disable까지 전체 흐름을 같이 보는 것입니다.
상태 전이 해석 팁: 부팅 초기에 장치가 동작한다고 해서 consumer 드라이버가 올바르게 CCF를 쓰고 있다는 뜻은 아닙니다. late unused clock disable 이후에도 살아남는지, suspend/resume 이후에도 같은 rate와 parent로 돌아오는지가 진짜 검증입니다.