Common Clock Framework -2- (APIs)

 

Common Clock Framework -2- (APIs)

클럭 등록 시 사용하는 플래그

다음의 플래그들은 최상위 framework인 common clock framework에서 유효하다.

include/linux/clk-provider.h

/*
 * flags used across common struct clk.  these flags should only affect the
 * top-level framework.  custom flags for dealing with hardware specifics
 * belong in struct clk_foo
 */
#define CLK_SET_RATE_GATE       BIT(0) /* must be gated across rate change */
#define CLK_SET_PARENT_GATE     BIT(1) /* must be gated across re-parent */
#define CLK_SET_RATE_PARENT     BIT(2) /* propagate rate change up one level */
#define CLK_IGNORE_UNUSED       BIT(3) /* do not gate even if unused */
#define CLK_IS_ROOT             BIT(4) /* root clk, has no parent */
#define CLK_IS_BASIC            BIT(5) /* Basic clk, can't do a to_clk_foo() */
#define CLK_GET_RATE_NOCACHE    BIT(6) /* do not use the cached clk rate */
#define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */
#define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */
  • CLK_SET_RATE_GATE
    • rate 변경 시 반드시 unprepare 되어 있어야 한다. (gate가 닫혀 있어야 한다)
  • CLK_SET_PARENT_GATE
    • 입력 클럭 소스(부모 클럭)를 선택 시 반드시 unprepare 되어 있어야 한다. (gate가 닫혀 있어야 한다)
  • CLK_SET_RATE_PARENT
    • rate 변경 시 부모에 전파(propogation) 하여 부모 클럭에서 rate를 변경하게 한다.
  • CLK_IGNORE_UNUSED
    • 사용하지 않아도 gate를 닫지 않는다.
  • CLK_IS_ROOT
    • 루트 클럭으로 부모가 없다.
  • CLK_IS_BASIC
    • clk_foo()와 같은 파생 클럭이 아닌 클럭이다.
    • common clock framework에 구현되어 있는 8개의 클럭 디바이스 드라이버는 모두 CLK_IS_BASIC 플래그가 설정되어 있다.
  • CLK_GET_RATE_NOCACHE
    • 캐시된 clock rate를 사용하지 못한다.
  • CLK_SET_RATE_NO_REPARENT
    • mux 기능 사용 시 rate 변경 시 부모 클럭을 선택하지 못하는 상태에서 현재 연결된 부모 클럭에게 rate 변경 요청을 하게한다.
  • CLK_GET_ACCURACY_NOCACHE
    • 캐시된 accuracy를 사용하지 못한다.

