kernel - clock

一、相关数据结构介绍

struct clk_hw {
    struct clk *clk;                    
    const struct clk_init_data *init;   
};
struct clk_init_data {
    const char      *name;              
    const struct clk_ops    *ops;       
    const char      **parent_names;     
    u8          num_parents;
    unsigned long       flags;
};

struct clk_ops {
    int     (*prepare)(struct clk_hw *hw);
    void        (*unprepare)(struct clk_hw *hw);
    int     (*is_prepared)(struct clk_hw *hw);
    void        (*unprepare_unused)(struct clk_hw *hw);
    int     (*enable)(struct clk_hw *hw);
    void        (*disable)(struct clk_hw *hw);
    int     (*is_enabled)(struct clk_hw *hw);
    void        (*disable_unused)(struct clk_hw *hw);
    unsigned long   (*recalc_rate)(struct clk_hw *hw,
                    unsigned long parent_rate);
    long        (*round_rate)(struct clk_hw *hw, unsigned long rate,
                    unsigned long *parent_rate);
    long        (*determine_rate)(struct clk_hw *hw, unsigned long rate,
                    unsigned long *best_parent_rate,
                    struct clk **best_parent_clk);
    int     (*set_parent)(struct clk_hw *hw, u8 index);
    u8      (*get_parent)(struct clk_hw *hw);
    int     (*set_rate)(struct clk_hw *hw, unsigned long rate,
                    unsigned long parent_rate);
    int     (*set_rate_and_parent)(struct clk_hw *hw,
                    unsigned long rate,
                    unsigned long parent_rate, u8 index);
    unsigned long   (*recalc_accuracy)(struct clk_hw *hw,
                       unsigned long parent_accuracy);
    int     (*get_phase)(struct clk_hw *hw);
    int     (*set_phase)(struct clk_hw *hw, int degrees);
    void        (*init)(struct clk_hw *hw);
    int     (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};

// 固定速率时钟
struct clk_fixed_rate {
    struct      clk_hw hw;
    unsigned long   fixed_rate;
    unsigned long   fixed_accuracy;
    u8      flags;
};

// 固定倍频时钟
struct clk_fixed_factor {
    struct clk_hw   hw;
    unsigned int    mult;
    unsigned int    div;
}; 

// 分频时钟
struct clk_divider {
    struct clk_hw   hw; 
    void __iomem    *reg;
    u8      shift;
    u8      width;
    u8      flags;
    const struct clk_div_table  *table;
    spinlock_t  *lock;
};

// 多选一时钟
struct clk_mux {
    struct clk_hw   hw;
    void __iomem    *reg;
    u32     *table;
    u32     mask;
    u8      shift;
    u8      flags; 
    spinlock_t  *lock;
};

// 门时钟
struct clk_gate {
    struct clk_hw hw;
    void __iomem    *reg;
    u8      bit_idx;
    u8      flags;
    spinlock_t  *lock;
};

二、时钟的注册

struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{
    int i, ret;
    struct clk_core *clk;

    clk = kzalloc(sizeof(*clk), GFP_KERNEL);
    if (!clk) {
        pr_err("%s: could not allocate clk\n", __func__);
        ret = -ENOMEM;
        goto fail_out;
    }

    clk->name = kstrdup_const(hw->init->name, GFP_KERNEL);
    if (!clk->name) {
        pr_err("%s: could not allocate clk->name\n", __func__);
        ret = -ENOMEM;
        goto fail_name;
    }
    clk->ops = hw->init->ops;
    if (dev && dev->driver)
        clk->owner = dev->driver->owner;
    clk->hw = hw;
    clk->flags = hw->init->flags;
    clk->num_parents = hw->init->num_parents;
    hw->core = clk;

    /* allocate local copy in case parent_names is __initdata */
    clk->parent_names = kcalloc(clk->num_parents, sizeof(char *),
                    GFP_KERNEL);

    if (!clk->parent_names) {
        pr_err("%s: could not allocate clk->parent_names\n", __func__);
        ret = -ENOMEM;
        goto fail_parent_names;
    }


    /* copy each string name in case parent_names is __initdata */
    for (i = 0; i < clk->num_parents; i++) {
        clk->parent_names[i] = kstrdup_const(hw->init->parent_names[i],
                        GFP_KERNEL);
        if (!clk->parent_names[i]) {
            pr_err("%s: could not copy parent_names\n", __func__);
            ret = -ENOMEM;
            goto fail_parent_names_copy;
        }
    }

    INIT_HLIST_HEAD(&clk->clks);

    hw->clk = __clk_create_clk(hw, NULL, NULL);
    if (IS_ERR(hw->clk)) {
        pr_err("%s: could not allocate per-user clk\n", __func__);
        ret = PTR_ERR(hw->clk);
        goto fail_parent_names_copy;
    }

    ret = __clk_init(dev, hw->clk);
    if (!ret)
        return hw->clk;

    __clk_free_clk(hw->clk);
    hw->clk = NULL;
    
    ......
}
  • 创建分配clk_core结构体对象,并填充其成员变量
  • 调用__clk_create_clk创建用户侧时钟结构体
  • 调用__clk_init将clk_core加入到时钟树里
static int __clk_init(struct device *dev, struct clk *clk_user)
{
    ......
    
    if (clk->parent)
        hlist_add_head(&clk->child_node,
                &clk->parent->children);
    else if (clk->flags & CLK_IS_ROOT)
        hlist_add_head(&clk->child_node, &clk_root_list);
    else
        hlist_add_head(&clk->child_node, &clk_orphan_list);

    /*
     * Set clk's accuracy.  The preferred method is to use
     * .recalc_accuracy. For simple clocks and lazy developers the default
     * fallback is to use the parent's accuracy.  If a clock doesn't have a
     * parent (or is orphaned) then accuracy is set to zero (perfect
     * clock).
     */
    if (clk->ops->recalc_accuracy)
        clk->accuracy = clk->ops->recalc_accuracy(clk->hw,
                    __clk_get_accuracy(clk->parent));
    else if (clk->parent)
        clk->accuracy = clk->parent->accuracy;
    else
        clk->accuracy = 0;

    /*
     * Set clk's phase.
     * Since a phase is by definition relative to its parent, just
     * query the current clock phase, or just assume it's in phase.
     */
    if (clk->ops->get_phase)
        clk->phase = clk->ops->get_phase(clk->hw);
    else
        clk->phase = 0;

    /*
     * Set clk's rate.  The preferred method is to use .recalc_rate.  For
     * simple clocks and lazy developers the default fallback is to use the
     * parent's rate.  If a clock doesn't have a parent (or is orphaned)
     * then rate is set to zero.
     */
    if (clk->ops->recalc_rate)
        rate = clk->ops->recalc_rate(clk->hw,
                clk_core_get_rate_nolock(clk->parent));
    else if (clk->parent)
        rate = clk->parent->rate;
    else
        rate = 0;
    clk->rate = clk->req_rate = rate;

    /*
     * walk the list of orphan clocks and reparent any that are children of
     * this clock
     */
    hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
        if (orphan->num_parents && orphan->ops->get_parent) {
            i = orphan->ops->get_parent(orphan->hw);
            if (!strcmp(clk->name, orphan->parent_names[i]))
                clk_core_reparent(orphan, clk);
            continue;
        }

        for (i = 0; i < orphan->num_parents; i++)
            if (!strcmp(clk->name, orphan->parent_names[i])) {
                clk_core_reparent(orphan, clk);
                break;
            }
     }


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

