Common Clock Framework -2- (APIs)

<kernel v5.4>

Common Clock Framework -2- (APIs)

주요 API

  • clk_get()
    • 사용할 클럭을 찾아온다.
    • 클럭 provider의 참조 카운터를 증가시킨다
  • clk_put()
    • 사용이 끝난 클럭을 지정한다.
    • 클럭 provider의 참조 카운터를 감소시킨다
  • clk_prepare()
    • 클럭 소스로부터 클럭이 enable 되게 한다. 슬립 가능한 API이다.
    • 절전 기능이 동작하던 클럭들을 깨운다.
  • clk_unprepare()
    • 클럭 소스로부터 클럭이 disable 되게 한다. 슬립 가능한 API이다.
    • 절전 기능이 동작하는 클럭들을 재운다.
  • clk_enable()
    • 클럭 소스로부터 클럭 gate가 열리도록 하며 슬립하지 않으므로 atomic context에서도 사용할 수 있다.
  • clk_disable()
    • 클럭 소스로부터 클럭 gate가 닫히도록 하며 슬립하지 않으므로 atomic context에서도 사용할 수 있다.
  • clk_get_rate()
    • 현재 클럭의 rate를 알아온다.
  • clk_set_rate()
    • 현재 클럭의 rate를 설정한다.
  • clk_set_parent()
    • mux 클럭의 소스를 선택한다. (부모 클럭을 선택한다)
  • clk_get_parent()
    • mux 클럭의 소스를 알아온다. (부모 클럭을 알아온다.)

관리 카운터

다음은 클럭 코어들의 관리 카운터들이다.

  • ref.refcount
    • 사용되는 클럭 코어들의 참조 카운터가 증/감된다.
  • prepare_count
    • prepare되는 클럭 코어들의 카운터가 증/감된다.
  • enable_count
    • enable되는 클럭 코어들의 카운터가 증/감된다.
  • protect_count
    • protection되는 클럭 코어들의 카운터가 증/감된다.
    • protection된 경우 exclusive 사용자 외에는 구성 값들을 변경할 수 없다.

 

다음 그림은 호출되는 클럭 API와 참조 카운터들을 보여준다.

  • gated 클럭의 경우 clk_enable() 함수를 호출해야 클럭이 출력된다.

 

다음 그림은 디바이스가 클럭을 사용할 때 하이라키로 구성된 클럭들의 각종 참조 카운터들을 보여준다.

  • 클럭 API가 호출되어 최상위 클럭까지 참조 카운터들이 증/감된다.

 


클럭 사용/해제

클럭 사용

clk_get()

drivers/clk/clkdev.c

struct clk *clk_get(struct device *dev, const char *con_id)
{
        const char *dev_id = dev ? dev_name(dev) : NULL;
        struct clk_hw *hw;

        if (dev && dev->of_node) {
                hw = of_clk_get_hw(dev->of_node, 0, con_id);
                if (!IS_ERR(hw) || PTR_ERR(hw) == -EPROBE_DEFER)
                        return clk_hw_create_clk(dev, hw, dev_id, con_id);
        }

        return __clk_get_sys(dev, dev_id, con_id);
}
EXPORT_SYMBOL(clk_get);

디바이스에서 사용할 클럭을 알아온다. @con_id가 주어진 경우 @con_id 명으로 검색하여 해당 클럭을 찾아오고, 지정되지 않은 경우 연결된 부모 클럭을 알아온다. 그리고 클럭 코어의 참조 카운터를 1 증가시킨다.

  • 코드 라인 6~10에서 디바이스 트리에서 @con_id 또는 0번 인덱스의 클럭 hw를 알아온다. 만일 에러가 없거나 유예 상태인 경우 클럭을 할당하고 알아온 클럭 hw의 클럭 코어에 연결하고 클럭을 반환한다.
  • 코드 라인 12에서 클럭 provider 리스트에서 @con_id 명으로 검색하여 해당 클럭을 할당하고 반환한다.

 

__clk_get_sys()

drivers/clk/clkdev.c

static struct clk *__clk_get_sys(struct device *dev, const char *dev_id,
                                 const char *con_id)
{
        struct clk_hw *hw = clk_find_hw(dev_id, con_id);

        return clk_hw_create_clk(dev, hw, dev_id, con_id);
}

클럭 provider 리스트에서 @con_id 명으로 클럭 hw를 검색하여 해당 클럭 hw에 연결할 클럭을 할당하고 반환한다.

 

클럭 찾기

clk_find_hw()

drivers/clk/clkdev.c

struct clk_hw *clk_find_hw(const char *dev_id, const char *con_id)
{
        struct clk_lookup *cl;
        struct clk_hw *hw = ERR_PTR(-ENOENT);

        mutex_lock(&clocks_mutex);
        cl = clk_find(dev_id, con_id);
        if (cl)
                hw = cl->clk_hw;
        mutex_unlock(&clocks_mutex);

        return hw;
}

전역 클럭 리스트에서 @dev_id 및 @con_id 명으로 매치되는 클럭 hw를 반환한다. 검색이 실패한 경우 -ENOENT 에러를 반환한다.

 

clk_find()

drivers/clk/clkdev.c

/*
 * Find the correct struct clk for the device and connection ID.
 * We do slightly fuzzy matching here:
 *  An entry with a NULL ID is assumed to be a wildcard.
 *  If an entry has a device ID, it must match
 *  If an entry has a connection ID, it must match
 * Then we take the most specific entry - with the following
 * order of precedence: dev+con > dev only > con only.
 */
static struct clk_lookup *clk_find(const char *dev_id, const char *con_id)
{
        struct clk_lookup *p, *cl = NULL;
        int match, best_found = 0, best_possible = 0;

        if (dev_id)
                best_possible += 2;
        if (con_id)
                best_possible += 1;

        lockdep_assert_held(&clocks_mutex);

        list_for_each_entry(p, &clocks, node) {
                match = 0;
                if (p->dev_id) {
                        if (!dev_id || strcmp(p->dev_id, dev_id))
                                continue;
                        match += 2;
                }
                if (p->con_id) {
                        if (!con_id || strcmp(p->con_id, con_id))
                                continue;
                        match += 1;
                }

                if (match > best_found) {
                        cl = p;
                        if (match != best_possible)
                                best_found = match;
                        else
                                break;
                }
        }
        return cl;
}

전역 클럭 리스트에서 @dev_id 및 @con_id 명으로 클럭을 찾아 clk_lookup을 반환한다. 검색이 실패한 경우 null을 반환한다.

  • @dev_id 및 @con_id가 주어진 경우 일치되지 않으면 null을 반환한다. 검색된 항목들 중 가장 best 매치를 다음과 같은 조건으로 수행한다.
    • @dev_id 및 @con_id 명이 모두 일치하는 경우 즉각 반환
    • @dev_id 명이 일치하는 마지막 항목
    • @con_id 명이 일치하는 마지막 항목

 

클럭 사용 해제

clk_put()

drivers/clk/clkdev.c

void clk_put(struct clk *clk)
{
        __clk_put(clk);
}
EXPORT_SYMBOL(clk_put);

사용한 클럭을 해제한다.  그리고 사용한 클럭 코어의 참조 카운터를 1 감소시킨다.

 

__clk_put()

drivers/clk/clkdev.c

void __clk_put(struct clk *clk)
{
        struct module *owner;

        if (!clk || WARN_ON_ONCE(IS_ERR(clk)))
                return;

        clk_prepare_lock();

        /*
         * Before calling clk_put, all calls to clk_rate_exclusive_get() from a
         * given user should be balanced with calls to clk_rate_exclusive_put()
         * and by that same consumer
         */
        if (WARN_ON(clk->exclusive_count)) {
                /* We voiced our concern, let's sanitize the situation */
                clk->core->protect_count -= (clk->exclusive_count - 1);
                clk_core_rate_unprotect(clk->core);
                clk->exclusive_count = 0;
        }

        hlist_del(&clk->clks_node);
        if (clk->min_rate > clk->core->req_rate ||
            clk->max_rate < clk->core->req_rate)
                clk_core_set_rate_nolock(clk->core, clk->core->req_rate);

        owner = clk->core->owner;
        kref_put(&clk->core->ref, __clk_release);

        clk_prepare_unlock();

        module_put(owner);

        free_clk(clk);
}

사용한 클럭을 해제한다.  그리고 사용한 클럭 코어의 참조 카운터를 1 감소시킨다.

  • 코드 라인 8에서 클럭 설정을 변경하려면 전역 클럭 락을 획득해야 한다.
  • 코드 라인 15~20에서 클럭 rate 변경에 대한 protection을 위해 0이 아닌 경우 경고 메시지를 출력하고 0으로 변경한다.
  • 코드 라인 22에서 클럭 코어에서 클럭을 제거한다.
  • 코드 라인 23~25에서 클럭 사용자가 요청한 rate 범위가 클럭 코어의 req_rate가 아닌 경우 클럭 코어의 req_rate로 변경한다.
  • 코드 라인 28에서 클럭 코어의 참조 카운터를 감소시킨다.
  • 코드 라인 32에서 모듈의 참조 카운터를 감소시킨다.
  • 코드 라인 34에서 할당된 클럭을 해제한다.

 


클럭 준비/해지

클럭 준비(prepare)

clk_prepare()

drivers/clk/clk.c

/**
 * clk_prepare - prepare a clock source
 * @clk: the clk being prepared
 *
 * clk_prepare may sleep, which differentiates it from clk_enable.  In a simple
 * case, clk_prepare can be used instead of clk_enable to ungate a clk if the
 * operation may sleep.  One example is a clk which is accessed over I2c.  In
 * the complex case a clk ungate operation may require a fast and a slow part.
 * It is this reason that clk_prepare and clk_enable are not mutually
 * exclusive.  In fact clk_prepare must be called before clk_enable.
 * Returns 0 on success, -EERROR otherwise.
 */
int clk_prepare(struct clk *clk)
{
        if (!clk)
                return 0;

        return clk_core_prepare_lock(clk->core);
}
EXPORT_SYMBOL_GPL(clk_prepare);

상위 클럭 소스까지 prepare 시킨다.

 

clk_core_prepare_lock()

drivers/clk/clk.c

static int clk_core_prepare_lock(struct clk_core *core)
{
        int ret;

        clk_prepare_lock();
        ret = clk_core_prepare(core);
        clk_prepare_unlock();

        return ret;
}

lock을 획득한 상태에서 상위 클럭 소스까지 prepare 시킨다.

 

clk_core_prepare()

drivers/clk/clk.c

static int clk_core_prepare(struct clk_core *core)
{
        int ret = 0;

        lockdep_assert_held(&prepare_lock);

        if (!core)
                return 0;

        if (core->prepare_count == 0) {
                ret = clk_pm_runtime_get(core);
                if (ret)
                        return ret;

                ret = clk_core_prepare(core->parent);
                if (ret)
                        goto runtime_put;

                trace_clk_prepare(core);

                if (core->ops->prepare)
                        ret = core->ops->prepare(core->hw);

                trace_clk_prepare_complete(core);

                if (ret)
                        goto unprepare;
        }

        core->prepare_count++;

        /*
         * CLK_SET_RATE_GATE is a special case of clock protection
         * Instead of a consumer claiming exclusive rate control, it is
         * actually the provider which prevents any consumer from making any
         * operation which could result in a rate change or rate glitch while
         * the clock is prepared.
         */
        if (core->flags & CLK_SET_RATE_GATE)
                clk_core_rate_protect(core);

        return 0;
unprepare:
        clk_core_unprepare(core->parent);
runtime_put:
        clk_pm_runtime_put(core);
        return ret;
}

상위 클럭 소스까지 prepare 시킨다.

  • 코드 라인 7~8에서 이 함수는 재귀호출에서 사용되므로 클럭이 지정되지 않으면 빠져나간다.
  • 코드 라인 10~28에서 한 번도 prepare 한 적이 없는 경우 다음과 같은 동작을 처리한다.
    • 절전 기능이 있어 슬립된 클럭 코어의 경우 깨운다.
    • 부모 클럭에 대해 재귀 호출로 이 함수를 호출하여 prepare 한다.
    • 이 클럭 디바이스에 구현된 ops->prepare 후크 함수를 호출한다.
  • 코드 라인 30에서 이 클럭 코어의 prepare_count를 1 증가시킨다.
  • 코드 라인 39~40에서 이 클럭 코어가 rate를 변경할 때 반드시 gate가 닫혀있어야 하는 경우 rate를 바꿀 수 없도록 protect를 설정한다.
  • 코드 라인 42에서 정상 값 0을 반환한다.

 

클럭 해제(unprepare)

clk_unprepare()

drivers/clk/clk.c

/**
 * clk_unprepare - undo preparation of a clock source
 * @clk: the clk being unprepared
 *
 * clk_unprepare may sleep, which differentiates it from clk_disable.  In a
 * simple case, clk_unprepare can be used instead of clk_disable to gate a clk
 * if the operation may sleep.  One example is a clk which is accessed over
 * I2c.  In the complex case a clk gate operation may require a fast and a slow
 * part.  It is this reason that clk_unprepare and clk_disable are not mutually
 * exclusive.  In fact clk_disable must be called before clk_unprepare.
 */
void clk_unprepare(struct clk *clk)
{
        if (IS_ERR_OR_NULL(clk))
                return;

        clk_core_unprepare_lock(clk->core);
}
EXPORT_SYMBOL_GPL(clk_unprepare);