rate 설정 -1- (set_rate)

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();
 
        ret = clk_core_set_rate_nolock(clk->core, rate);
        
        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 *clk,
                                    unsigned long req_rate)
{
        struct clk_core *top, *fail_clk;
        unsigned long rate = req_rate;
        int ret = 0;

        if (!clk)
                return 0;

        /* bail early if nothing to do */
        if (rate == clk_core_get_rate_nolock(clk))
                return 0;

        if ((clk->flags & CLK_SET_RATE_GATE) && clk->prepare_count)
                return -EBUSY;

        /* calculate new rates and get the topmost changed clock */
        top = clk_calc_new_rates(clk, 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;

        return ret;
}

클럭의 rate 설정을 한다. 성공 시에 0을 반환한다.

  • 코드 라인 12~13에서 클럭의 rate 값을 반환한다. rate 값이 기존 값과 변화가 없으면 0을 반환한다.
  • 코드 라인 15~16에서 CLK_SET_RATE_GATE 플래그 설정된 경우이며넛 prepare_count가 있으면 -EBUSY 에러를 반환한다.
  • 코드 라인 19~21에서 rate가 적용될 상위 클럭부터 new rate가 적용되고 하위 클럭들이 재계산된다. 적용된 상위 클럭을 알아온다.
    • CLK_SET_RATE_PARENT 플래그가 사용된 클럭들은 그 상위 클럭에서 rate를 결정한다.
    • rate를 결정하는 클럭의 타입에 따라 다음과 같이 재계산된다.
      • Mux 타입
        • determine_rate() 함수를 사용하여 어떤 부모 클럭을 사용해야 요청한 노드의 rate에 인접한 값이 나올 수 있는지 계산된다.
      • 그 외 배율 조절 타입
        • round_rate() 함수를 사용하여 어떤 배율의 클럭을 사용해야 요청한 노드의 rate에 인접한 값이 나올 수 있는지 계산된다.
  • 코드 라인 24~30에서 PRE_RATE_CHANGE 상태 를 통지 받아야 하는 클럭 노드들에 전달하여 클럭 rate 재설정을 준비한다. 만일 실패하는 경우 ABORT_RATE_CHANGE를 다시 통지하여 rollback을 알린다.
    • 상위 부모 클럭까지 rate를 설정하게 할 때 상위 전달(propagation) 과정에서 notify되는 함수에서 해당 클럭의 설정을 허용할지 여부를 결정하게 한다.
  • 코드 라인 33에서 적용될 상위 클럭부터 하위 방향 까지 자식 노드로 연결된 노드들을 재계산하여 rate를 적용한다. 이 때 POST_RATE_CHANGE를 다시 통지하여 commit한다.

 

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

 

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

 

clk_core_get_rate_nolock()

drivers/clk/clk.c

static unsigned long clk_core_get_rate_nolock(struct clk_core *clk)
{
        unsigned long ret;

        if (!clk) {
                ret = 0;
                goto out;
        }

        ret = clk->rate;

        if (clk->flags & CLK_IS_ROOT)
                goto out;

        if (!clk->parent)
                ret = 0;

out:
        return ret;
}

클럭의 rate 값을 반환한다. 만일 루트 클럭이 아닌데 아직 부모 설정되지 않은 경우 0을 반환한다.

 

rate 설정 -2- (new rates 산출)

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 *clk,
                                           unsigned long rate)
{
        struct clk_core *top = clk;
        struct clk_core *old_parent, *parent;
        struct clk_hw *parent_hw;
        unsigned long best_parent_rate = 0;
        unsigned long new_rate;
        unsigned long min_rate;
        unsigned long max_rate;
        int p_index = 0;

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

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

        clk_core_get_boundaries(clk, &min_rate, &max_rate);

        /* find the closest rate and parent clk/rate */
        if (clk->ops->determine_rate) {
                parent_hw = parent ? parent->hw : NULL;
                new_rate = clk->ops->determine_rate(clk->hw, rate,
                                                    min_rate,
                                                    max_rate,
                                                    &best_parent_rate,
                                                    &parent_hw);
                parent = parent_hw ? parent_hw->core : NULL;
        } else if (clk->ops->round_rate) {
                new_rate = clk->ops->round_rate(clk->hw, rate,
                                                &best_parent_rate);
                if (new_rate < min_rate || new_rate > max_rate)
                        return NULL;
        } else if (!parent || !(clk->flags & CLK_SET_RATE_PARENT)) {
                /* pass-through clock without adjustable parent */
                clk->new_rate = clk->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 &&
            (clk->flags & CLK_SET_PARENT_GATE) && clk->prepare_count) {
                pr_debug("%s: %s not gated but wants to reparent\n",
                         __func__, clk->name);
                return NULL;
        }

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

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

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

        return top;
}

최상위 클럭의 rate 값이 바뀌어서 현재 클럭 rate로 변경될 값을 알아온다.

  • 코드 라인 22에서 요청 클럭의 부모 클럭을 알아와서 parent 및 old_parent에 보관한다.
  • 코드 라인 23~24에서 부모 클럭이 존재하는 경우 부모 클럭의 rate를 best_parent_rate에 보관한다.
  • 코드 라인 26에서 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.
  • 코드 라인 29~36에서 mux h/w 드라이버의 ops->determine_rate 콜백 함수를 호출하여 실제 클럭 hw가 설정할 수 있는 최적의 new_rate를 알아온다.
    • 참고: drivers/clk/ti/mux.c – __clk_mux_determine_rate()
  • 코드 라인 37~41에서 배율이 변경될 수 있는 클럭(divide, fractional, fixed factor) h/w 드라이버의 ops->round_rate 콜백 함수를 호출하여 실제 클럭 hw가 설정할 수 있는 최적의 new_rate를 알아온다.
    • 참고: drivers/clk/ti/divider.c – ti_clk_divider_round_rate()
  • 코드 라인 42~45에서 부모 클럭이 없거나 CLK_SET_RATE_PARENT 플래그가 없는 경우 현재 클럭의 rate를 new_rate로 사용한다.
  • 코드 라인 46~51에서 부모 클럭의 rate를 산출하기 위해 인수로 부모 클럭과 요청 rate 값을 가지고 이 함수를 재귀호출하여 new_rate를 알아온다.
    • ops->determine_rate 및 ops->round_rate가 없는 gate 타입의 클럭을 사용하는 경우 CLK_SET_RATE_PARENT 플래그가 사용되지 않을 때까지 상위 클럭으로 이동한다.
  • 코드 라인 54~59에서 mux 타입 클럭에서 rate 변경 요청으로 인해 부모 클럭의 변경이 필요한 상태이며 현재 클럭에 CLK_SET_PARENT_GATE 플래그가 설정된 경우일 때 gate가 열린 상태이면 null을 반환한다.
    • CLK_SET_PARENT_GATE 플래그 옵션을 사용하는 mux인 경우 gate를 닫지 않으면 mux에서 부모 클럭의 변경이 실패한다.
  • 코드 라인 62~69에서 mux 타입 클럭에서 부모 클럭이 2개 이상이면 현재 선택된 부모 인덱스 값을 알아온다. 만일 알아올 수 없으면 null을 반환한다.
  • 코드 라인 71~73에서 클럭에 CLK_SET_RATE_PARENT 플래그가 설정되었고 parent의 rate가 변경된 경우 인수로 부모 클럭과 best_parent_rate 값으로 이 함수를 재귀호출하여 상위 클럭으로 올라가서 rate가 변경될 상위 부모 클럭을 알아온다.
  • 코드 라인 76에서 이 함수가 재귀 호출된 경우 rate가 변경될 상위 부모 클럭부터 시작하여 연결된 모든 자식 클럭 방향으로 rate를 재계산하게 한다.

 

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

 