    ......
}
  • 对clk_core进行约束性检查
  • 将clk_core加入时钟树
  • 对clk_core进行初始化工作,并且注册debugfs调试接口
int clk_register_clkdev(struct clk *clk, const char *con_id,
    const char *dev_fmt, ...)
{
    struct clk_lookup *cl;
    va_list ap;

    if (IS_ERR(clk))
        return PTR_ERR(clk);

    va_start(ap, dev_fmt);
    cl = vclkdev_alloc(clk, con_id, dev_fmt, ap);
    va_end(ap);

    if (!cl)
        return -ENOMEM;

    clkdev_add(cl);

    return 0;
}
  • 该函数的主要功能是建立用户侧时钟clk和设备的关联,方便设备驱动获取clk
  • 调用vclkdev_alloc创建clk_lookup
  • 调用clkdev_add添加clk_lookup到系统中
void clkdev_add(struct clk_lookup *cl)
{
    mutex_lock(&clocks_mutex);
    list_add_tail(&cl->node, &clocks);
    mutex_unlock(&clocks_mutex);
}
EXPORT_SYMBOL(clkdev_add);
  • 添加clk_lookup到链表clocks中
int of_clk_add_provider(struct device_node *np,
            struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,
                           void *data),
            void *data)
{
    struct of_clk_provider *cp;
    int ret;

    cp = kzalloc(sizeof(struct of_clk_provider), GFP_KERNEL);
    if (!cp)
        return -ENOMEM;

    cp->node = of_node_get(np);
    cp->data = data;
    cp->get = clk_src_get;

    mutex_lock(&of_clk_mutex);
    list_add(&cp->link, &of_clk_providers);
    mutex_unlock(&of_clk_mutex);
    pr_debug("Added clock from %s\n", np->full_name);

    ret = of_clk_set_defaults(np, true);
    if (ret < 0)
        of_clk_del_provider(np);

    return ret;
}
  • 该函数为设备树节点注册一个查找用户侧时钟的方法
  • 创建of_clk_provider结点体,并将该结构点添加到链表of_clk_providers