상위 클럭 소스까지 unprepare 시킨다

 

clk_core_unprepare_lock()

drivers/clk/clk.c

static void clk_core_unprepare_lock(struct clk_core *core)
{
        clk_prepare_lock();
        clk_core_unprepare(core);
        clk_prepare_unlock();
}

lock을 획득한 상태에서 상위 클럭 소스까지 unprepare 시킨다

 

clk_core_unprepare()

drivers/clk/clk.c

static void clk_core_unprepare(struct clk_core *core)
{
        lockdep_assert_held(&prepare_lock);

        if (!core)
                return;

        if (WARN(core->prepare_count == 0,
            "%s already unprepared\n", core->name))
                return;

        if (WARN(core->prepare_count == 1 && core->flags & CLK_IS_CRITICAL,
            "Unpreparing critical %s\n", core->name))
                return;

        if (core->flags & CLK_SET_RATE_GATE)
                clk_core_rate_unprotect(core);

        if (--core->prepare_count > 0)
                return;

        WARN(core->enable_count > 0, "Unpreparing enabled %s\n", core->name);

        trace_clk_unprepare(core);

        if (core->ops->unprepare)
                core->ops->unprepare(core->hw);

        clk_pm_runtime_put(core);

        trace_clk_unprepare_complete(core);
        clk_core_unprepare(core->parent);
}

상위 클럭 소스까지 unprepare 시킨다.

  • 코드 라인 5~6에서 이 함수는 재귀호출을 하게 하였으며 클럭이 지정되지 않으면 빠져나간다.
  • 코드 라인 8~10에서 이미 unprepare된 클럭 코어는 경고 메시지 출력과 함께 함수를 빠져나간다.
  • 코드 라인 12~14에서 클럭의 출력을 off하게 막은 클럭 코어의 경우 경고 메시지 출력과 함께 함수를 빠져나간다.
  • 코드 라인 16~17에서 이 클럭 코어가 rate를 변경할 때 반드시 gate가 닫혀있어야 하는 경우이다. 이제 unprepare하여 gate가 닫혀 rate를 바꿀 수 있는 상태가 되었으므로 protect를 해제한다.
  • 코드 라인 19~20에서 prepare_count를 1 감소시킨다. 실제 0이 되기 전까지 이 클럭 코어를 unprepare 하지 않는다.
    • 최상위 부모 클럭까지 재귀 호출되어 1씩 감소시킨다.
  • 코드 라인 26~27에서 클럭 디바이스 드라이버에 구현된 ops->unprepare 후크를 호출한다.
  • 코드 라인 29에서 절전 기능을 가진 클럭 코어인 경우 절전 상태에 진입한다.
  • 코드 라인 32에서 상위 부모 클럭 코어를 unprepare하도록 이 함수를 호출한다. 재귀 동작으로 최상위 클럭 코어까지 호출된다.

 


클럭 게이트 enable & disable

클럭 게이트 enable

clk_enable()

drivers/clk/clk.c

/**
 * clk_enable - ungate a clock
 * @clk: the clk being ungated
 *
 * clk_enable must not sleep, which differentiates it from clk_prepare.  In a
 * simple case, clk_enable can be used instead of clk_prepare to ungate a clk
 * if the operation will never sleep.  One example is a SoC-internal clk which
 * is controlled via simple register writes.  In the complex case a clk ungate
 * operation may require a fast and a slow part.  It is this reason that
 * clk_enable and clk_prepare are not mutually exclusive.  In fact clk_prepare
 * must be called before clk_enable.  Returns 0 on success, -EERROR
 * otherwise.
 */
int clk_enable(struct clk *clk)
{
        if (!clk)
                return 0;

        return clk_core_enable_lock(clk->core);
}
EXPORT_SYMBOL_GPL(clk_enable);

상위 클럭 소스까지 gate를 enable 시킨다.

 

clk_core_enable_lock()

drivers/clk/clk.c

static int clk_core_enable_lock(struct clk_core *core)
{
        unsigned long flags;
        int ret;

        flags = clk_enable_lock();
        ret = clk_core_enable(core);
        clk_enable_unlock(flags);

        return ret;
}

lock을 획득한 상태에서 상위 클럭 소스까지 gate를 enable 시킨다.

 

clk_core_enable()

drivers/clk/clk.c

static int clk_core_enable(struct clk_core *core)
{
        int ret = 0;

        lockdep_assert_held(&enable_lock);

        if (!core)
                return 0;

        if (WARN(core->prepare_count == 0,
            "Enabling unprepared %s\n", core->name))
                return -ESHUTDOWN;

        if (core->enable_count == 0) {
                ret = clk_core_enable(core->parent);

                if (ret)
                        return ret;

                trace_clk_enable_rcuidle(core);

                if (core->ops->enable)
                        ret = core->ops->enable(core->hw);

                trace_clk_enable_complete_rcuidle(core);

                if (ret) {
                        clk_core_disable(core->parent);
                        return ret;
                }
        }

        core->enable_count++;
        return 0;
}

상위 클럭 소스까지 gate를 enable 시킨다.

  • 코드 라인 7~8에서 이 함수는 재귀호출에서 사용되므로 클럭이 지정되지 않으면 빠져나간다.
  • 코드 라인 10~12에서 아직 unprepare된 클럭 코어는 경고 메시지 출력과 함께 함수를 빠져나간다.
  • 코드 라인 14~31에서 한 번도 enable 한 적이 없는 경우 다음과 같은 동작을 처리한다.
    • 부모 클럭에 대해 재귀 호출로 이 함수를 호출하여 enable 한다.
    • 이 클럭 디바이스에 구현된 ops->enable 후크 함수를 호출한다.
  • 코드 라인 33에서 이 클럭 코어의 enable_count를 1 증가시킨다.
  • 코드 라인 34에서 정상 값 0을 반환한다.

 

클럭 게이트 disable

clk_disable()

drivers/clk/clk.c

/**
 * clk_disable - gate a clock
 * @clk: the clk being gated
 *
 * clk_disable must not sleep, which differentiates it from clk_unprepare.  In
 * a simple case, clk_disable can be used instead of clk_unprepare to gate a
 * clk if the operation is fast and will never sleep.  One example is a
 * SoC-internal clk which is controlled via simple register writes.  In the
 * complex case a clk gate operation may require a fast and a slow part.  It is
 * this reason that clk_unprepare and clk_disable are not mutually exclusive.
 * In fact clk_disable must be called before clk_unprepare.
 */
void clk_disable(struct clk *clk)
{
        if (IS_ERR_OR_NULL(clk))
                return;

        clk_core_disable_lock(clk->core);
}
EXPORT_SYMBOL_GPL(clk_disable);

상위 클럭 소스까지 gate를 disable 시킨다.

 

clk_core_disable_lock()

drivers/clk/clk.c

static void clk_core_disable_lock(struct clk_core *core)
{
        unsigned long flags;

        flags = clk_enable_lock();
        clk_core_disable(core);
        clk_enable_unlock(flags);
}

lock을 획득한 상태에서 상위 클럭 소스까지 gate를 disable 시킨다.

 

clk_core_disable()

drivers/clk/clk.c

static void clk_core_disable(struct clk_core *core)
{
        lockdep_assert_held(&enable_lock);

        if (!core)
                return;

        if (WARN(core->enable_count == 0, "%s already disabled\n", core->name))
                return;

        if (WARN(core->enable_count == 1 && core->flags & CLK_IS_CRITICAL,
            "Disabling critical %s\n", core->name))
                return;

        if (--core->enable_count > 0)
                return;

        trace_clk_disable_rcuidle(core);

        if (core->ops->disable)
                core->ops->disable(core->hw);

        trace_clk_disable_complete_rcuidle(core);

        clk_core_disable(core->parent);
}

상위 클럭 소스까지 gate를 disable 시킨다.

  • 코드 라인 5~6에서 이 함수는 재귀호출에서 사용되므로 클럭이 지정되지 않으면 빠져나간다.
  • 코드 라인 8~9에서 아직 enable되지 않은 클럭 코어는 경고 메시지 출력과 함께 함수를 빠져나간다.
  • 코드 라인 11~13에서 클럭의 출력을 항상 유지해야 하는 critical 클럭 코어의 경우 disable하면 안되므로 경고 메시지 출력과 함께 함수를 빠져나간다.
  • 코드 라인 15~16에서 enable_count를 1 감소시킨다. 실제 0이 되기 전까지 이 클럭 코어를 disable 하지 않는다.
    • 최상위 부모 클럭까지 재귀 호출되어 1씩 감소시킨다.
  • 코드 라인 20~21 이 클럭 디바이스에 구현된 ops->disable 후크 함수를 호출한다.
  • 코드 라인 25에서 상위 부모 클럭에 대해 disable하기 위해 이 함수를 재귀호출한다. 최상위 클럭 코어까지 호출된다.

 


Rate 설정

clk_rate_request 구조체

include/linux/clk-provider.h

/**
 * struct clk_rate_request - Structure encoding the clk constraints that
 * a clock user might require.
 *
 * @rate:               Requested clock rate. This field will be adjusted by
 *                      clock drivers according to hardware capabilities.
 * @min_rate:           Minimum rate imposed by clk users.
 * @max_rate:           Maximum rate imposed by clk users.
 * @best_parent_rate:   The best parent rate a parent can provide to fulfill the
 *                      requested constraints.
 * @best_parent_hw:     The most appropriate parent clock that fulfills the
 *                      requested constraints.
 *
 */
struct clk_rate_request {
        unsigned long rate;
        unsigned long min_rate;
        unsigned long max_rate;
        unsigned long best_parent_rate;
        struct clk_hw *best_parent_hw;
};

rate에 관련한 API 사용 시 인자 수를 줄이기 위해 사용되는 구조체이다.

  • rate
    • rate 값
  • min_rate
    • 최소 rate 값
  • max_rate
    • 최대 rate 값
  • best_parent_rate
    • 가장 적합한 부모 클럭 rate 값
  • *best_parent_hw
    • 가장 적합한 부모 클럭 hw 포인터

다음 그림은 clk_set_rate() 함수가 rate를 변경하기 위해 호출하는 4 단계의 주요 함수만을 보여준다.

clk_set_rate()

drivers/clk/clk.c

/**
 * clk_set_rate - specify a new rate for clk
 * @clk: the clk whose rate is being changed
 * @rate: the new rate for clk
 *
 * In the simplest case clk_set_rate will only adjust the rate of clk.
 *
 * Setting the CLK_SET_RATE_PARENT flag allows the rate change operation to
 * propagate up to clk's parent; whether or not this happens depends on the
 * outcome of clk's .round_rate implementation.  If *parent_rate is unchanged
 * after calling .round_rate then upstream parent propagation is ignored.  If
 * *parent_rate comes back with a new rate for clk's parent then we propagate
 * up to clk's parent and set its rate.  Upward propagation will continue
 * until either a clk does not support the CLK_SET_RATE_PARENT flag or
 * .round_rate stops requesting changes to clk's parent_rate.
 *
 * Rate changes are accomplished via tree traversal that also recalculates the
 * rates for the clocks and fires off POST_RATE_CHANGE notifiers.
 *
 * Returns 0 on success, -EERROR otherwise.
 */
int clk_set_rate(struct clk *clk, unsigned long rate)
{
        int ret; 

        if (!clk)
                return 0;

        /* prevent racing with updates to the clock topology */
        clk_prepare_lock();

        if (clk->exclusive_count)
                clk_core_rate_unprotect(clk->core);
 
        ret = clk_core_set_rate_nolock(clk->core, rate);

        if (clk->exclusive_count)
                clk_core_rate_protect(clk->core);
        
        clk_prepare_unlock();
                
        return ret;     
} 
EXPORT_SYMBOL_GPL(clk_set_rate);

클럭 rate를 변경 요청한다.

  • CLK_SET_RATE_PARENT 플래그가 사용된 클럭은 rate 설정 시 상위 클럭으로 전파되도록 한다.

 

clk_core_set_rate_nolock()

drivers/clk/clk.c

static int clk_core_set_rate_nolock(struct clk_core *core,
                                    unsigned long req_rate)
{
        struct clk_core *top, *fail_clk;
        unsigned long rate = req_rate;
        int ret = 0;

        if (!core)
                return 0;

        rate = clk_core_req_round_rate_nolock(core, req_rate);
        /* bail early if nothing to do */
        if (rate == clk_core_get_rate_nolock(core)
                return 0;

        /* fail on a direct rate set of a protected provider */
        if (clk_core_rate_is_protected(core))
                return -EBUSY;
        /* calculate new rates and get the topmost changed clock */
        top = clk_calc_new_rates(core, rate);
        if (!top)
                return -EINVAL;

        /* notify that we are about to change rates */
        fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);
        if (fail_clk) {
                pr_debug("%s: failed to set %s rate\n", __func__,
                                fail_clk->name);
                clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
                return -EBUSY;
        }

        /* change the rates */
        clk_change_rate(top);

        clk->req_rate = req_rate;
err:
        clk_pm_runtime_put(core);
        return ret;
}

클럭의 rate 설정을 한다. 성공 시에 0을 반환한다. 적용 시 현재 클럭 curr에서 변경이 필요한 클럭 코어 top까지 상위로 이동하면서 new rate를 산출하고, top 클럭에서 관련 클럭들의 최하위 클럭 bottom까지 rate를 재산출한다.