clk_core_get_boundaries()

drivers/clk/clk.c

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

        *min_rate = 0;
        *max_rate = ULONG_MAX;

        hlist_for_each_entry(clk_user, &clk->clks, child_node)
                *min_rate = max(*min_rate, clk_user->min_rate);

        hlist_for_each_entry(clk_user, &clk->clks, child_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_fetch_parent_index()

drivers/clk/clk.c

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

        if (!clk->parents) {
                clk->parents = kcalloc(clk->num_parents,
                                        sizeof(struct clk *), GFP_KERNEL);
                if (!clk->parents)
                        return -ENOMEM;
        }

        /*
         * find index of new parent clock using cached parent ptrs,
         * or if not yet cached, use string name comparison and cache
         * them now to avoid future calls to clk_core_lookup.
         */
        for (i = 0; i < clk->num_parents; i++) {
                if (clk->parents[i] == parent)
                        return i;

                if (clk->parents[i])
                        continue;

                if (!strcmp(clk->parent_names[i], parent->name)) {
                        clk->parents[i] = clk_core_lookup(parent->name);
                        return i;
                }
        }

        return -EINVAL;
}

현재 클럭에 연결된 인수 parent 클럭으로 인덱스 값을 알아온다. 실패하는 경우 음수 에러가 반환된다.

  • 코드 라인 6~11에서 parents 멤버에 할당된 배열이 없는 경우 부모 클럭 수 만큼 포인터 배열을 할당한다.
  • 코드 라인 18~20에서 부모 클럭 수만큼 인덱스를 증가시키며 요청한 부모 클럭인 경우 그 인덱스 값을 반환한다.
  • 코드 라인 22~23에서 인덱스에 해당하는 어떤 클럭이 있는 경우 skip 한다.
  • 코드 라인 25~28에서 인덱스에 해당하는 클럭이 없는 경우 인수 parent 클럭의 이름으로 검색하여 연결해 놓는다.

 

동적 rate 설정 -3- (recalc)

clk_calc_subtree()