三、时钟的获取

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

    if (dev) {
        clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);
        if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)
            return clk;
    }

    return clk_get_sys(dev_id, con_id);
}
EXPORT_SYMBOL(clk_get);
  • 如果dev存在,则首先通过设备树的方法获取clk
  • 其次通过传统方法查找clk_lookup链表获取clk
static struct clk *__of_clk_get_by_name(struct device_node *np,
                    const char *dev_id,
                    const char *name)
{
    struct clk *clk = ERR_PTR(-ENOENT);

    /* Walk up the tree of devices looking for a clock that matches */
    while (np) {
        int index = 0;

        /*
         * For named clocks, first look up the name in the
         * "clock-names" property.  If it cannot be found, then
         * index will be an error code, and of_clk_get() will fail.
         */
        if (name)
            index = of_property_match_string(np, "clock-names", name);
        clk = __of_clk_get(np, index, dev_id, name);
        if (!IS_ERR(clk)) {
            break;
        } else if (name && index >= 0) {
            if (PTR_ERR(clk) != -EPROBE_DEFER)
                pr_err("ERROR: could not get clock %s:%s(%i)\n",
                    np->full_name, name ? name : "", index);
            return clk;
        }

        /*
         * No matching clock found on this node.  If the parent node
         * has a "clock-ranges" property, then we can try one of its
         * clocks.
         */
        np = np->parent;
        if (np && !of_get_property(np, "clock-ranges", NULL))
            break;
    }

    return clk;
}
  • 根据传入的name查找index
  • 调用__of_clk_get获取clk
static struct clk *__of_clk_get(struct device_node *np, int index,
                   const char *dev_id, const char *con_id)
{   
    struct of_phandle_args clkspec;
    struct clk *clk; 
    int rc; 
            
    if (index < 0)
        return ERR_PTR(-EINVAL);
        
    rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
                    &clkspec);
    if (rc)
        return ERR_PTR(rc);

    clk = __of_clk_get_from_provider(&clkspec, dev_id, con_id);
    of_node_put(clkspec.np); 

    return clk;
}   
  • 调用of_parse_phandle_with_args获取时钟参数
  • 调用__of_clk_get_from_provider获取clk
struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec,
                       const char *dev_id, const char *con_id)
{
    struct of_clk_provider *provider;
    struct clk *clk = ERR_PTR(-EPROBE_DEFER);
                    
    if (!clkspec)
        return ERR_PTR(-EINVAL);
    
    /* Check if we have such a provider in our array */
    mutex_lock(&of_clk_mutex);
    list_for_each_entry(provider, &of_clk_providers, link) {
        if (provider->node == clkspec->np)
            clk = provider->get(clkspec, provider->data);
        if (!IS_ERR(clk)) {
            clk = __clk_create_clk(__clk_get_hw(clk), dev_id,
                           con_id);

            if (!IS_ERR(clk) && !__clk_get(clk)) {
                __clk_free_clk(clk);
                clk = ERR_PTR(-ENOENT);
            }
        
            break;
        }
    }
    mutex_unlock(&of_clk_mutex);

    return clk;
}
  • 编历链表of_clk_providers
  • 根据传入的clkspec查找对应的of_clk_provider
  • 然后调用of_clk_provider的get函数获取时钟,并调用__clk_create_clk创建用户侧clk
// 传统方法获取时钟
struct clk *clk_get_sys(const char *dev_id, const char *con_id)
{   
    struct clk_lookup *cl;
    struct clk *clk = NULL;
    
    mutex_lock(&clocks_mutex);
    
    cl = clk_find(dev_id, con_id);
    if (!cl) 
        goto out;
    
    clk = __clk_create_clk(__clk_get_hw(cl->clk), dev_id, con_id);
    if (IS_ERR(clk))
        goto out;
    
    if (!__clk_get(clk)) {
        __clk_free_clk(clk);
        cl = NULL;
        goto out;
    }

out:
    mutex_unlock(&clocks_mutex);
    
    return cl ? clk : ERR_PTR(-ENOENT);
}
  • 调用clk_find函数,根据dev_id和con_id得到clk_lookup
  • 然后在调用__clk_create_clk创建用户侧clk
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;

    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;
}
  • 遍历链表clocks,查找最佳clk_lookup
posted @ 2019-06-18 15:03  qzhang1535  阅读(515)  评论(0编辑  收藏  举报