1 단계 – new rate 변경 필요 확인 (top <- curr)
  • 코드 라인 11~14에서 @req_rate에 대해 클럭 hw가 지원하는 가장 근접한 rate 값을 알아와서 기존 설정되었던 rate 값과 변화가 있는지 사전 체크한다. 만일 rate 변화가 없으면 req_rate를 적용할 필요 없으므로 성공 값 0을 반환한다.
  • 코드 라인 17~18에서 rate 변경이 protect된 상태이다. 이러한 경우 -EBUSY를 반환한다.
    • exclusive된 클럭 코어의 경우 다른 유저가 rate를 설정할 수 없다.
    • CLK_SET_RATE_GATE 플래그 설정된 클럭 코어의 경우 클럭이 prepare되어 gate가 열린 상태에선 rate를 설정할 수 없다.
2 단계 – new rate & parent 산출 (top <- curr)
  • 코드 라인 20~22에서 @req_rate에 대해 해당 클럭 코어부터 변경이 필요한 상위 클럭 코어까지 위로 올라가며 클럭 hw가 지원하는 가장 근접한 new rate 값을 각각 산출해둔다. 그리고 변경이 필요한 최상위 최상위 클럭 코어를 알아온다.
    • CLK_SET_RATE_PARENT 플래그가 사용된 클럭 코어들은 그 상위 클럭 코어에서 rate를 결정한다.
    • rate를 결정하는 클럭 코어의 타입에 따라 다음과 같이 재계산된다.
      • Mux 타입
        • determine_rate() 함수를 사용하여 어떤 부모 클럭 코어를 사용해야 요청한 노드의 rate에 인접한 값이 나올 수 있는지 계산된다.
      • 그 외 rate 조절 타입
        • round_rate() 함수를 사용하여 어떤 배율의 클럭 코어를 사용해야 요청한 노드의 rate에 인접한 값이 나올 수 있는지 계산된다.
3 단계 – new rate & parent 적용 통지 체크 (top -> bottom)
  • 코드 라인 25~31에서 적용할 클럭 코어들에 PRE_RATE_CHANGE를 통지하여 rate를 재설정 준비를 한다. 이 과정에서 실패하면 ABORT_RATE_CHANGE를 통지하여 roll-back 한다.
    • 상위 부모 클럭 코어까지 rate를 설정하게 할 때 상위 전달(propagation) 과정에서 notify되는 함수에서 해당 클럭 코어의 설정을 허용할지 여부를 결정하게 한다.
4 단계 – new rate & parent 적용 (top -> bottom)
  • 코드 라인 34에서 top 클럭 코어부터 마지막 자식 클럭 코어까지 산출된 new rate 및 new 부모 클럭을 적용하고 통지한다.
  • 코드 라인 36에서 현재 클럭 코어의 req_rate를 설정한다.
  • 코드 라인 38에서 필요 시 절전 기능을 켠다.

 

다음 그림은 클럭 F에서 rate를 바꿀 때 클럭 F->G까지 어떠한 단계로 바뀌는지 그 과정을 보여준다. (성공 예)

 

다음 그림은 위의 방법으로 실패 사례를 보여준다.

 


1 단계 – new rate 변경 필요 확인

best rate 산출과 관련한 API들은 다음과 같다.

  • clk_round_rate()
  • __clk_round_rate()
    • 호출 전에 먼저 lock을 획득한 상태여야 한다.
  • __clk_determine_rate()
    • 호출 전에 먼저 lock을 획득한 상태여야 한다.
  • clk_hw_round_rate()

 

clk_round_rate()

drivers/clk/clk.c

/**
 * clk_round_rate - round the given rate for a clk
 * @clk: the clk for which we are rounding a rate
 * @rate: the rate which is to be rounded
 *
 * Takes in a rate as input and rounds it to a rate that the clk can actually
 * use which is then returned.  If clk doesn't support round_rate operation
 * then the parent rate is returned.
 */
long clk_round_rate(struct clk *clk, unsigned long rate)
{
        struct clk_rate_request req;
        int ret;

        if (!clk)
                return 0;

        clk_prepare_lock();

        if (clk->exclusive_count)
                clk_core_rate_unprotect(clk->core);

        clk_core_get_boundaries(clk->core, &req.min_rate, &req.max_rate);
        req.rate = rate;

        ret = clk_core_round_rate_nolock(clk->core, &req);

        if (clk->exclusive_count)
                clk_core_rate_protect(clk->core);

        clk_prepare_unlock();

        if (ret)
                return ret;

        return req.rate;
}
EXPORT_SYMBOL_GPL(clk_round_rate);

요청 @rate에 대해 클럭 hw가 지원하는 가장 근접한 rate를 반환한다. 만일 클럭이 (*round_rate) ops를 지원하지 않으면 부모 클럭의 rate 값이 반환된다.

  • 코드 라인 11~12에서 클럭 사용자가 독점 관리하는 경우 가장 근접한 rate 계산을 위해 잠시 unprotect를 한다.
  • 코드 라인 14에서 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.
  • 코드 라인 17에서 요청 rate에 대해 클럭 hw가 지원하는 가장 근접한 rate를 반환한다. 만일 클럭이 ops->round_rate를 지원하지 않으면 부모 클럭의 rate 값이 반환된다.
  • 코드 라인 19~20에서 클럭 코어를 독점(exclusive)적으로 관리하는 경우 round rate 계산이 완료되었으므로 다시 protect를 한다.
  • 코드 라인 27에서 rate 값을 반환한다.

 

__clk_round_rate()

drivers/clk/clk.c

/**
 * __clk_round_rate - round the given rate for a clk
 * @clk: round the rate of this clock
 * @rate: the rate which is to be rounded
 *
 * Caller must hold prepare_lock.  Useful for clk_ops such as .set_rate
 */
unsigned long __clk_round_rate(struct clk *clk, unsigned long rate)
{
        unsigned long min_rate;
        unsigned long max_rate;

        if (!clk)
                return 0;

        clk_core_get_boundaries(clk->core, &min_rate, &max_rate);

        return clk_core_round_rate_nolock(clk->core, rate, min_rate, max_rate);
}
EXPORT_SYMBOL_GPL(__clk_round_rate);

요청 @rate에 대해 클럭 hw가 지원하는 가장 근접한 rate를 반환한다. 만일 클럭이 (*round_rate) ops를 지원하지 않으면 부모 클럭의 rate 값이 반환된다. 호출 시 prepare_lock을 잡아야 한다.

  • 코드 라인 16에서 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.
  • 코드 라인 18에서 요청 @rate에 대해 클럭 hw가 지원하는 가장 근접한 rate를 반환한다. 만일 클럭이 ops->round_rate를 지원하지 않으면 부모 클럭의 rate 값이 반환된다.

 

clk_hw_round_rate()

drivers/clk/clk.c

unsigned long clk_hw_round_rate(struct clk_hw *hw, unsigned long rate)
{
        int ret;
        struct clk_rate_request req;

        clk_core_get_boundaries(hw->core, &req.min_rate, &req.max_rate);
        req.rate = rate;

        ret = clk_core_round_rate_nolock(hw->core, &req);
        if (ret)
                return 0;

        return req.rate;
}
EXPORT_SYMBOL_GPL(clk_hw_round_rate);

요청 @rate에 대해 클럭 hw가 지원하는 가장 근접한  rate를 반환한다. 실패한 경우 0을 반환한다.

  • 코드 라인 6에서 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.
  • 코드 라인 9~11에서 클럭 hw가 지원하는 @rate에 근접한 rate를 반환한다. 만일 min_rate 및 max_rate 값을 초과하는 경우 실패 0 을 반환한다.
  • 코드 라인 13에서 결정된 rate 값을 반환한다.

 

__clk_determine_rate()

drivers/clk/clk.c

/**
 * __clk_determine_rate - get the closest rate actually supported by a clock
 * @hw: determine the rate of this clock
 * @req: target rate request
 *
 * Useful for clk_ops such as .set_rate and .determine_rate.
 */
int __clk_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
{
        if (!hw) {
                req->rate = 0;
                return 0;
        }

        return clk_core_round_rate_nolock(hw->core, req);
}
EXPORT_SYMBOL_GPL(__clk_determine_rate);

요청 rate에 대해 클럭 hw가 지원하는 가장 근접한  rate를 찾아 req->rate에 담아온다. 성공 시 0을 반환한다.

 

clk_core_req_round_rate_nolock()

drivers/clk/clk.c

static unsigned long clk_core_req_round_rate_nolock(struct clk_core *core,
                                                     unsigned long req_rate)
{
        int ret, cnt;
        struct clk_rate_request req;

        lockdep_assert_held(&prepare_lock);

        if (!core)
                return 0;

        /* simulate what the rate would be if it could be freely set */
        cnt = clk_core_rate_nuke_protect(core);
        if (cnt < 0)
                return cnt;

        clk_core_get_boundaries(core, &req.min_rate, &req.max_rate);
        req.rate = req_rate;

        ret = clk_core_round_rate_nolock(core, &req);

        /* restore the protection */
        clk_core_rate_restore_protect(core, cnt);

        return ret ? 0 : req.rate;
}

클럭 hw가 지원하는 @req_rate에 가장 근접한  rate를 반환한다. 실패한 경우 0을 반환한다.

  • 코드 라인 9~10에서 클럭 코어가 지정되지 않은 경우 0을 반환한다.
  • 코드 라인 13~15에서  클럭 코어에 protect가 걸려있는 경우 잠시 해제한다.
  • 코드 라인 17에서 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.
  • 코드 라인 20에서 rate 요청에 대해 클럭 hw가 지원하는 가장 근접한 rate를 반환한다. 만일 min_rate 및 max_rate 값을 초과하는 경우 에러(음수) 값을 얻어온다.
  • 코드 라인 23에서 클럭 코어를 잠시 unprotect 한 경우 다시 원래 대로 복원한다.
  • 코드 라인 25에서 결정된 rate 값을 반환한다. 만일 실패한 경우 0을 반환한다.

 

다음 그림은 rate 변경이 필요한지 유무를 판단하기 위해 호출하는 과정을 보여준다.

 

min_rate & max_rate boundary 산출

clk_core_get_boundaries()

drivers/clk/clk.c

static void clk_core_get_boundaries(struct clk_core *core,
                                    unsigned long *min_rate,
                                    unsigned long *max_rate)
{
        struct clk *clk_user;

        lockdep_assert_held(&prepare_lock);

        *min_rate = core->min_rate;
        *max_rate = core->max_rate;

        hlist_for_each_entry(clk_user, &core->clks, clks_node)
                *min_rate = max(*min_rate, clk_user->min_rate);

        hlist_for_each_entry(clk_user, &core->clks, clks_node)
                *max_rate = min(*max_rate, clk_user->max_rate);
}

자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.

  • min_rate
    • 자식 클럭들의 min_rate 값들 중 최대 min_rate
  • max_rate
    • 자식 클럭들의 max_rate 값들 중 최소 max_rate

 

다음 그림은 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아오는 것을 보여준다. (min_rate가 max_rate 보다 큰 숫자임을 주의한다)

 

연속하여 다음 두 함수를 알아본다.

  • clk_core_round_rate_nolock()
  • clk_core_determine_round_nolock()

 

clk_core_round_rate_nolock()

drivers/clk/clk.c

static int clk_core_round_rate_nolock(struct clk_core *core,
                                      struct clk_rate_request *req)
{
        lockdep_assert_held(&prepare_lock);

        if (!core) {
                req->rate = 0;
                return 0;
        }

        clk_core_init_rate_req(core, req);

        if (clk_core_can_round(core))
                return clk_core_determine_round_nolock(core, req);
        else if (core->flags & CLK_SET_RATE_PARENT)
                return clk_core_round_rate_nolock(core->parent, req);

        req->rate = core->rate;
        return 0;
}

rate 요청에 대해 클럭 hw가 지원하는 가장 근접한 rate를 찾아 req->rate에 담고, 성공 시 0을 반환한다.

  • 코드 라인 6~9에서 재귀 호출되었을 때 클럭 코어가 없으면 req->rate에 0을 담고, 성공 값 0을 반환한다.
  • 코드 라인 11에서 rate 요청 전에 현재 부모 클럭 및 rate로 best 결과를 초기화한다.
  • 코드 라인 13~14에서 클럭이 ops->determine_rate를 지원하는 mux 타입 클럭인 경우 호출하여 hw에 맞춰 적절한 rate 값을 산출하여 반환한다.
  • 코드 라인 15~16에서 CLK_SET_RATE_PARENT 플래그를 사용한 클럭은 rate 산출을 위해 부모 클럭에게 위임하러 이 함수를 재귀 호출한다.
  • 코드 라인 18~19에서 그 외의 클럭 타입인 경우 현재 클럭의 rate로 결정하고, 성공 값 0을 반환한다.

 

clk_core_determine_round_nolock()

drivers/clk/clk.c

static int clk_core_determine_round_nolock(struct clk_core *core,
                                           struct clk_rate_request *req)
{
        long rate;

        lockdep_assert_held(&prepare_lock);

        if (!core)
                return 0;