drivers/clk/clk.c

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

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

        hlist_for_each_entry(child, &clk->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에서 자식 노드에 대해 이 함수를 재귀 호출하여 계산하게 한다.

 

clk_recalc()

drivers/clk/clk.c

static unsigned long clk_recalc(struct clk_core *clk,
                                unsigned long parent_rate)
{
        if (clk->ops->recalc_rate)
                return clk->ops->recalc_rate(clk->hw, parent_rate);
        return parent_rate;
}

클럭 h/w 디바이스 드라이버를 통해 parent_rate 값을 사용하여 현재 클럭의 rate를 재계산해온다.

 

__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.
 *
 * Caller must hold prepare_lock.
 */
static void __clk_recalc_rates(struct clk_core *clk, unsigned long msg)
{
        unsigned long old_rate;
        unsigned long parent_rate = 0;
        struct clk_core *child;

        old_rate = clk->rate;

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

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

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

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

 

 

 

rate 설정 -4- (mux 타입 rate 설정)

__clk_mux_determine_rate()

drivers/clk/clk.c

/*
 * 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.
 */
long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate,
                              unsigned long min_rate,
                              unsigned long max_rate,
                              unsigned long *best_parent_rate,
                              struct clk_hw **best_parent_p)
{
        return clk_mux_determine_rate_flags(hw, rate, min_rate, max_rate,
                                            best_parent_rate,
                                            best_parent_p, 0);
}
EXPORT_SYMBOL_GPL(__clk_mux_determine_rate);

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

 

long __clk_mux_determine_rate_closest(struct clk_hw *hw, unsigned long rate,
                              unsigned long min_rate,
                              unsigned long max_rate,
                              unsigned long *best_parent_rate,
                              struct clk_hw **best_parent_p)
{
        return clk_mux_determine_rate_flags(hw, rate, min_rate, max_rate,
                                            best_parent_rate,
                                            best_parent_p,
                                            CLK_MUX_ROUND_CLOSEST);
}
EXPORT_SYMBOL_GPL(__clk_mux_determine_rate_closest);

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

 

clk_mux_determine_rate_flags()

drivers/clk/clk.c

static long
clk_mux_determine_rate_flags(struct clk_hw *hw, unsigned long rate,
                             unsigned long min_rate,
                             unsigned long max_rate,
                             unsigned long *best_parent_rate,
                             struct clk_hw **best_parent_p,
                             unsigned long flags)
{
        struct clk_core *core = hw->core, *parent, *best_parent = NULL;
        int i, num_parents;
        unsigned long parent_rate, best = 0;

        /* 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)
                        best = __clk_determine_rate(parent ? parent->hw : NULL,
                                                    rate, min_rate, max_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_rate = __clk_determine_rate(parent->hw, rate,
                                                           min_rate,
                                                           max_rate);
                else
                        parent_rate = clk_core_get_rate_nolock(parent);
                if (mux_is_better_rate(rate, parent_rate, best, flags)) {
                        best_parent = parent;
                        best = parent_rate;
                }
        }

out:
        if (best_parent)
                *best_parent_p = best_parent->hw;
        *best_parent_rate = best;

        return best;
}

요청한 rate에 가장 적절한 rate를 구한다. 출력 인수 best_parent_rate에 산출한 최적의 rate가 담기고 best_parent_p는 산출된 최적의 부모 클럭을 가리킨다. 단 min_rate ~ max_rate 범위를 벗어나는 경우 0을 반환한다.

  • 코드 라인 14~24에서 현재 mux 클럭에서 CLK_SET_RATE_NO_REPARENT 플래그가 주어진 경우 현재 클럭에 대해 다른 부모 클럭을 선택하지 못하게된다. 즉 이미 선택된 부모 클럭은 변경하지 않고 고정한다. 이렇게 부모 클럭을 고정시킨 후 rate 값을 알아오는데 다음 3가지 중 하나로 결정하여 best 값으로 대입하고 out 레이블로 이동한다.
    • 코드 라인 16~18에서 현재 클럭에서 CLK_SET_RATE_PARENT 플래그가 사용되면 상위 부모 클럭으로 이동하여 최적의 rate 값을 알아온다.
    • 코드 라인 19~20에서 지정된 부모 클럭의 rate 값을 알아온다.
    • 코드 라인 21~22에서 현재 클럭의 rate 값을 알아온다.
  • 코드 라인 27~42에서 부모 클럭 수 만큼 루프를 돌며 최적의 rate가 산출될 수 있는 부모 클럭을 선택하려 한다. 연결되지 않은 부모 클럭은 skip하고 다음 2가지 중 하나의 상황으로 계속 루프를 돌며 산출된 부모 rate 값이 최적 rate 값인 경우 갱신한다.
    • 코드 라인 32~35에서 현재 클럭에 CLK_SET_RATE_PARENT 플래그가 사용되면 현재 인덱스의 상위 부모 클럭으로 이동하여 최적의 rate 값을 알아온다.
    • 코드 라인 36~37에서 현재 인덱스의 부모 클럭 rate 값을 알아온다.
  • 코드 라인 45~46에서 부모 클럭을 선택할 수 있고 최적의 부모가 산출된 경우 출력 인수 best_parent_p가 선택된 부모 클럭을 가리키게 한다.
  • 코드 라인 47에서 출력 인수 best_parent_rate에 산출된 best 값을 대입한다.

 

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)

 

 

__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
 * @rate: target rate
 * @min_rate: returned rate must be greater than this rate
 * @max_rate: returned rate must be less than this rate
 *
 * Caller must hold prepare_lock.  Useful for clk_ops such as .set_rate and
 * .determine_rate.
 */
unsigned long __clk_determine_rate(struct clk_hw *hw,
                                   unsigned long rate,
                                   unsigned long min_rate,
                                   unsigned long max_rate)
{
        if (!hw)
                return 0;

        return clk_core_round_rate_nolock(hw->core, rate, min_rate, max_rate);
}
EXPORT_SYMBOL_GPL(__clk_determine_rate);

클럭 디바이스 드라이버가 실제 지원하는 rate에 가장 가까운 값을 알아온다. min_rate ~ max_rate 범위를 벗어나는 경우 0을 반환한다.

 

rate 설정 -5- (divider, fractional, … 타입 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)
{
        unsigned long ret;

        if (!clk)
                return 0;

        clk_prepare_lock();
        ret = __clk_round_rate(clk, rate);
        clk_prepare_unlock();

        return ret;
}
EXPORT_SYMBOL_GPL(clk_round_rate);

요청 rate에 대해 클럭 디바이스 드라이버가 지원하는 rate로 반올림한 rate를 반환한다. 만일 클럭이 ops->round_rate를 지원하지 않으면 부모의 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에 대해 클럭 디바이스 드라이버가 지원하는 rate로 반올림한 rate를 반환한다. 만일 클럭이 ops->round_rate를 지원하지 않으면 부모의 rate 값이 반환된다. 호출 시 prepare_lock을 잡아야 한다.

  • 코드 라인 16에서 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.
  • 코드 라인 18에서 요청 rate에 대해 클럭 디바이스 드라이버가 지원하는 rate로 반올림한 rate를 반환한다. 만일 클럭이 ops->round_rate를 지원하지 않으면 부모의 rate 값이 반환된다. 또한 min_rate 및 max_rate 값을 초과하는 경우 0을 반환한다.

 

clk_core_round_rate_nolock()

drivers/clk/clk.c

static unsigned long clk_core_round_rate_nolock(struct clk_core *clk,
                                                unsigned long rate,
                                                unsigned long min_rate,
                                                unsigned long max_rate)
{
        unsigned long parent_rate = 0;
        struct clk_core *parent;
        struct clk_hw *parent_hw;

        if (!clk)
                return 0;

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

        if (clk->ops->determine_rate) {
                parent_hw = parent ? parent->hw : NULL;
                return clk->ops->determine_rate(clk->hw, rate,
                                                min_rate, max_rate,
                                                &parent_rate, &parent_hw);
        } else if (clk->ops->round_rate)
                return clk->ops->round_rate(clk->hw, rate, &parent_rate);
        else if (clk->flags & CLK_SET_RATE_PARENT)
                return clk_core_round_rate_nolock(clk->parent, rate, min_rate,
                                                  max_rate);
        else
                return clk->rate;
}

 

요청 rate에 대해 클럭 디바이스 드라이버가 지원하는 rate로 반올림한 rate를 반환한다. 만일 클럭이 ops->round_rate를 지원하지 않으면 부모의 rate 값이 반환된다. 또한 min_rate 및 max_rate 값을 초과하는 경우 0을 반환한다.

  • 코드 라인 13~15에서 부모 클럭이 있는 경우 부모 클럭의 rate 값을 알아온다.
  • 코드 라인 17~21에서 클럭이 ops->determine_rate를 지원하는 mux 타입 클럭인 경우 호출하여 적절한 rate 값을 산출하여 반환한다.
    • 현재 mux 클럭이 선택할 수 있는 부모 클럭을 변경해가며 적절한 rate 값을 알아온다.
  • 코드 라인 22~23에서 클럭이 ops->round_rate를 지원하는 배율 조정 타입 클럭인 경우 호출하여 적절한 rate 값을 산출하여 반환한다.
    • 현재 배율 조정 타입 클럭이 선택할 수 있는 배율을 변경해가며 적절한 rate 값을 알아온다.
  • 코드 라인 24~26에서 클럭이 ops->determine_rate 및 ops_round_rate를 지원하지 않지만 CLK_SET_RATE_PARENT가 설정된 gate 타입인 경우 부모 클럭에서 적절한 rate 값을 산출해온다.
  • 코드 라인 27~28에서 클럭이 ops->determine_rate 및 ops_round_rate를 지원하지 않지만 CLK_SET_RATE_PARENT가 설정되지 않은 gate 타입인 경우 현재 클럭의 rate 값을 반환한다.

 

클럭 준비/해지

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)
{
        int ret;

        if (!clk)
                return 0;

        clk_prepare_lock();
        ret = clk_core_prepare(clk->core);
        clk_prepare_unlock();

        return ret;
}
EXPORT_SYMBOL_GPL(clk_prepare);

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

 

clk_core_prepare()

drivers/clk/clk.c

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

        if (!clk)
                return 0;

        if (clk->prepare_count == 0) {
                ret = clk_core_prepare(clk->parent);
                if (ret)
                        return ret;

                if (clk->ops->prepare) {
                        ret = clk->ops->prepare(clk->hw);
                        if (ret) {
                                clk_core_unprepare(clk->parent);
                                return ret;
                        }
                }
        }

        clk->prepare_count++;

        return 0;
}

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

  • 코드 라인 5~6에서 이 함수는 재귀호출을 하게 하였으며 클럭이 지정되지 않으면 빠져나간다.
  • 코드 라인 8~11에서 한 번도 prepare 한 적이 없는 경우 부모 클럭에 대해 재귀 호출로 이 함수를 호출하여 prepare 한다. 만일 에러가 발생하면 재귀 호출을 빠져나간다.
  • 코드 라인 13~19에서 custom 클럭 디바이스에 구현된 ops->prepare 후크에 연결된 콜백 함수를 수행한다. 만일 에러가 발생한 경우 unprepare 한다.
  • 코드 라인 22~24에서 prepare_count를 1 증가시키고 성공(0)으로 함수를 빠져나간다.
    • 최상위 부모 클럭까지 재귀 호출되어 1씩 증가된다.

 

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_prepare_lock();
        clk_core_unprepare(clk->core);
        clk_prepare_unlock();
}
EXPORT_SYMBOL_GPL(clk_unprepare);

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

 