        /*
         * At this point, core protection will be disabled if
         * - if the provider is not protected at all
         * - if the calling consumer is the only one which has exclusivity
         *   over the provider
         */
        if (clk_core_rate_is_protected(core)) {
                req->rate = core->rate;
        } else if (core->ops->determine_rate) {
                return core->ops->determine_rate(core->hw, req);
        } else if (core->ops->round_rate) {
                rate = core->ops->round_rate(core->hw, req->rate,
                                             &req->best_parent_rate);
                if (rate < 0)
                        return rate;

                req->rate = rate;
        } else {
                return -EINVAL;
        }

        return 0;
}

rate 요청에 대해 클럭 hw가 지원하는 가장 근접한 rate를 찾아 req->rate에 담고, 성공 시 0을 반환한다.

  • 코드 라인 8~9에서 재귀 호출되었을 때 클럭 코어가 없으면 0을 반환한다.
  • 코드 라인 17~18에서 클럭 코어가 protect된 경우 결과 값으로 현재 클럭 코어의 rate 값을 req->rate에 대입한다.
  • 코드 라인 19~20에서 mux 클럭 타입에서 지원하는 (*determine_rate) 후크 함수를 호출하여 산출된 rate 값을 반환한다.
  • 코드 라인 21~27에서 rate류 클럭 타입에서 지원하는 (*round_rate) 후크 함수를 호출하여 rate 값을 알아와서 req->rate에 설정하고, 성공 0을 반환한다.

 

rate 요청 전 초기화

clk_core_init_rate_req()

drivers/clk/clk.c

static void clk_core_init_rate_req(struct clk_core * const core,
                                   struct clk_rate_request *req)
{
        struct clk_core *parent;

        if (WARN_ON(!core || !req))
                return;

        parent = core->parent;
        if (parent) {
                req->best_parent_hw = parent->hw;
                req->best_parent_rate = parent->rate;
        } else {
                req->best_parent_hw = NULL;
                req->best_parent_rate = 0;
        }
}

현재 부모 클럭 및 rate로 best 결과를 초기화한다.

  • 현재 클럭의 best 부모 클럭 및 rate로 현재 부모 클럭 및 rate를 지정한다.

 

clk_core_can_round()

drivers/clk/clk.c

static bool clk_core_can_round(struct clk_core * const core)
{
        return core->ops->determine_rate || core->ops->round_rate;
}

round & determine rate를 지원하는 클럭인지 여부를 반환한다.

  • (*determine_rate) 후크 함수는 mux 클럭 타입에서 지원한다.
  • (*round_rate) 후크 함수는 rate 클럭 타입에서 지원한다.

 


2 단계 – new rate & parent 산출

clk_calc_new_rates()

drivers/clk/clk.c

/*
 * calculate the new rates returning the topmost clock that has to be
 * changed.
 */
static struct clk_core *clk_calc_new_rates(struct clk_core *core,
                                           unsigned long rate)
{
        struct clk_core *top = core;
        struct clk_core *old_parent, *parent;
        unsigned long best_parent_rate = 0;
        unsigned long new_rate;
        unsigned long min_rate;
        unsigned long max_rate;
        int p_index = 0;
        long ret;

        /* sanity */
        if (IS_ERR_OR_NULL(core))
                return NULL;

        /* save parent rate, if it exists */
        parent = old_parent = core->parent;
        if (parent)
                best_parent_rate = parent->rate;

        clk_core_get_boundaries(core, &min_rate, &max_rate);

        /* find the closest rate and parent clk/rate */
        if (clk_core_can_round(core)) {
                struct clk_rate_request req;

                req.rate = rate;
                req.min_rate = min_rate;
                req.max_rate = max_rate;

                clk_core_init_rate_req(core, &req);

                ret = clk_core_determine_round_nolock(core, &req);
                if (ret < 0)
                        return NULL;

                best_parent_rate = req.best_parent_rate;
                new_rate = req.rate;
                parent = req.best_parent_hw ? req.best_parent_hw->core : NULL;

                if (new_rate < min_rate || new_rate > max_rate)
                        return NULL;
        } else if (!parent || !(core->flags & CLK_SET_RATE_PARENT)) {
                /* pass-through clock without adjustable parent */
                core->new_rate = core->rate;
                return NULL;
        } else {
                /* pass-through clock with adjustable parent */
                top = clk_calc_new_rates(parent, rate);
                new_rate = parent->new_rate;
                goto out;
        }

        /* some clocks must be gated to change parent */
        if (parent != old_parent &&
            (core->flags & CLK_SET_PARENT_GATE) && core->prepare_count) {
                pr_debug("%s: %s not gated but wants to reparent\n",
                         __func__, core->name);
                return NULL;
        }

        /* try finding the new parent index */
        if (parent && core->num_parents > 1) {
                p_index = clk_fetch_parent_index(core, parent);
                if (p_index < 0) {
                        pr_debug("%s: clk %s can not be parent of clk %s\n",
                                 __func__, parent->name, core->name);
                        return NULL;
                }
        }

        if ((core->flags & CLK_SET_RATE_PARENT) && parent &&
            best_parent_rate != parent->rate)
                top = clk_calc_new_rates(parent, best_parent_rate);

out:
        clk_calc_subtree(core, new_rate, parent, p_index);

        return top;
}

현재 클럭 코어로부터 변경이 필요한 상위 클럭까지 new rate를 산출하고, new rate 산출된 최상위 클럭 코어를 반환한다.

  • 코드 라인 18에서 요청 클럭 코어의 부모 클럭 코어를 알아와서 parent 및 old_parent에 보관한다.
  • 코드 라인 19~20에서 부모 클럭 코어가 존재하는 경우 부모 클럭 코어의 rate를 best_parent_rate에 보관한다.
  • 코드 라인 22에서 자식 클럭 코어들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.
  • 코드 라인 25~43에서 rate 최적값을 구하는 후크 함수가 지원되는 경우 이를 호출하여 최적의 rate 값을 알아온다.
    • 참고:
      • drivers/clk/ti/mux.c – __clk_mux_determine_rate()
      • drivers/clk/ti/divider.c – ti_clk_divider_round_rate()
  • 코드 라인 44~47에서 부모 클럭이 없거나 CLK_SET_RATE_PARENT 플래그가 없는 경우 현재 클럭의 rate만을 변경한다.
  • 코드 라인 48~53에서 부모 클럭의 rate를 산출하기 위해 인수로 부모 클럭과 요청 rate 값을 가지고 이 함수를 재귀호출하여 new_rate를 알아온다.
    • ops->determine_rate 및 ops->round_rate가 없는 gate 타입의 클럭을 사용하는 경우 CLK_SET_RATE_PARENT 플래그가 사용되지 않을 때까지 상위 클럭으로 이동한다.
  • 코드 라인 56~61에서 mux 타입 클럭 코어에서 rate 변경 요청으로 인해 부모 클럭의 변경이 필요한 상태이며 현재 클럭 코어에 CLK_SET_PARENT_GATE 플래그가 설정된 경우일 때 gate가 열린 상태이면 null을 반환한다.
    • CLK_SET_PARENT_GATE 플래그 옵션을 사용하는 mux 클럭 코어인 경우 gate를 닫지 않으면 mux에서 부모 클럭 코어의 변경이 실패한다.
  • 코드 라인 64~71에서 mux 타입 클럭 코어에서 부모 클럭 코어가 2개 이상이면 현재 선택된 부모 인덱스 값을 알아온다. 만일 알아올 수 없으면 null을 반환한다.
  • 코드 라인 73~75에서 클럭 코어에 CLK_SET_RATE_PARENT 플래그가 설정되었고 parent의 rate가 변경된 경우 인수로 부모 클럭과 best_parent_rate 값으로 이 함수를 재귀호출하여 상위 클럭으로 올라가서 rate가 변경될 상위 부모 클럭을 알아온다.
    • CLK_SET_RATE_PARENT 플래그가 사용되면 사용자 요청에 의해 현재 클럭의 hw가 지원하는 rate가 설정 불가능하면 부모 클럭의 hw가 지원하는 rate를 변경한다.
  • 코드 라인 77~78에서 out: 레이블이다. 이 함수가 재귀 호출된 경우 rate가 변경될 상위 부모 클럭부터 시작하여 연결된 모든 자식 클럭 방향으로 rate를 재계산하게 한다.
  • 코드 라인 80에서 변경된 최상위 클럭 코어를 반환한다.

 

다음 그림은 요청한 rate에 대해 클럭 hw가 지원하는 가장 근접한 new rate를 산출하는 과정을 보여준다.

 

다음 그림은 클럭 F에서 rate를 바꾸고자 계산하는 경우 클럭 F->D까지 rate를 산출하고 다시 클럭 D->G까지 재계산하는 과정을 보여준다.

 

clk_fetch_parent_index()

drivers/clk/clk.c

static int clk_fetch_parent_index(struct clk_core *core,
                                  struct clk_core *parent)
{
        int i;

        if (!parent)
                return -EINVAL;

        for (i = 0; i < core->num_parents; i++) {
                /* Found it first try! */
                if (core->parents[i].core == parent)
                        return i;

                /* Something else is here, so keep looking */
                if (core->parents[i].core)
                        continue;

                /* Maybe core hasn't been cached but the hw is all we know? */
                if (core->parents[i].hw) {
                        if (core->parents[i].hw == parent->hw)
                                break;

                        /* Didn't match, but we're expecting a clk_hw */
                        continue;
                }

                /* Maybe it hasn't been cached (clk_set_parent() path) */
                if (parent == clk_core_get(core, i))
                        break;

                /* Fallback to comparing globally unique names */
                if (core->parents[i].name &&
                    !strcmp(parent->name, core->parents[i].name))
                        break;
        }

        if (i == core->num_parents)
                return -EINVAL;

        core->parents[i].core = parent;
        return i;
}

현재 클럭 코어 @core의 부모 @parent 클럭 코어에 해당하는 인덱스 값을 알아온다. 실패하는 경우 음수 에러가 반환된다.

  • 코드 라인 9~12에서 num_parents 수 만큼 순회하며 @parent와 동일한 경우 해당 인덱스를 반환한다.
  • 코드 라인 15~16에서 다른 값을 가진 경우 skip 한다.
  • 코드 라인 19~25에서 동일한 부모 hw를 찾은 경우 해당 인덱스를 반환하기 위해 루프를 벗어난다.
  • 코드 라인 28~29에서 인덱스에 해당하는 부모 클럭이 @parent와 동일하면 해당 인덱스를 반환하기 위해 루프를 벗어난다.
  • 코드 라인 32~34에서 이름으로 검색하여 동일한 이름을 가진 부모 클럭 코어를 찾은 경우 해당 인덱스를 반환하기 위해 루프를 벗어난다.
  • 코드 라인 37~38에서 검색이 실패한 경우 -EINVAL을 반환한다.
  • 코드 라인 40~41에서 parent[] 맵에 부모 클럭 코어를 연결하고 해당 인덱스를 반환한다.

 


3 단계 – new rate & parent 적용 통지 체크

Rate 변경에 따른 통지 체크

clk_propagate_rate_change()

drivers/clk/clk.c

/*
 * Notify about rate changes in a subtree. Always walk down the whole tree
 * so that in case of an error we can walk down the whole tree again and
 * abort the change.
 */
static struct clk_core *clk_propagate_rate_change(struct clk_core *core,
                                                  unsigned long event)
{
        struct clk_core *child, *tmp_clk, *fail_clk = NULL;
        int ret = NOTIFY_DONE;

        if (core->rate == core->new_rate)
                return NULL;

        if (core->notifier_count) {
                ret = __clk_notify(core, event, core->rate, core->new_rate);
                if (ret & NOTIFY_STOP_MASK)
                        fail_clk = core;
        }

        hlist_for_each_entry(child, &core->children, child_node) {
                /* Skip children who will be reparented to another clock */
                if (child->new_parent && child->new_parent != core)
                        continue;
                tmp_clk = clk_propagate_rate_change(child, event);
                if (tmp_clk)
                        fail_clk = tmp_clk;
        }

        /* handle the new child who might not be in core->children yet */
        if (core->new_child) {
                tmp_clk = clk_propagate_rate_change(core->new_child, event);
                if (tmp_clk)
                        fail_clk = tmp_clk;
        }

        return fail_clk;
}

요청 클럭 코어부터 연결된 모든 하위 트리의 클럭 코어에 rate 변화를 통지한다. 성공 시 null을 반환하고, 실패하는 경우 실패한 클럭 코어를 반환한다.

  • 코드 라인 7~8에서 rate의 변화가 없는 경우 성공 값 null을 반환한다.
  • 코드 라인 10~14에서 통지 대상으로 등록된 클럭 코어에 대해 rate 변화 요청을 통지한다. 만일 결과가 NOTIFY_BAD 또는 NOTIFY_STOP을 갖는 경우 반환 값으로 사용할 fail_clk에 이 클럭 코어를 대입한다.
  • 코드 라인 16~19에서 자식 클럭 코어 수 만큼 루프를 돌며 요청 클럭이 이 자식 클럭 코어의 새로운 부모 클럭 코어가 아닌 경우 skip 한다.
  • 코드 라인 20~22에서 자식 클럭 코어들에 rate 변화 요청을 통지한다. 만일 실패한 경우 반환 값으로 사용할 fail_clk에 에러를 반환한 클럭 코어를 담는다.
  • 코드 라인 26~30에서 요청 클럭 코어에 새로 연결될(곧 children에 들어갈) 클럭 코어가 있는 경우 그 new_child 클럭에 대해서도 rate 변화 요청을 통지한다. 만일 실패한 경우 반환 값으로 사용할 fail_clk에 에러를 반환한 클럭 코어를 담는다.

 

다음 그림은 new rate로 변경하기 전에 통지 체크할 클럭에 확인하는 과정을 보여준다.

 

__clk_notify()

drivers/clk/clk.c

/**
 * __clk_notify - call clk notifier chain
 * @core: clk that is changing rate
 * @msg: clk notifier type (see include/linux/clk.h)
 * @old_rate: old clk rate
 * @new_rate: new clk rate
 *
 * Triggers a notifier call chain on the clk rate-change notification
 * for 'clk'.  Passes a pointer to the struct clk and the previous
 * and current rates to the notifier callback.  Intended to be called by
 * internal clock code only.  Returns NOTIFY_DONE from the last driver
 * called if all went well, or NOTIFY_STOP or NOTIFY_BAD immediately if
 * a driver returns that.
 */
static int __clk_notify(struct clk_core *core, unsigned long msg,
                unsigned long old_rate, unsigned long new_rate)
{
        struct clk_notifier *cn;
        struct clk_notifier_data cnd;
        int ret = NOTIFY_DONE;

        cnd.old_rate = old_rate;
        cnd.new_rate = new_rate;

        list_for_each_entry(cn, &clk_notifier_list, node) {
                if (cn->clk->core == core) {
                        cnd.clk = cn->clk;
                        ret = srcu_notifier_call_chain(&cn->notifier_head, msg,
                                        &cnd);
                        if (ret & NOTIFY_STOP_MASK)
                                return ret;
                }
        }

        return ret;
}

클럭 통지 리스트에 등록된 현재 클럭의 notifier 체인에 연결된 항목에 대해 srcu를 사용하여 모두 통지한다. 성공한 경우 NOTIFY_DONE을 반환한다.

 

클럭 Rate 변경 통지(notify) 등록 API

clk_notifier_register()

drivers/clk/clk.c

/***        clk rate change notifiers        ***/

/**
 * clk_notifier_register - add a clk rate change notifier
 * @clk: struct clk * to watch
 * @nb: struct notifier_block * with callback info
 *
 * Request notification when clk's rate changes.  This uses an SRCU
 * notifier because we want it to block and notifier unregistrations are
 * uncommon.  The callbacks associated with the notifier must not
 * re-enter into the clk framework by calling any top-level clk APIs;
 * this will cause a nested prepare_lock mutex.
 *
 * In all notification cases cases (pre, post and abort rate change) the
 * original clock rate is passed to the callback via struct
 * clk_notifier_data.old_rate and the new frequency is passed via struct
 * clk_notifier_data.new_rate.
 *
 * clk_notifier_register() must be called from non-atomic context.
 * Returns -EINVAL if called with null arguments, -ENOMEM upon
 * allocation failure; otherwise, passes along the return value of
 * srcu_notifier_chain_register().
 */
int clk_notifier_register(struct clk *clk, struct notifier_block *nb)
{
        struct clk_notifier *cn;
        int ret = -ENOMEM;

        if (!clk || !nb)
                return -EINVAL;

        clk_prepare_lock();

        /* search the list of notifiers for this clk */
        list_for_each_entry(cn, &clk_notifier_list, node)
                if (cn->clk == clk)
                        break;

        /* if clk wasn't in the notifier list, allocate new clk_notifier */
        if (cn->clk != clk) {
                cn = kzalloc(sizeof(*cn), GFP_KERNEL);
                if (!cn)
                        goto out;

                cn->clk = clk;
                srcu_init_notifier_head(&cn->notifier_head);

                list_add(&cn->node, &clk_notifier_list);
        }

        ret = srcu_notifier_chain_register(&cn->notifier_head, nb);

        clk->core->notifier_count++;

out:
        clk_prepare_unlock();

        return ret;
}
EXPORT_SYMBOL_GPL(clk_notifier_register);

클럭의 notify chain에 nofitier_block을 등록한다.

  • 코드 라인 12~14에서 clk_notifier_list에 요청한 클럭이 있는지 검색한다.
  • 코드 라인 17~26에서 검색되지 않는 경우 clk_notifier 구조체를 할당하고 클럭 정보를 대입한 후 clk_notifier_list에 등록한다.
  • 코드 라인 28에서 clk_notifier 구조체의 멤버 notifier_head에 요청한 notifier_block을 추가한다.
  • 코드 라인 30에서 클럭의 notifier_count 값을 1 증가시킨다.

 


4 단계 – 산출된 new rate & parent 적용

clk_change_rate()

drivers/clk/clk.c -1/2-

/*
 * walk down a subtree and set the new rates notifying the rate
 * change on the way
 */
static void clk_change_rate(struct clk_core *core)
{
        struct clk_core *child;
        struct hlist_node *tmp;
        unsigned long old_rate;
        unsigned long best_parent_rate = 0;
        bool skip_set_rate = false;
        struct clk_core *old_parent;
        struct clk_core *parent = NULL;

        old_rate = core->rate;

        if (core->new_parent) {
                parent = core->new_parent;
                best_parent_rate = core->new_parent->rate;
        } else if (core->parent) {
                parent = core->parent;
                best_parent_rate = core->parent->rate;
        }

        if (clk_pm_runtime_get(core))
                return;

        if (core->flags & CLK_SET_RATE_UNGATE) {
                unsigned long flags;

                clk_core_prepare(core);
                flags = clk_enable_lock();
                clk_core_enable(core);
                clk_enable_unlock(flags);
        }

        if (core->new_parent && core->new_parent != core->parent) {
                old_parent = __clk_set_parent_before(core, core->new_parent);
                trace_clk_set_parent(core, core->new_parent);

                if (core->ops->set_rate_and_parent) {
                        skip_set_rate = true;
                        core->ops->set_rate_and_parent(core->hw, core->new_rate,
                                        best_parent_rate,
                                        core->new_parent_index);
                } else if (core->ops->set_parent) {
                        core->ops->set_parent(core->hw, core->new_parent_index);
                }

                trace_clk_set_parent_complete(core, core->new_parent);
                __clk_set_parent_after(core, core->new_parent, old_parent);
        }

        if (core->flags & CLK_OPS_PARENT_ENABLE)
                clk_core_prepare_enable(parent);

        trace_clk_set_rate(core, core->new_rate);

        if (!skip_set_rate && core->ops->set_rate)
                core->ops->set_rate(core->hw, core->new_rate, best_parent_rate);

        trace_clk_set_rate_complete(core, core->new_rate);

요청한 클럭 코어부터 마지막 자식 클럭 코어까지 산출된 new rate 및 new 부모 클럭을 적용하고 통지한다.

  • 코드 라인 11~19에서 현재 클럭 코어의 rate를 백업해두고, 변경될 부모 클럭 정보를 다음 변수에 지정한다.
    • best_parent & best_parent_rate
  • 코드 라인 21~22에서 절전 기능이 있는 클럭이 슬립된 상태이면 깨운다.
  • 코드 라인 24~31에서 gate가 열린 상태에서만 rate를 바꿀 수 있는 클럭 hw를 위해 임시로 잠시 이 클럭을 prepare & enable 한다.
  • 코드 라인 33~48에서 새 부모 클럭으로 변경된 경우 다음과 같이 처리한다.
    • new 부모 클럭(입력 소스 변경)을 선택(변경)하기 전에 처리할 일을 수행한다.
    • mux 타입과 rate 변경이 동시에 가능한(pll) 타입의 클럭 디바이스 드라이버에 구현된 ops->set_rate_and_parent 후크 함수를 호출하여 실제 hw 기능으로 new 부모 클럭(입력 클럭 소스) 및 new rate를 변경한다.
    • mux 타입만을 지원하는 클럭 디바이스 드라이버에 구현된 ops->set_parent 후크 함수를 호출하여 실제 hw 기능으로 new 부모 클럭(입력 클럭 소스)을 선택(변경)한다.
    • 코드 라인 27에서 new 부모 클럭(입력 소스 변경)을 선택한 후에 처리할 일을 수행한다
  • 코드 라인 50~51에서 부모 클럭이 enable된 상태에서만 operation을 수행할 수 있는 클럭 hw를 지원하기 위해 부모 클럭이 닫혀있으면 임시로 잠시 부모 클럭을 prepare & enable 한다.
  • 코드 라인 55~56에서 바로 위에서 rate 설정한 경우가 아닌 경우로 한정한다. 클럭 디바이스 드라이버의 ops->set_rate 후크 함수를 호출하여 클럭 hw의 rate를 설정한다.

 

drivers/clk/clk.c -2/2-

        core->rate = clk_recalc(core, best_parent_rate);

        if (core->flags & CLK_SET_RATE_UNGATE) {
                unsigned long flags;

                flags = clk_enable_lock();
                clk_core_disable(core);
                clk_enable_unlock(flags);
                clk_core_unprepare(core);
        }

        if (core->flags & CLK_OPS_PARENT_ENABLE)
                clk_core_disable_unprepare(parent);

        if (core->notifier_count && old_rate != core->rate)
                __clk_notify(core, POST_RATE_CHANGE, old_rate, core->rate);

        if (core->flags & CLK_RECALC_NEW_RATES)
                (void)clk_calc_new_rates(core, core->new_rate);

        /*
         * Use safe iteration, as change_rate can actually swap parents
         * for certain clock types.
         */
        hlist_for_each_entry_safe(child, tmp, &core->children, child_node) {
                /* Skip children who will be reparented to another clock */
                if (child->new_parent && child->new_parent != core)
                        continue;
                clk_change_rate(child);
        }

        /* handle the new child who might not be in core->children yet */
        if (core->new_child)
                clk_change_rate(core->new_child);

        clk_pm_runtime_put(core);
}
  • 코드 라인 1에서 클럭 hw의 (*recalc_rate) ops를 호출하여 현재 클럭 코어에 재계산한 rate를 지정한다.
  • 코드 라인 3~10에서 임시로 잠시 현재 클럭 코어를 prepare & enable 한 경우 다시 disable & unprepare 한다.
  • 코드 라인 12~13에서 임시로 잠시 부모 클럭 코어를 prepare & enable 한 경우 disable & unprepare 한다.
  • 코드 라인 15~16에서 통지 대상 클럭에 대해 rate가 변경된 경우 POST_RATE_CHANGE를 보내 commit 통지한다.
  • 코드 라인 18~19에서 CLK_RECALC_NEW_RATES 플래그가 설정된 클럭은 rate가 변경된 경우  현재 클럭 코어로부터 변경이 필요한 상위 클럭까지 new rate를 다시 산출하게 한다.
    • exynos cpu의 경우 재산출을 통해 divider가 잘못 설정되는 일을 막아야 한다.
  • 코드 라인 25~30에서 하위 클럭 코어들의 부모 클럭이 변경된 경우 이 클럭 코어를 포함하고 그 하위 클럭 코어들에 대해 rate를 다시 산출한다. (마지막 child 클럭까지 재귀 호출된다)
  • 코드 라인 33~34에서 새로운 하위 클럭 코어가 추가된 경우 이 클럭 코어를 포함하고 그 하위 클럭 코어들에 대해 rate를 다시 산출한다. (마지막 child 클럭까지 재귀 호출된다)
  • 코드 라인 36에서 절전 기능이 있어 잠시 꺼둔 상태인 경우 필요 시 다시 슬립시킨다.

 

다음 그림은 최종 산출된 new rate를 결정 또는 취소할 때 호출되는 과정을 보여준다.

 

clk_calc_subtree()

drivers/clk/clk.c

static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate,
                             struct clk_core *new_parent, u8 p_index)
{
        struct clk_core *child;

        core->new_rate = new_rate;
        core->new_parent = new_parent;
        core->new_parent_index = p_index;
        /* include clk in new parent's PRE_RATE_CHANGE notifications */
        core->new_child = NULL;
        if (new_parent && new_parent != core->parent)
                new_parent->new_child = core;

        hlist_for_each_entry(child, &core->children, child_node) {
                child->new_rate = clk_recalc(child, new_rate);
                clk_calc_subtree(child, child->new_rate, NULL, 0);
        }
}

현재 클럭 코어 및 모든 연결된 하위 클럭 코어들에 대해 새 rate, 새 부모, 새 부모 등을 갱신하게 한다.

  • 코드 라인 6~8에서 현재 클럭 코어의 new_rate, new_parent, new_parent_index 값을 갱신한다.
  • 코드 라인 10~12에서 부모가 변경된 경우 new_child에 현재 클럭 코어를 대입한다. 그렇지 않은 경우 null을 대입한다.
  • 코드 라인 14~15에서 자식 클럭들 수 만큼 루프를 돌며 new_rate로 재계산하도록 한다.
  • 코드 라인 16에서 자식 노드에 대해 이 함수를 재귀 호출하여 계산하게 한다.

 

1개 클럭 rate 재산출(recalc)

clk_recalc()

drivers/clk/clk.c

static unsigned long clk_recalc(struct clk_core *core,
                                unsigned long parent_rate)
{
        unsigned long rate = parent_rate;

        if (core->ops->recalc_rate && !clk_pm_runtime_get(core)) {
                rate = core->ops->recalc_rate(core->hw, parent_rate);
                clk_pm_runtime_put(core);
        }
        return rate;
}