clk_core_unprepare()

drivers/clk/clk.c

static void clk_core_unprepare(struct clk_core *clk)
{
        if (!clk)
                return;

        if (WARN_ON(clk->prepare_count == 0))
                return;

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

        WARN_ON(clk->enable_count > 0);

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

        clk_core_unprepare(clk->parent);
}

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

  • 코드 라인 3~4에서 이 함수는 재귀호출을 하게 하였으며 클럭이 지정되지 않으면 빠져나간다.
  • 코드 라인 9~10에서 prepare_count를 1 감소시킨 값이 0보다 큰 경우 함수를 빠져나간다.
    • 최상위 부모 클럭까지 재귀 호출되어 1씩 감소시킨다.
  • 코드 라인 14~15에서 custom 클럭 디바이스에 구현된 ops->unprepare 후크에 연결된 콜백 함수를 수행한다.
  • 코드 라인 17에서 최상위 부모 클럭까지 이 함수를 재귀 호출한다.

 

클럭 게이트 enable & disable

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)
{
        unsigned long flags;
        int ret;

        flags = clk_enable_lock();
        ret = __clk_enable(clk);
        clk_enable_unlock(flags);

        return ret;
}
EXPORT_SYMBOL_GPL(clk_enable);

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

 

__clk_enable()