@parent_rate 값을 사용하여 클럭 hw의 (*recalc_rate) ops를 호출하여 현재 클럭 코어의 재계산한 rate를 반환한다.

 


Rate 조회

clk_get_rate()

drivers/clk/clk.c

/**
 * clk_get_rate - return the rate of clk
 * @clk: the clk whose rate is being returned
 *
 * Simply returns the cached rate of the clk, unless CLK_GET_RATE_NOCACHE flag
 * is set, which means a recalc_rate will be issued.
 * If clk is NULL then returns 0.
 */
unsigned long clk_get_rate(struct clk *clk)
{
        if (!clk)
                return 0;

        return clk_core_get_rate(clk->core);
}
EXPORT_SYMBOL_GPL(clk_get_rate);

클럭의 rate 값을 반환한다.

 

clk_core_get_rate()

drivers/clk/clk.c

static unsigned long clk_core_get_rate(struct clk_core *core)
{
        unsigned long rate;

        clk_prepare_lock();

        if (core && (core->flags & CLK_GET_RATE_NOCACHE))
                __clk_recalc_rates(core, 0);

        rate = clk_core_get_rate_nolock(core);
        clk_prepare_unlock();

        return rate;
}

클럭 코어의 rate 값을 반환한다.

  • CLK_GET_RATE_NOCACHE 플래그를 사용한 클럭 코어는 캐시된 rate 값이 아니라 재산출한 rate 값을 반환한다.

 

클럭 rate 재산출(recalc) – 클럭 조회 및 부모 클럭 변경 시

__clk_recalc_rates()

drivers/clk/clk.c

/**
 * __clk_recalc_rates
 * @clk: first clk in the subtree
 * @msg: notification type (see include/linux/clk.h)
 *
 * Walks the subtree of clks starting with clk and recalculates rates as it
 * goes.  Note that if a clk does not implement the .recalc_rate callback then
 * it is assumed that the clock will take on the rate of its parent.
 *
 * clk_recalc_rates also propagates the POST_RATE_CHANGE notification,
 * if necessary.
 */
static void __clk_recalc_rates(struct clk_core *core, unsigned long msg)
{
        unsigned long old_rate;
        unsigned long parent_rate = 0;
        struct clk_core *child;

        old_rate = core->rate;

        if (core->parent)
                parent_rate = core->parent->rate;

        core->rate = clk_recalc(core, parent_rate);

        /*
         * ignore NOTIFY_STOP and NOTIFY_BAD return values for POST_RATE_CHANGE
         * & ABORT_RATE_CHANGE notifiers
         */
        if (core->notifier_count && msg)
                __clk_notify(core, msg, old_rate, core->rate);

        hlist_for_each_entry(child, &core->children, child_node)
                __clk_recalc_rates(child, msg);
}

요청한 클럭 코어 및 모든 연결된 하위 클럭 코어들에 대해 rate를 재산출하여 갱신한다. 그리고 부모 클럭이 통지가 필요한 클럭 코어들에 @msg를 전달한다.

  • 코드 라인 7~10에서 현재 클럭 코어의 rate를 old_rate에 백업하고, 부모 클럭 코어의 rate도 parent_rate에 대입한다.
  • 코드 라인 12에서 부모 클럭 rate로 현재 클럭 코어의 rate를 재산출하여 반영한다.
  • 코드 라인 18~19에서 통지 대상 클럭 코어에 @msg를 통지하고, 결과 값은 무시한다.
    • rate 조회하는 clk_core_get_rate() 함수에서 이 함수를 호출한 경우 @msg에는 0이 전달되므로 통지하지 않는다.
  • 코드 라인 21~22에서 하위 클럭 코어들에 대해  이 함수를 재귀 호출하게 한다.

 

clk_core_get_rate_nolock()

drivers/clk/clk.c

static unsigned long clk_core_get_rate_nolock(struct clk_core *clk)
{
        if (!core)
                return 0;

        if (!core->num_parents || core->parent)
                return core->rate;

        /*
         * Clk must have a parent because num_parents > 0 but the parent isn't
         * known yet. Best to return 0 as the rate of this clk until we can
         * properly recalc the rate based on the parent's rate.
         */
        return 0;
}

클럭 코어의 rate 값을 반환한다.

  • 루트 클럭 코어의 경우 num_parents 값은 0이다.

 


부모 클럭 선택

부모 클럭을 변경한다는 것은 입력 클럭 소스가 바뀐다는 의미이고 gate된 상태가 아닌 상태에서 실시간으로 변경하는 경우 glitch가 발생됨을 유의해야 한다. glitch를 방지하려면 클럭 gate를 닫고 변경한 후 클럭 gate를 열어야야 한다. 클럭 코어에 CLK_SET_PARENT_GATE 플래그를 사용하면 gate된 상태에서 부모 클럭을 변경할 수 없게 할 수 있다.

 

다음 그림은 clk_set_parent() 함수 이후의 호출 관계를 보여준다.

 

clk_set_parent()

drivers/clk/clk.c

/**
 * clk_set_parent - switch the parent of a mux clk
 * @clk: the mux clk whose input we are switching
 * @parent: the new input to clk
 *
 * Re-parent clk to use parent as its new input source.  If clk is in
 * prepared state, the clk will get enabled for the duration of this call. If
 * that's not acceptable for a specific clk (Eg: the consumer can't handle
 * that, the reparenting is glitchy in hardware, etc), use the
 * CLK_SET_PARENT_GATE flag to allow reparenting only when clk is unprepared.
 *
 * After successfully changing clk's parent clk_set_parent will update the
 * clk topology, sysfs topology and propagate rate recalculation via
 * __clk_recalc_rates.
 *
 * Returns 0 on success, -EERROR otherwise.
 */
int clk_set_parent(struct clk *clk, struct clk *parent)
{
        int ret;

        if (!clk)
                return 0;

        clk_prepare_lock();

        if (clk->exclusive_count)
                clk_core_rate_unprotect(clk->core);

        ret = clk_core_set_parent_nolock(clk->core,
                                         parent ? parent->core : NULL);

        if (clk->exclusive_count)
                clk_core_rate_protect(clk->core);

        clk_prepare_unlock();

        return ret;
}
EXPORT_SYMBOL_GPL(clk_set_parent);

부모 클럭 코어를 선택한다. 성공 시 클럭 topology가 변경되며 rate 재산출이 일어난다. 성공 시 0을 반환한다.

  • 코드 라인 10~11에서 클럭 코어를 독점(exclusive)하여 관리하는 경우 parent 설정 전에 unprotect를 한다.
  • 코드 라인 13~14에서 부모 클럭 코어를 선택한다. (입력 클럭 소스 선택)
  • 코드 라인 16~17에서 클럭 코어를 독점(exclusive)하여 관리하는 경우 parent 설정이 완료되었으므로 다시 protect를 한다.

 

clk_core_set_parent_nolock()

drivers/clk/clk.c

static int clk_core_set_parent_nolock(struct clk_core *core,
                                      struct clk_core *parent)
{
        int ret = 0;
        int p_index = 0;
        unsigned long p_rate = 0;

        lockdep_assert_held(&prepare_lock);

        if (!core)
                return 0;

        if (core->parent == parent)
                return 0;

        /* verify ops for multi-parent clks */
        if (core->num_parents > 1 && !core->ops->set_parent)
                return -EPERM;

        /* check that we are allowed to re-parent if the clock is in use */
        if ((core->flags & CLK_SET_PARENT_GATE) && core->prepare_count)
                return -EBUSY;

        if (clk_core_rate_is_protected(core))
                return -EBUSY;

        /* try finding the new parent index */
        if (parent) {
                p_index = clk_fetch_parent_index(core, parent);
                if (p_index < 0) {
                        pr_debug("%s: clk %s can not be parent of clk %s\n",
                                        __func__, parent->name, core->name);
                        return p_index;
                }
                p_rate = parent->rate;
        }

        ret = clk_pm_runtime_get(core);
        if (ret)
                return ret;

        /* propagate PRE_RATE_CHANGE notifications */
        ret = __clk_speculate_rates(core, p_rate);

        /* abort if a driver objects */
        if (ret & NOTIFY_STOP_MASK)
                goto runtime_put;

        /* do the re-parent */
        ret = __clk_set_parent(core, parent, p_index);

        /* propagate rate an accuracy recalculation accordingly */
        if (ret) {
                __clk_recalc_rates(core, ABORT_RATE_CHANGE);
        } else {
                __clk_recalc_rates(core, POST_RATE_CHANGE);
                __clk_recalc_accuracies(core);
        }

runtime_put:
        clk_pm_runtime_put(core);

        return ret;
}

부모 클럭(입력 클럭 소스) 코어를 선택한다. 성공 시 연결된 모든 자식 클럭들의 rate를 재계산하고 0을 반환한다.

  • 코드 라인 10~11 이 함수는 재귀호출에서 사용되므로 클럭 코어가 지정되지 않으면 함수를 빠져나간다.
  • 코드 라인 13~14에서 요청한 부모 클럭(입력 클럭 소스)이 이미 지정되어 있었던 경우 변경할 필요가 없으므로 성공(0) 결과로 함수를 빠져나간다.
  • 코드 라인 17~18에서 2개 이상의 부모 클럭(입력 클럭 소스)을 가진 mux 타입 클럭 디바이스 드라이버의 ops->set_parent 후크가 구현되어 있지 않은 경우 -ENOSYS 에러를 반환한다.
  • 코드 라인 21~22에서 CLK_SET_PARENT_GATE 플래그를 사용한 경우 prepare 상태(클럭이 출력되는)의 클럭 코어는 glitch를 방지하기 위해 부모 클럭의 선택을 허락하지 않는다. 따라서 -EBUSY 에러를 반환한다.
  •  코드라인 24~25에서 protect 걸린 클럭 코어의 경우 -EBUSY 에러를 반환한다.
  • 코드 라인 28~36에서 부모 클럭(입력 클럭 소스)의 인덱스와 rate 값을 알아온다.
  • 코드 라인 38~40에서 절전 기능이 있는 클럭이 슬립된 상태이면 깨운다.
  • 코드 라인 47에서 현재 클럭 이하 연결된 모든 자식 클럭에 대해 PRE_RATE_CHANGE를 통지한다. 만일 결과가 NOTIFY_STOP 또는 NOTIFY_BAD인 경우 함수를 빠져나간다.
  • 코드 라인 50~58에서 부모 클럭(입력 클럭 소스)을 선택한다. 만일 에러가 발생한 경우 ABORT_RATE_CHANGE를 하위 노드에 전파한다. 성공한 경우에는 POST_RATE_CHANGE를 하위 노드에 전파한다. 전파 중에는 rate가 재계산된다. 성공 시 accuracy도 재산출한다.
  • 코드 라인 60~61에서 runtime_put: 레이블이다. 절전 기능이 있는 클럭의 경우 슬립이 필요하면 슬립시킨다.

 

__clk_set_parent()

drivers/clk/clk.c

static int __clk_set_parent(struct clk_core *core, struct clk_core *parent,
                            u8 p_index)
{
        unsigned long flags;
        int ret = 0;
        struct clk_core *old_parent;

        old_parent = __clk_set_parent_before(core, parent);

        trace_clk_set_parent(core, parent);

        /* change clock input source */
        if (parent && core->ops->set_parent)
                ret = core->ops->set_parent(core->hw, p_index);

        trace_clk_set_parent_complete(core, parent);

        if (ret) {
                flags = clk_enable_lock();
                clk_reparent(core, old_parent);
                clk_enable_unlock(flags);
                __clk_set_parent_after(core, old_parent, parent);

                return ret;
        }

        __clk_set_parent_after(core, parent, old_parent);

        return 0;
}

부모 클럭(입력 클럭 소스)을 선택한다. 성공한 경우 0을 반환한다.

  • 코드 라인 8에서 입력 클럭 소스(부모 클럭)를 선택하기 전에 처리할 일을 수행한다.
  • 코드 라인 13~25에서 mux 타입 클럭의 디바이스 드라이버에 구현된 ops->set_parent 후크 함수를 호출하여 실제 hw 기능으로 부모 클럭(입력 클럭 소스)을 선택하게 한다. 에러가 발생한 경우 기존 부모 클럭(입력 클럭 소스)로 재 변경한다.
  • 코드 라인 27에서 입력 클럭 소스(부모 클럭)을 선택한 후에 처리할 일을 수행한다.

 

__clk_set_parent_before()

drivers/clk/clk.c

static struct clk_core *__clk_set_parent_before(struct clk_core *core,
                                           struct clk_core *parent)
{
        unsigned long flags;
        struct clk_core *old_parent = core->parent;

        /*
         * 1. enable parents for CLK_OPS_PARENT_ENABLE clock
         *
         * 2. Migrate prepare state between parents and prevent race with
         * clk_enable().
         *
         * If the clock is not prepared, then a race with
         * clk_enable/disable() is impossible since we already have the
         * prepare lock (future calls to clk_enable() need to be preceded by
         * a clk_prepare()).
         *
         * If the clock is prepared, migrate the prepared state to the new
         * parent and also protect against a race with clk_enable() by
         * forcing the clock and the new parent on.  This ensures that all
         * future calls to clk_enable() are practically NOPs with respect to
         * hardware and software states.
         *
         * See also: Comment for clk_set_parent() below.
         */