drivers/clk/clk.c

static int __clk_enable(struct clk *clk)
{
        if (!clk)
                return 0;

        return clk_core_enable(clk->core);
}

상위 클럭 소스까지 enable 시킨다. 재귀 호출되는 함수이므로 클럭이 지정되지 않으면 0의 결과값으로 함수를 빠져나간다.

 

clk_core_enable()

drivers/clk/clk.c

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

        if (!clk)
                return 0;

        if (WARN_ON(clk->prepare_count == 0))
                return -ESHUTDOWN;

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

                if (ret)
                        return ret;

                if (clk->ops->enable) {
                        ret = clk->ops->enable(clk->hw);
                        if (ret) {
                                clk_core_disable(clk->parent);
                                return ret;
                        }
                }
        }

        clk->enable_count++;
        return 0;
}

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

  • 코드 라인 5~6에서 이 함수는 재귀호출을 하게 하였으며 클럭이 지정되지 않으면 빠져나간다.
  • 코드 라인 8~9에서 이 클럭이 prepare된 적이 없으면 경고 메시지를 출력하고 -ESHUTDOWN 에러 코드를 반환한다.
  • 코드 라인 11~15에서 한 번도 enable 한 적이 없는 경우 부모 클럭에 대해 재귀 호출로 이 함수를 호출하여 enable 한다. 만일 에러가 발생하면 재귀 호출을 빠져나간다.
  • 코드 라인 17~23에서 gate 타입 클럭 디바이스에 구현된 ops->enable 후크에 연결된 콜백 함수를 수행하여 실제 hw의 gate를 연다. 만일 에러가 발생한 경우 disable 한다.
  • 코드 라인 22~24에서 enable_count를 1 증가시키고 성공(0)으로 함수를 빠져나간다.
    • 최상위 부모 클럭까지 재귀 호출되어 1씩 증가된다.

 

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)
{
        unsigned long flags;

        if (IS_ERR_OR_NULL(clk))
                return;

        flags = clk_enable_lock();
        __clk_disable(clk);
        clk_enable_unlock(flags);
}
EXPORT_SYMBOL_GPL(clk_disable);

lock을 획득한 상태에서 상위 클럭 소스까지 disable 시킨다. 재귀 호출되는 함수이므로 클럭이 지정되지 않거나 에러 코드인 경우 함수를 빠져나간다.

 

__clk_disable()

drivers/clk/clk.c

static void __clk_disable(struct clk *clk)
{
        if (!clk)
                return;

        clk_core_disable(clk->core);
}

상위 클럭 소스까지 disable 시킨다. 재귀 호출되는 함수이므로 클럭이 지정되지 않으면 함수를 빠져나간다.

 

clk_core_disable()

drivers/clk/clk.c

static void clk_core_disable(struct clk_core *clk)
{
        if (!clk)
                return;

        if (WARN_ON(clk->enable_count == 0))
                return;

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

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

        clk_core_disable(clk->parent);
}

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

  • 코드 라인 3~4에서 이 함수는 재귀호출을 하게 하였으며 클럭이 지정되지 않으면 함수를 빠져나간다.
  • 코드 라인 6~7에서 이 클럭이 enable된 적이 없으면 경고 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 9~10에서 enable_count를 1 감소시킨 값이 0보다 큰 경우 함수를 빠져나간다.
    • 최상위 부모 클럭까지 재귀 호출되어 1씩 감소시킨다.
  • 코드 라인 14~15에서 gate 타입 클럭 디바이스에 구현된 ops->disable 후크에 연결된 콜백 함수를 수행하여 실제 hw의 gate를 닫는다.
  • 코드 라인 17에서 최상위 부모 클럭까지 이 함수를 재귀 호출한다.

 

mux 타입 클럭에서 입력 클럭 소스(부모 클럭) 선택

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)
{
        if (!clk)
                return 0;

        return clk_core_set_parent(clk->core, parent ? parent->core : NULL);
}
EXPORT_SYMBOL_GPL(clk_set_parent);

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

 

clk_core_set_parent()

drivers/clk/clk.c

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

        if (!clk)
                return 0;

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

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

        if (clk->parent == parent)
                goto out;

        /* check that we are allowed to re-parent if the clock is in use */
        if ((clk->flags & CLK_SET_PARENT_GATE) && clk->prepare_count) {
                ret = -EBUSY;
                goto out;
        }

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

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

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

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

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

out:
        clk_prepare_unlock();

        return ret;
}

mux 타입 클럭의 부모 클럭(입력 클럭 소스)을 선택하고 연결된 모든 자식 클럭들의 rate는 재계산된다. 성공인 경우 0을 반환한다.

  • 코드 라인 11~12에서 2개 이상의 부모 클럭(입력 클럭 소스)이 있고 ops->set_parent가 구현된 mux 타입 클럭이 아니면 -ENOSYS 에러를 반환한다.
  • 코드 라인 17~18에서 요청한 부모 클럭(입력 클럭 소스)이 이미 지정되어 있었던 경우 변경할 필요가 없으므로 성공(0) 결과로 함수를 빠져나온다.
  • 코드 라인 21~24에서 클럭에 CLK_SET_PARENT_GATE 플래그가 사용되었고 prepare된 상태인 경우 -EBUSY 에러를 반환한다.
    • unprepare된 상태에서만 입력 클럭 소스(부모 클럭)을 선택할 수 있게 제한한 장치이다.
  • 코드 라인 27~36에서 부모 클럭(입력 클럭 소스)의 인덱스와 rate 값을 알아온다. 만일 알아올 수 없으면 에러를 반환한다.
  • 코드 라인 39~43에서 현재 클럭 이하 연결된 모든 자식 클럭에 대해 PRE_RATE_CHANGE를 통지한다. 만일 결과가 NOTIFY_STOP 또는 NOTIFY_BAD인 경우 함수를 빠져나간다.
  • 코드 라인 46~54에서 부모 클럭(입력 클럭 소스)을 선택한다. 만일 에러가 발생한 경우 ABORT_RATE_CHANGE를 하위 노드에 전파한다. 성공한 경우에는 POST_RATE_CHANGE를 하위 노드에 전파한 후 현재 노드의 모든 하위 노드를 재계산하도록 요청한다.

 

__clk_set_parent()