        /* enable old_parent & parent if CLK_OPS_PARENT_ENABLE is set */
        if (core->flags & CLK_OPS_PARENT_ENABLE) {
                clk_core_prepare_enable(old_parent);
                clk_core_prepare_enable(parent);
        }

        /* migrate prepare count if > 0 */
        if (core->prepare_count) {
                clk_core_prepare_enable(parent);
                clk_core_enable_lock(core);
        }

        /* update the clk tree topology */
        flags = clk_enable_lock();
        clk_reparent(core, parent);
        clk_enable_unlock(flags);

        return old_parent;
}

입력 클럭 소스(부모 클럭)를 선택하기 전에 처리할 일을 수행한다. 결과로 기존 부모 클럭을 반환한다.

  • 코드 라인 28~31에서 현재 클럭 코어에서 CLK_OPS_PARENT_ENABLE 플래그를 사용한 경우 기존 부모 클럭과 새 부모 클럭을 prepare하고 enable한다.
  • 코드 라인 34~37에서 현재 클럭 코어가 prepare 상태면 부모 클럭 코어도 prepare 및 enable 한다.
  • 코드 라인 40~44에서 클럭 topology를 갱신하고 기존 부모 클럭 코어를 반환한다.

 

__clk_set_parent_after()

drivers/clk/clk.c

static void __clk_set_parent_after(struct clk_core *core,
                                   struct clk_core *parent,
                                   struct clk_core *old_parent)
{
        /*
         * Finish the migration of prepare state and undo the changes done
         * for preventing a race with clk_enable().
         */
        if (core->prepare_count) {
                clk_core_disable_lock(core);
                clk_core_disable_unprepare(old_parent);
        }

        /* re-balance ref counting if CLK_OPS_PARENT_ENABLE is set */
        if (core->flags & CLK_OPS_PARENT_ENABLE) {
                clk_core_disable_unprepare(parent);
                clk_core_disable_unprepare(old_parent);
        }
}

입력 클럭 소스(부모 클럭)를 선택한 후에 처리할 일을 수행한다.

  • 코드 라인 9~12에서 클럭 코어가 prepare 상태인 경우 disable하고, 부모 클럭을 unprepare 상태로 변경한다.
  • 코드 라인 15~18에서 현재 클럭 코어에서 CLK_OPS_PARENT_ENABLE 플래그를 사용한 경우 기존 부모 클럭과 새 부모 클럭을 disable 하고 unprepare 한다.

 

clk_reparent()

drivers/clk/clk.c

static void clk_reparent(struct clk_core *core, struct clk_core *new_parent)
{
        bool was_orphan = core->orphan;

        hlist_del(&core->child_node);

        if (new_parent) {
                bool becomes_orphan = new_parent->orphan;

                /* avoid duplicate POST_RATE_CHANGE notifications */
                if (new_parent->new_child == core)
                        new_parent->new_child = NULL;

                hlist_add_head(&core->child_node, &new_parent->children);

                if (was_orphan != becomes_orphan)
                        clk_core_update_orphan_status(core, becomes_orphan);
        } else {
                hlist_add_head(&core->child_node, &clk_orphan_list);
                if (!was_orphan)
                        clk_core_update_orphan_status(core, true);
        }

        core->parent = new_parent;
}

clock tree 토플로지를 갱신한다.

  • 코드 라인 5에서 부모 클럭 코어(입력 클럭 소스)의 child_node에서 현재 클럭 코어를 제거한다.
  • 코드 라인 7~17에서 새 부모 클럭(입력 클럭 소스)이 지정된 경우 그 부모 클럭의 children으로 추가한다. 만일 새 부모 클럭의 new_child에 현재 클럭이 지정되어 있었던 경우라면 new_child에 null을 대입한다.
  • 코드 라인 18~22에서 새 부모 클럭이 지정되지 않은 경우 고아 리스트에 현재 클럭을 추가한다.
  • 코드 라인 24에서 현재 클럭 코어의 부모를 갱신한다.

 


Mux 클럭 rate 관련 ops

(*determine_rate) 후크 함수

mux 타입 클럭의 (*determine_rate) 후크 함수에서 사용되는 아래 함수를 알아본다.

 

clk_mux_determine_rate()

drivers/etc/clk-mux.c

static int clk_mux_determine_rate(struct clk_hw *hw,
                                  struct clk_rate_request *req)
{
        struct clk_mux *mux = to_clk_mux(hw);

        return clk_mux_determine_rate_flags(hw, req, mux->flags);
}

요청한 rate에 대해 mux 클럭 hw가 지원하는 가장 근접한 rate를 산출한다. 성공 시 0을 반환하고, req->rate 및 req->best_parent_rate에 산출된 rate가 저장되고, req->best_parent_rate에 산출된 rate와 관련된 부모 클럭 hw가 저장된다.

 

clk_mux_determine_rate_flags()

drivers/etc/clk-mux.c

int clk_mux_determine_rate_flags(struct clk_hw *hw,
                                 struct clk_rate_request *req,
                                 unsigned long flags)
{
        struct clk_core *core = hw->core, *parent, *best_parent = NULL;
        int i, num_parents, ret;
        unsigned long best = 0;
        struct clk_rate_request parent_req = *req;

        /* if NO_REPARENT flag set, pass through to current parent */
        if (core->flags & CLK_SET_RATE_NO_REPARENT) {
                parent = core->parent;
                if (core->flags & CLK_SET_RATE_PARENT) {
                        ret = __clk_determine_rate(parent ? parent->hw : NULL,
                                                   &parent_req);
                        if (ret)
                                return ret;

                        best = parent_req.rate;
                } else if (parent) {
                        best = clk_core_get_rate_nolock(parent);
                } else {
                        best = clk_core_get_rate_nolock(core);
                }

                goto out;
        }

        /* find the parent that can provide the fastest rate <= rate */
        num_parents = core->num_parents;
        for (i = 0; i < num_parents; i++) {
                parent = clk_core_get_parent_by_index(core, i);
                if (!parent)
                        continue;

                if (core->flags & CLK_SET_RATE_PARENT) {
                        parent_req = *req;
                        ret = __clk_determine_rate(parent->hw, &parent_req);
                        if (ret)
                                continue;
                } else {
                        parent_req.rate = clk_core_get_rate_nolock(parent);
                }

                if (mux_is_better_rate(req->rate, parent_req.rate,
                                       best, flags)) {
                        best_parent = parent;
                        best = parent_req.rate;
                }
        }

        if (!best_parent)
                return -EINVAL;

out:
        if (best_parent)
                req->best_parent_hw = best_parent->hw;
        req->best_parent_rate = best;
        req->rate = best;

        return 0;
}
EXPORT_SYMBOL_GPL(clk_mux_determine_rate_flags);

요청한 rate에 대해 mux 클럭 hw가 지원하는 가장 근접한 rate를 산출한다. 성공 시 0을 반환하고, req->rate 및 req->best_parent_rate에 산출된 rate가 저장되고, req->best_parent_rate에 산출된 rate와 관련된 부모 클럭 hw가 저장된다.

  • 코드 라인 8에서 기존 요청들을 parent_req에 백업해둔다.
  • 코드 라인 11~27에서 rate 변경 시 부모 클럭(입력 소스)이 변경되지 않는 클럭 코어인 경우 다음 값으로 pass through 처리한다.
    • 부모 클럭 먼저 rate 변경하게 요청한 경우 부모 클럭에서 먼저 가장 근접한 rate
    • 부모 클럭의 rate
    • 부모 클럭이 없으면 현재 클럭 코어의 rate
  • 코드 라인 30~50에서 부모 클럭 수만큼 순회하며 요청 rate에 대해 mux 클럭 hw가 지원하는 가장 근접한 rate를 산출하기 위해 다음과 같이 처리하고, 이들 중 가장 적합한 rate를 선택한다.
    • 부모 클럭 중에 더 상위 부모 클럭 먼저 rate 변경하게 요청한 경우 부모 클럭에서 먼저 가장 근접한 rate
    • 부모 클럭의 rate
  • 코드 라인 52~53에서 어떠한 부모 클럭도 요청을 만족하지 못한 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 55~61에서 out: 레이블은 가장 근접한 rate를 찾은 경우이다. 성공 값 0을 반환하고, req->rate 및 req->best_parent_rate에 산출된 rate가 저장하고, req->best_parent_rate에 산출된 rate와 관련된 부모 클럭 hw를 저장한다.

 

mux 타입 rate 설정 API

__clk_mux_determine_rate()

drivers/clk/clk.c

/*
 * __clk_mux_determine_rate - clk_ops::determine_rate implementation for a mux type clk
 * @hw: mux type clk to determine rate on
 * @req: rate request, also used to return preferred parent and frequencies
 *
 * Helper for finding best parent to provide a given frequency. This can be used
 * directly as a determine_rate callback (e.g. for a mux), or from a more
 * complex clock that may combine a mux with other operations.
 *
 * Returns: 0 on success, -EERROR value on error
 */
int __clk_mux_determine_rate(struct clk_hw *hw,
                             struct clk_rate_request *req)
{
        return clk_mux_determine_rate_flags(hw, req, 0);
}
EXPORT_SYMBOL_GPL(__clk_mux_determine_rate);

mux 타입 클럭 디바이스 드라이버의 ops->determine_rate에서 호출되는 콜백함수로도 사용된다. 요청한 rate 이하 값으로 가장 가까운 rate를 구한다. req->best_parent_rate에 산출한 최적의 rate가 담기고 req->best_parent_hw는 산출된 최적의 부모 클럭 hw를 가리킨다. 단 req->min_rate ~ req->max_rate 범위를 초과하는 경우 0을 반환한다. 디폴트 플래그로 0을 사용한다.

 

__clk_mux_determine_rate_closest()

drivers/clk/clk.c

int __clk_mux_determine_rate_closest(struct clk_hw *hw,
                                     struct clk_rate_request *req)
{
        return clk_mux_determine_rate_flags(hw, req, CLK_MUX_ROUND_CLOSEST);
}
EXPORT_SYMBOL_GPL(__clk_mux_determine_rate_closest);

요청한 rate에 가장 가까운 rate를 구한다. req->best_parent_rate에 산출한 최적의 rate가 담기고 req->best_parent_hw는 산출된 최적의 부모 클럭hw를 가리킨다. 단 req->min_rate ~ req->max_rate 범위를 초과하는 경우 0을 반환한다. 디폴트 플래그로 CLK_MUX_ROUND_CLOSEST을 사용한다.

 

mux_is_better_rate()

drivers/clk/clk.c

static bool mux_is_better_rate(unsigned long rate, unsigned long now,
                           unsigned long best, unsigned long flags)
{
        if (flags & CLK_MUX_ROUND_CLOSEST)
                return abs(now - rate) < abs(best - rate);

        return now <= rate && now > best;
}

mux가 설정하고자 하는 rate 값에 now 값이 best 값보다 더 적절한 경우 true(1)를 반환한다. CLK_MUX_ROUND_CLOSEST 플래그의 사용 여부에 따라 적절한 값의 여부 판단이 바뀐다.

  • 사용하지 않는 경우 rate 이하 범위에서 now 값이 best 값 보다 더 가까운 경우 true(1)
  • 사용하는 경우 요청 rate에 now 값이 best 보다 더 가까운 경우 true(1)

 

 


Fixed Factor 클럭 rate 관련 ops

 

(*round_rate) 후크 함수

fixed factor 타입 클럭의 (*round_rate) 후크 함수에서 사용되는 아래 함수를 알아본다.

 

clk_factor_round_rate()

drivers/etc/clk-fixed-factor.c

static long clk_factor_round_rate(struct clk_hw *hw, unsigned long rate,
                                unsigned long *prate)
{
        struct clk_fixed_factor *fix = to_clk_fixed_factor(hw);

        if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
                unsigned long best_parent;

                best_parent = (rate / fix->mult) * fix->div;
                *prate = clk_hw_round_rate(clk_hw_get_parent(hw), best_parent);
        }

        return (*prate / fix->div) * fix->mult;
}

요청한 @rate에 대해 fixed factor 타입 클럭 hw가 지원하는 가장 근접한 rate를 산출한다. 성공 시 rate를 반환하고, *prate에는 부모 클럭의 rate를 저장해온다.

  • 코드 라인 6~11에서 부모 클럭 코어의 rate를 먼저 설정해야 하는 클럭 코어인 경우 변경을 원하는 rate에 따른 부모 클럭 rate를 산출하여 출력 인자 prate에 저장한다.
    • 예) 부모 클럭이 1Mhz였고, 하위 divider 클럭이 1/2를 적용하여 500Khz였는데, 원하는 클럭이 600Khz인 경우 가능하면 부모 클럭을 1.2Mhz로 변경한다.
  • 코드 라인 13에서  부모 클럭 rate로부터 factor 비율을 적용한 rate를 반환한다.
    • 예) 부모 클럭 rate가 10Mhz, fix->div=3, fix->mult=2인 경우
      • 10000000 / 3 * 2 = 6666666

 


클럭 드라이버 샘플

 

클럭 디바이스 트리

샘플에 사용한 디바이스 트리의 추가된 6개의 클럭 노드 내용은 다음과 같다.

  • fooclk1 (fixed-rate)
    • 1mhz 고정 rate
  • fooclk2 (divider)
    • fooclk1의 1mhz를 1~32 분주하여 사용한다.
  • fooclk3 (divider)
    • fooclk2에서 분주된 클럭을 1~8 분주하여 사용한다.
    • 원하는 rate 설정이 안되면 부모 클럭 rate도 변경한다.
  • fooclk4 (divider)
    • fooclk2에서 분주된 클럭을 1,2,4,8,16 분주하여 사용한다.
  • fooclk5 (mux)
    • fooclk1, fooclk2, fooclk3, fooclk4 클럭 중 하나를 선택하여 사용한다.
  • foo
    • 사용자 디바이스용으로 위의 클럭 5개를 사용할 수 있게하였다.
        fooclk1 {
                phandle = <0x8100>;
                clock-output-names = "fooclk1";
                clock-frequency = <1000000>;
                #clock-cells = <0x0>;
                compatible = "fixed-clock";
        };

        fooclk2 {
                phandle = <0x8200>;
                clock-output-names = "fooclk2";
                clocks = <0x8100>;
                #clock-cells = <0x0>;
                compatible = "foo,divider-clock";
                foo,max-div = <32>;
        };

        fooclk3 {
                phandle = <0x8300>;
                clock-output-names = "fooclk3";
                clocks = <0x8200>;
                #clock-cells = <0x0>;
                compatible = "foo,divider-clock";
                foo,max-div = <8>;
                foo,set-rate-parent;
        };

        fooclk4 {
                phandle = <0x8400>;
                clock-output-names = "fooclk4";
                clocks = <0x8200>;
                #clock-cells = <0x0>;
                compatible = "foo,divider-clock";
                foo,max-div = <5>;
                foo,index-power-of-two;
        };

        fooclk5 {
                phandle = <0x8500>;
                clock-output-names = "fooclk5";
                clocks = <0x8100 0x8200 0x8300 0x8400>;
                #clock-cells = <0x0>;
                compatible = "foo,mux-clock";
        };

        foo {
                compatible = "foo,foo";
                clock-names = "fooclk1", "fooclk2", "fooclk3", "fooclk4", "fooclk5";
                clocks = <0x8100 0x8200 0x8300 0x8400 0x8500>;
        };

 

rate  초기 상태

다음 그림은 5 개의 클럭이 처음 초기화된 상태를 보여준다.

  • 모든 분배기(divider)들이 1:1로 동작하고 있고, mux 클럭은 0번 입력으로 초기화된 상태이다.

 

다음은 5개의 클럭에 대해 prepare 및 enable한 상태이고, cat /sys/kernel/debug/clk/clk_summary 명령을 통해 확인한 결과이다.

$ insmod clk.ko
clk: loading out-of-tree module taints kernel.
foo: foo_probe
foo: devm_clk_get() clk1
foo: devm_clk_get() clk2
foo: devm_clk_get() clk3
foo: devm_clk_get() clk4
foo: devm_clk_get() clk5
foo: clk_prepare() clk1 rc=0
foo: clk_prepare() clk2 rc=0
foo: clk_prepare() clk3 rc=0
foo: clk_prepare() clk4 rc=0
foo: clk_prepare() clk5 rc=0
foo: clk_enable() clk1 rc=0
foo: clk_enable() clk2 rc=0
foo: clk_enable() clk3 rc=0
foo: clk_enable() clk4 rc=0
foo: clk_enable() clk5 rc=0
$ cat /sys/kernel/debug/clk/clk_summary
                                 enable  prepare  protect                                duty
   clock                          count    count    count        rate   accuracy phase  cycle
---------------------------------------------------------------------------------------------
 fooclk1                              0        0        0     1000000          0     0  50000
    fooclk5                           0        0        0     1000000          0     0  50000
    fooclk2                           0        0        0     1000000          0     0  50000
       fooclk4                        0        0        0     1000000          0     0  50000
       fooclk3                        0        0        0     1000000          0     0  50000
 clk24mhz                             4        5        0    24000000          0     0  50000

 

rate  설정 -1

다음 그림은 clk2, clk3, 및 clk4의 rate를 초기값으로 설정하고, clk5의 입력을 2번으로 선택한 상태이다.

 

다음은 각 클럭을 설정하는 과정을 보여준다.

$ echo 40000 > /sys/bus/platform/drivers/foo/foo2
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=25
foo_clk_divider_round_rate: rate=40000, prate=1000000, round=40000
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=25
foo_clk_divider_round_rate: rate=40000, prate=1000000, round=40000
foo_clk_divider_recalc_rate: parent_rate=40000
foo_readl: val=0
foo_clk_divider_recalc_rate: parent_rate=40000, round=40000
foo_clk_divider_recalc_rate: parent_rate=40000
foo_readl: val=0
foo_clk_divider_recalc_rate: parent_rate=40000, round=40000
foo_clk_divider_set_rate: rate=40000, parent_rate=1000000
foo_readl: val=0
foo_writel: val=24
foo_clk_divider_recalc_rate: parent_rate=1000000
foo_readl: val=24
foo_clk_divider_recalc_rate: parent_rate=1000000, round=40000
foo_clk_divider_set_rate: rate=40000, parent_rate=40000
foo_readl: val=0
foo_writel: val=0
foo_clk_divider_recalc_rate: parent_rate=40000
foo_readl: val=0
foo_clk_divider_recalc_rate: parent_rate=40000, round=40000
foo_clk_divider_set_rate: rate=40000, parent_rate=40000
foo_readl: val=0
foo_writel: val=0
foo_clk_divider_recalc_rate: parent_rate=40000
foo_readl: val=0
foo_clk_divider_recalc_rate: parent_rate=40000, round=40000
foo foo: clk_set_rate() clk2 val=40000 rc=0
$ echo 10000 > /sys/bus/platform/drivers/foo/foo3
foo_clk_divider_bestdiv: maxdiv=8
foo_clk_divider_bestdiv: maxdiv2=8
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=32
foo_clk_divider_round_rate: rate=10000, prate=1000000, round=31250
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=32
foo_clk_divider_round_rate: rate=20001, prate=1000000, round=31250
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=32
foo_clk_divider_round_rate: rate=30002, prate=1000000, round=31250
foo_clk_divider_round_rate: rate=10000, prate=40000, round=10000
foo_clk_divider_bestdiv: maxdiv=8
foo_clk_divider_bestdiv: maxdiv2=8
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=32
foo_clk_divider_round_rate: rate=10000, prate=1000000, round=31250
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=32
foo_clk_divider_round_rate: rate=20001, prate=1000000, round=31250
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=32
foo_clk_divider_round_rate: rate=30002, prate=1000000, round=31250
foo_clk_divider_round_rate: rate=10000, prate=40000, round=10000
foo_clk_divider_set_rate: rate=10000, parent_rate=40000
foo_readl: val=0
foo_writel: val=3
foo_clk_divider_recalc_rate: parent_rate=40000
foo_readl: val=3
foo_clk_divider_recalc_rate: parent_rate=40000, round=10000
foo foo: clk_set_rate() clk3 val=10000 rc=0
$ echo 5000 > /sys/bus/platform/drivers/foo/foo4
foo_clk_divider_bestdiv: maxdiv=8
foo_clk_divider_bestdiv: bestdiv=8
foo_clk_divider_round_rate: rate=5000, prate=40000, round=5000
foo_clk_divider_bestdiv: maxdiv=8
foo_clk_divider_bestdiv: bestdiv=8
foo_clk_divider_round_rate: rate=5000, prate=40000, round=5000
foo_clk_divider_set_rate: rate=5000, parent_rate=40000
foo_readl: val=0
foo_writel: val=3
foo_clk_divider_recalc_rate: parent_rate=40000
foo_readl: val=3
foo_clk_divider_recalc_rate: parent_rate=40000, round=5000
foo foo: clk_set_rate() clk4 val=5000 rc=0
foo_clk_divider_bestdiv: maxdiv=8
foo_clk_divider_bestdiv: bestdiv=8
foo_clk_divider_round_rate: rate=5000, prate=40000, round=5000
foo foo: clk_set_rate() clk4 val=5000 rc=0
$ echo 2 > /sys/bus/platform/drivers/foo/foo5
foo_clk_mux_set_parent: index=2
foo_readl: val=0
foo_writel: val=2
foo_clk_mux_set_parent: index2=2, val=2
foo foo: clk_set_parent() val=2, select=clk3 rc=0
$ cat /sys/kernel/debug/clk/clk_summary
                                 enable  prepare  protect                                duty
   clock                          count    count    count        rate   accuracy phase  cycle
---------------------------------------------------------------------------------------------
 fooclk1                              2        2        0     1000000          0     0  50000
    fooclk2                           3        3        0       40000          0     0  50000
       fooclk4                        1        1        0        5000          0     0  50000
       fooclk3                        2        2        0       10000          0     0  50000
          fooclk5                     1        1        0       10000          0     0  50000
 clk24mhz                             4        5        0    24000000          0     0  50000

 

rate  설정-2

다음 그림은 clk3의 rate를 25khz로 변경된 모습을 보여준다.

  • clk3 자체만으로 rate 설정이 불가능하여 부모 클럭인 clk2의 rate도 변경하였다. 그 후 clk4 및 clk5의 rate에도 영향을 끼친 것을 확인할 수 있다.

 

다음은 clk3의 rate를 25khz로 변경하는 과정을 보여준다.

$ echo 25000 > /sys/bus/platform/drivers/foo/foo3
foo_clk_divider_bestdiv: maxdiv=8
foo_clk_divider_bestdiv: maxdiv2=8
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=32
foo_clk_divider_round_rate: rate=25000, prate=1000000, round=31250
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=20
foo_clk_divider_round_rate: rate=50001, prate=1000000, round=50000
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=14
foo_clk_divider_round_rate: rate=75002, prate=1000000, round=71429
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=10
foo_clk_divider_round_rate: rate=100003, prate=1000000, round=100000
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=8
foo_clk_divider_round_rate: rate=125004, prate=1000000, round=125000
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=7
foo_clk_divider_round_rate: rate=150005, prate=1000000, round=142858
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=6
foo_clk_divider_round_rate: rate=175006, prate=1000000, round=166667
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=5
foo_clk_divider_round_rate: rate=200007, prate=1000000, round=200000
foo_clk_divider_bestdiv: bestdiv2=2, best_parent_rate=50000
foo_clk_divider_round_rate: rate=25000, prate=50000, round=25000
foo_clk_divider_bestdiv: maxdiv=8
foo_clk_divider_bestdiv: maxdiv2=8
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=32
foo_clk_divider_round_rate: rate=25000, prate=1000000, round=31250
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=20
foo_clk_divider_round_rate: rate=50001, prate=1000000, round=50000
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=14
foo_clk_divider_round_rate: rate=75002, prate=1000000, round=71429
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=10
foo_clk_divider_round_rate: rate=100003, prate=1000000, round=100000
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=8
foo_clk_divider_round_rate: rate=125004, prate=1000000, round=125000
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=7
foo_clk_divider_round_rate: rate=150005, prate=1000000, round=142858
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=6
foo_clk_divider_round_rate: rate=175006, prate=1000000, round=166667
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=5
foo_clk_divider_round_rate: rate=200007, prate=1000000, round=200000
foo_clk_divider_bestdiv: bestdiv2=2, best_parent_rate=50000
foo_clk_divider_round_rate: rate=25000, prate=50000, round=25000
foo_clk_divider_bestdiv: maxdiv=32
foo_clk_divider_bestdiv: bestdiv=20
foo_clk_divider_round_rate: rate=50000, prate=1000000, round=50000
foo_clk_divider_recalc_rate: parent_rate=50000
foo_readl: val=3
foo_clk_divider_recalc_rate: parent_rate=50000, round=6250
foo_clk_divider_recalc_rate: parent_rate=50000
foo_readl: val=3
foo_clk_divider_recalc_rate: parent_rate=50000, round=12500
foo_clk_divider_set_rate: rate=50000, parent_rate=1000000
foo_readl: val=24
foo_writel: val=19
foo_clk_divider_recalc_rate: parent_rate=1000000
foo_readl: val=19
foo_clk_divider_recalc_rate: parent_rate=1000000, round=50000
foo_clk_divider_set_rate: rate=6250, parent_rate=50000
foo_readl: val=3
foo_writel: val=3
foo_clk_divider_recalc_rate: parent_rate=50000
foo_readl: val=3
foo_clk_divider_recalc_rate: parent_rate=50000, round=6250
foo_clk_divider_set_rate: rate=25000, parent_rate=50000
foo_readl: val=3
foo_writel: val=1
foo_clk_divider_recalc_rate: parent_rate=50000
foo_readl: val=1
foo_clk_divider_recalc_rate: parent_rate=50000, round=25000
foo foo: clk_set_rate() clk3 val=25000 rc=0
$ cat /sys/kernel/debug/clk/clk_summary
                                 enable  prepare  protect                                duty
   clock                          count    count    count        rate   accuracy phase  cycle
---------------------------------------------------------------------------------------------
 fooclk1                              2        2        0     1000000          0     0  50000
    fooclk2                           3        3        0       50000          0     0  50000
       fooclk4                        1        1        0        6250          0     0  50000
       fooclk3                        2        2        0       25000          0     0  50000
          fooclk5                     1        1        0       25000          0     0  50000
 clk24mhz                             4        5        0    24000000          0     0  50000

 

샘플 클럭 드라이버

 

참고

 

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다