drivers/clk/clk.c

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

        old_parent = __clk_set_parent_before(clk, parent);

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

        if (ret) {
                flags = clk_enable_lock();
                clk_reparent(clk, old_parent);
                clk_enable_unlock(flags);

                if (clk->prepare_count) {
                        flags = clk_enable_lock();
                        clk_core_disable(clk);
                        clk_core_disable(parent);
                        clk_enable_unlock(flags);
                        clk_core_unprepare(parent);
                }
                return ret;
        }

        __clk_set_parent_after(clk, parent, old_parent);

        return 0;
}

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

  • 코드 라인 8에서 입력 클럭 소스(부모 클럭)을 선택한 후에 처리할 일을 수행한다.
  • 코드 라인 11~12에서 mux 타입 클럭에서 ops->set_parent에 지정된 콜백 함수를 호출하여 실제 hw로 부모 클럭(입력 클럭 소스)을 선택하게 한다.
  • 코드 라인 14~27에서 에서 처리 시 에러가 발생한 경우 기존 부모 클럭(입력 클럭 소스)로 재 변경하고 현재 클럭을 disable하고 부모 클럭은 disable 및 unprepare를 수행하고 에러를 반환한다.
  • 코드 라인 29에서 입력 클럭 소스(부모 클럭)을 선택한 후에 처리할 일을 수행한다.

 

__clk_set_parent_before()

drivers/clk/clk.c

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

        /*
         * 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.
         */
        if (clk->prepare_count) {
                clk_core_prepare(parent);
                flags = clk_enable_lock();
                clk_core_enable(parent);
                clk_core_enable(clk);
                clk_enable_unlock(flags);
        }

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

        return old_parent;
}

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

  • 코드 라인 24~30에서 클럭이 prepare된 경우 부모 클럭을 prepare 및 enable 시키고 현재 클럭도 enable 한다.
  • 코드 라인 33~35에서 clock tree 토플로지를 갱신한다.

 

__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)
{
        unsigned long flags;

        /*
         * Finish the migration of prepare state and undo the changes done
         * for preventing a race with clk_enable().
         */
        if (core->prepare_count) {
                flags = clk_enable_lock();
                clk_core_disable(core);
                clk_core_disable(old_parent);
                clk_enable_unlock(flags);
                clk_core_unprepare(old_parent);
        }
}

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

  • 코드 라인 11~17에서 클럭이 prepare된 경우 현재 클럭을 disable하고, 부모 클럭도 disable 및 unprepare한다.

 

clk_reparent()

drivers/clk/clk.c

static void clk_reparent(struct clk_core *clk, struct clk_core *new_parent)
{
        hlist_del(&clk->child_node);

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

                hlist_add_head(&clk->child_node, &new_parent->children);
        } else {
                hlist_add_head(&clk->child_node, &clk_orphan_list);
        }

        clk->parent = new_parent;
}

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

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

 

클럭 통지(notify) 기능

clk notifier로 등록

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(struct clk_notifier), 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을 등록한다.

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

 

Rate 변경에 따른 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 *clk,
                                                  unsigned long event)
{
        struct clk_core *child, *tmp_clk, *fail_clk = NULL;
        int ret = NOTIFY_DONE;

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

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

        hlist_for_each_entry(child, &clk->children, child_node) {
                /* Skip children who will be reparented to another clock */
                if (child->new_parent && child->new_parent != clk)
                        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 clk->children yet */
        if (clk->new_child) {
                tmp_clk = clk_propagate_rate_change(clk->new_child, event);
                if (tmp_clk)
                        fail_clk = tmp_clk;
        }

        return fail_clk;
}

현재 클럭에 연결된 모든 하위 트리에 rate 변화를 통지한다. 성공 시 0을 반환한다.

  • 코드 라인 12~13에서 rate의 변화가 없는 경우 null을 반환한다.
  • 코드 라인 15~19에서 현재 클럭에 notifier_count가 있는 경우 notify한다. 만일 결과가 NOTIFY_BAD 또는 NOTIFY_STOP을 갖는 경우 fail_clk에 이 클럭을 대입한다.
  • 코드 라인 21~24에서 자식 클럭 수 만큼 루프를 돌며 요청 클럭이 이 자식 클럭의 새로운 부모 클럭이 아닌 경우 skip 한다.
  • 코드 라인 25~28에서 자식 클럭으로 요청 이벤트를 전파한다.  만일 실패한 경우 fail_clk에 tmp_clk를 담는다.
  • 코드 라인 31~35에서 요청 클럭의 new_child가 있는 경우(곧 children에 들어갈) 그 new_child 클럭에 대해서도 전파한다. 만일 실패한 경우 fail_clk에 tmp_clk를 담는다.

 

__clk_notify()

drivers/clk/clk.c

/**
 * __clk_notify - call clk notifier chain
 * @clk: struct 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 *clk, 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 == clk) {
                        cnd.clk = cn->clk;
                        ret = srcu_notifier_call_chain(&cn->notifier_head, msg,
                                        &cnd);
                }
        }

        return ret;
}

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

 

참고

 

답글 남기기

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