程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux驱动移植-通用时钟框架子系统

一、CCF子系统概述

计算机硬件通过许多时钟设备提供时钟。从进入系统内部的 cpu core 时钟开始,使用数十种时钟,如 timer、i2c、uart 等。每个 ARM SoC都通过多个时钟设备驱动程序设置时钟,硬件千差万别。

linux内核采用了一个时钟子系统来解决这个问题。在linux 3.4之后的版本,linux内核时钟子系统又叫通用时钟框架子系统,简称CCF子系统(common clock framework)。

CCF子系统是用来管理系统clock资源的子系统,根据职能,可以分为三个部分:

  • 向其它驱动提供操作clock的通用API,屏蔽其中的硬件特性;
  • 实现clock控制的通用逻辑,这部分和硬件无关,向上提供统一的操作接口,向下提供底层platform操作的接口;
  • 将和硬件相关的clock控制逻辑封装成操作函数集,交由底层的platform开发者实现;比如选择哪个时钟源,设置输出的频率等;

1.1 时钟种类说明

在s3c2410时钟章节中我们曾经介绍了其复杂的时钟结构,下图是一个简单的clock结构图:

如上图所示,时钟大致可以分为如下几种:

  • 提供基础时钟源的晶振(有源晶振、无源晶振);
  • 用于倍频的PLL(锁相环,Phase Locked Loop);
  • 用于分频的divider
  • 用于多路选择的mux;
  • 用于clock enable控制的与门;
  • 各个时钟模块的组合;

虽然每一个SOC的时钟控制器的寄存器地址空间和具体结构略有差异,但是时钟控制器的组成目前来说,基本都是这个样子。

在CCF子系统的抽象中,将这六种不同类型的时钟均抽象出来:

  • 定义不同的结构体,比如struck clk_gate、struct clk_mux等,他们都是对struct clk_hw的封装;如果你学过OOP的话,可以把 clk_hw看到是时钟基类,而把clk_gate、clk_mux看做具体的子类;
  • 提供了单独的时钟注册函数,比如clk_register_gate、clk_register_mux等,也就是对clk_register函数的封装体;
  • 针对硬件时钟的操作接口,也抽象了对应的结构体struct clk_ops,包含时钟的使能接口、时钟频率的修改接口等等;

在针对上述所说的不同种类的时钟,其并不需要实现所有struct clk_ops中定义的接口,如下图所示:

  • 针对时钟使能的与门电路而言,仅需要实现enable、disable、is_enable接口即可;
  • 针对多路时钟选择的mux而言,则需要实现父时钟的设置及获取的接口set_parent、get_parent等;
  • 对于倍频、分频而言,则需要实现时钟频率相关的接口set_rate、recalc_rate等;

1.2  CCF子系统框图

下图是CCF子系统的架构图,其对各设备驱动提供统一时钟操作的接口,实现为:

  • 根据硬件设备名获取其对应的输入时钟:
  • 配置时钟(使能时钟、时钟频率配置);

在CCF内部,针对每一个时钟,抽象基类结构为struct clk_hw,该结构体中包含每一个硬件时钟的操作接口struct clk_ops,当时钟驱动程序完成时钟操作接口的定义,并调用clk_register完成注册后,则CCF即可借助clk_hw的clk_ops完成对时钟的参数配置等操作。

 

在linux内核中称clock driver为clock provider,相应的clock的使用者一般也是我们设备驱动程序称为clock consumer。

二、CCF子系统基本数据结构

CCF子系统抽象了数据结构struck clk、struct clk_hw、struct clk_ops:

  • clk_hw是对一个时钟的抽象基类(具体的子类,根据时钟种类的不同定义不同,比如clk_gate、clk_mux,这里就不一一介绍了);
  • clk_ops是时钟的操作接口的抽象;
  • struct clk:对于驱动开发来说,struct clk只是访问时钟的一个句柄,有了它,驱动开发就可以对时钟进行配置;其中sruct clk_hw结构包含了struck clk成员;

2.1 struct clk(drivers/clk/clk.c)

一个系统的时钟树结构是固定的,因此时钟的数目和用途也是固定的,我们以上面我们的案例图为例,假设其为一个完整的时钟系统,它的时钟包括:osc_clk、pll1_clk、pll2_clk、pll3_clk、hw1_clk、hw2_clk、hw3_clk。

我们完全可以通过名字,抽象这7个clock,进行开/关、rate调整等操作。但这样做有一个缺点:不能很好的处理时钟之间的级联关系,如hw2_clk和hw3_clk都关闭后,pll2_clk才能关闭。因此就引入struct clk结构,以链表的形式维护这种关系。

系统的struct clk是在由clock driver在系统启动时初始化完毕的,我们需要访问某个时钟时,只需要获取它对应的struct clk结构即可,怎么获取呢?通过名字索引就可以。

由于设备(由struct device表示)对应的clock(由struct clk表示)也是固定的啊,可不可以找到设备就能找到clock?可以,不过需要借助device tree。

struct clk定义在drivers/clk/clk.c:

struct clk {
        struct clk_core *core;
        struct device *dev;   // 关联的设备
        const char *dev_id;  // 来自device->name
        const char *con_id;   // 来自时钟别名 
        unsigned long min_rate;
        unsigned long max_rate;
        unsigned int exclusive_count;
        struct hlist_node clks_node;  // 哈希链表数据节点
};

2.2 struck clk_core(drivers/clk/clk.c)

struct clk_core {
        const char              *name;
        const struct clk_ops    *ops;
        struct clk_hw           *hw;
        struct module           *owner;
        struct device           *dev;
        struct device_node      *of_node;
        struct clk_core         *parent;
        struct clk_parent_map   *parents;
        u8                      num_parents;
        u8                      new_parent_index;
        unsigned long           rate;
        unsigned long           req_rate;
        unsigned long           new_rate;
        struct clk_core         *new_parent;
        struct clk_core         *new_child;
        unsigned long           flags;
        bool                    orphan;
        bool                    rpm_enabled;
        unsigned int            enable_count;
        unsigned int            prepare_count;
        unsigned int            protect_count;
        unsigned long           min_rate;
        unsigned long           max_rate;
        unsigned long           accuracy;
        int                     phase;
        struct clk_duty         duty;
        struct hlist_head       children;
        struct hlist_node       child_node;
        struct hlist_head       clks;       // 哈希双向链表头节点
        unsigned int            notifier_count;
#ifdef CONFIG_DEBUG_FS
        struct dentry           *dentry;
        struct hlist_node       debug_node;
#endif
        struct kref             ref;
};
View Code

2.3 struct clk_ops(include/linux/clk-provider.h)

struct clk_ops定义在include/linux/clk-provider.h文件中:

/**
 * struct clk_ops -  Callback operations for hardware clocks; these are to
 * be provided by the clock implementation, and will be called by drivers
 * through the clk_* api.
 *
 * @prepare:    Prepare the clock for enabling. This must not return until
 *              the clock is fully prepared, and it's safe to call clk_enable.
 *              This callback is intended to allow clock implementations to
 *              do any initialisation that may sleep. Called with
 *              prepare_lock held.
 *
 * @unprepare:  Release the clock from its prepared state. This will typically
 *              undo any work done in the @prepare callback. Called with
 *              prepare_lock held.
 *
 * @is_prepared: Queries the hardware to determine if the clock is prepared.
 *              This function is allowed to sleep. Optional, if this op is not
 *              set then the prepare count will be used.
 *
 * @unprepare_unused: Unprepare the clock atomically.  Only called from
 *              clk_disable_unused for prepare clocks with special needs.
 *              Called with prepare mutex held. This function may sleep.
 *
 * @enable:     Enable the clock atomically. This must not return until the
 *              clock is generating a valid clock signal, usable by consumer
 *              devices. Called with enable_lock held. This function must not
 *              sleep.
 *
 * @disable:    Disable the clock atomically. Called with enable_lock held.
 *              This function must not sleep.
 *
 * @is_enabled: Queries the hardware to determine if the clock is enabled.
 *              This function must not sleep. Optional, if this op is not
 *              set then the enable count will be used.
 *
 * @disable_unused: Disable the clock atomically.  Only called from
 *              clk_disable_unused for gate clocks with special needs.
 *              Called with enable_lock held.  This function must not
 *              sleep.
 * @save_context: Save the context of the clock in prepration for poweroff.
 *
 * @restore_context: Restore the context of the clock after a restoration
 *              of power.
 *
 * @recalc_rate Recalculate the rate of this clock, by querying hardware. The
 *              parent rate is an input parameter.  It is up to the caller to
 *              ensure that the prepare_mutex is held across this call.
 *              Returns the calculated rate.  Optional, but recommended - if
 *              this op is not set then clock rate will be initialized to 0.
 *
 * @round_rate: Given a target rate as input, returns the closest rate actually
 *              supported by the clock. The parent rate is an input/output
 *              parameter.
 *
 * @determine_rate: Given a target rate as input, returns the closest rate
 *              actually supported by the clock, and optionally the parent clock
 *              that should be used to provide the clock rate.
 *
 * @set_parent: Change the input source of this clock; for clocks with multiple
 *              possible parents specify a new parent by passing in the index
 *              as a u8 corresponding to the parent in either the .parent_names
 *              or .parents arrays.  This function in affect translates an
 *              array index into the value programmed into the hardware.
 *              Returns 0 on success, -EERROR otherwise.
 *
 * @get_parent: Queries the hardware to determine the parent of a clock.  The
 *              return value is a u8 which specifies the index corresponding to
 *              the parent clock.  This index can be applied to either the
 *              .parent_names or .parents arrays.  In short, this function
 *              translates the parent value read from hardware into an array
 *              index.  Currently only called when the clock is initialized by
 *              __clk_init.  This callback is mandatory for clocks with
 *              multiple parents.  It is optional (and unnecessary) for clocks
 *              with 0 or 1 parents.
 *
 * @set_rate:   Change the rate of this clock. The requested rate is specified
 *              by the second argument, which should typically be the return
 *              of .round_rate call.  The third argument gives the parent rate
 *              which is likely helpful for most .set_rate implementation.
 *              Returns 0 on success, -EERROR otherwise.
 *
 * @set_rate_and_parent: Change the rate and the parent of this clock. The
 *              requested rate is specified by the second argument, which
 *              should typically be the return of .round_rate call.  The
 *              third argument gives the parent rate which is likely helpful
 *              for most .set_rate_and_parent implementation. The fourth
 *              argument gives the parent index. This callback is optional (and
 *              unnecessary) for clocks with 0 or 1 parents as well as
 *              for clocks that can tolerate switching the rate and the parent
 *              separately via calls to .set_parent and .set_rate.
 *              Returns 0 on success, -EERROR otherwise.
 *
 * @recalc_accuracy: Recalculate the accuracy of this clock. The clock accuracy
 *              is expressed in ppb (parts per billion). The parent accuracy is
 *              an input parameter.
 *              Returns the calculated accuracy.  Optional - if this op is not
 *              set then clock accuracy will be initialized to parent accuracy
 *              or 0 (perfect clock) if clock has no parent.
 *
 * @get_phase:  Queries the hardware to get the current phase of a clock.
 *              Returned values are 0-359 degrees on success, negative
 *              error codes on failure.
 *
 * @set_phase:  Shift the phase this clock signal in degrees specified
 *              by the second argument. Valid values for degrees are
 *              0-359. Return 0 on success, otherwise -EERROR.
 *
 * @get_duty_cycle: Queries the hardware to get the current duty cycle ratio
 *              of a clock. Returned values denominator cannot be 0 and must be
 *              superior or equal to the numerator.
 *
 * @set_duty_cycle: Apply the duty cycle ratio to this clock signal specified by
 *              the numerator (2nd argurment) and denominator (3rd  argument).
 *              Argument must be a valid ratio (denominator > 0
 *              and >= numerator) Return 0 on success, otherwise -EERROR.
 *
 * @init:       Perform platform-specific initialization magic.
 *              This is not not used by any of the basic clock types.
 *              Please consider other ways of solving initialization problems
 *              before using this callback, as its use is discouraged.
 *
 * @debug_init: Set up type-specific debugfs entries for this clock.  This
 *              is called once, after the debugfs directory entry for this
 *              clock has been created.  The dentry pointer representing that
 *              directory is provided as an argument.  Called with
 *              prepare_lock held.  Returns 0 on success, -EERROR otherwise.
 *
 *
 * The clk_enable/clk_disable and clk_prepare/clk_unprepare pairs allow
 * implementations to split any work between atomic (enable) and sleepable
 * (prepare) contexts.  If enabling a clock requires code that might sleep,
 * this must be done in clk_prepare.  Clock enable code that will never be
 * called in a sleepable context may be implemented in clk_enable.
 *
 * Typically, drivers will call clk_prepare when a clock may be needed later
 * (eg. when a device is opened), and clk_enable when the clock is actually
 * required (eg. from an interrupt). Note that clk_prepare MUST have been
 * called before clk_enable.
 */

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);
        int             (*save_context)(struct clk_hw *hw);
        void            (*restore_context)(struct clk_hw *hw);
        unsigned long   (*recalc_rate)(struct clk_hw *hw,
                                        unsigned long parent_rate);
        long            (*round_rate)(struct clk_hw *hw, unsigned long rate,
                                        unsigned long *parent_rate);
        int             (*determine_rate)(struct clk_hw *hw,
                                          struct clk_rate_request *req);
        int             (*set_parent)(struct clk_hw *hw, u8 index);
        u8              (*get_parent)(struct clk_hw *hw);
        int             (*set_rate)(struct clk_hw *hw, unsigned long rate,
                                    unsigned long parent_rate);
        int             (*set_rate_and_parent)(struct clk_hw *hw,
                                    unsigned long rate,
                                    unsigned long parent_rate, u8 index);
        unsigned long   (*recalc_accuracy)(struct clk_hw *hw,
                                           unsigned long parent_accuracy);
        int             (*get_phase)(struct clk_hw *hw);
        int             (*set_phase)(struct clk_hw *hw, int degrees);
        int             (*get_duty_cycle)(struct clk_hw *hw,
                                          struct clk_duty *duty);
        int             (*set_duty_cycle)(struct clk_hw *hw,
                                          struct clk_duty *duty);
        void            (*init)(struct clk_hw *hw);
        void            (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};
View Code

2.4 struct clk_hw(include/linux/clk-provider.h)

/**
 * struct clk_hw - handle for traversing from a struct clk to its corresponding
 * hardware-specific structure.  struct clk_hw should be declared within struct
 * clk_foo and then referenced by the struct clk instance that uses struct
 * clk_foo's clk_ops
 *
 * @core: pointer to the struct clk_core instance that points back to this
 * struct clk_hw instance
 *
 * @clk: pointer to the per-user struct clk instance that can be used to call
 * into the clk API
 *
 * @init: pointer to struct clk_init_data that contains the init data shared
 * with the common clock framework.
 */
struct clk_hw {
        struct clk_core *core;
        struct clk *clk;    // 由CCF维护,并且提供给用户使用
        const struct clk_init_data *init;   // 描述该clk的静态属性
};

2.5 struck clk_init_data(include/linux/clk-provider.h)

/**
 * struct clk_init_data - holds init data that's common to all clocks and is
 * shared between the clock provider and the common clock framework.
 *
 * @name: clock name
 * @ops: operations this clock supports
 * @parent_names: array of string names for all possible parents
 * @parent_data: array of parent data for all possible parents (when some
 *               parents are external to the clk controller)
 * @parent_hws: array of pointers to all possible parents (when all parents
 *              are internal to the clk controller)
 * @num_parents: number of possible parents
 * @flags: framework-level hints and quirks
 */
struct clk_init_data {
        const char              *name;   // 时钟的名字
        const struct clk_ops    *ops;      // 时钟的操作函数
        /* Only one of the following three should be assigned */
        const char              * const *parent_names;   // 时钟的父时钟名字列表
        const struct clk_parent_data    *parent_data;     // 时钟的父时钟数据列表
        const struct clk_hw             **parent_hws;      // 时钟的父时钟列表
        u8                      num_parents;          // 父时钟的个数
        unsigned long           flags;
};

2.6 数据结构之间的关系

为了更加清晰的了解CCF子系统各个数据结构作用和之间的关系,我们绘制其关系图,如下:

不知道你没有发现CCF子系统的数据结构中没有使用设备驱动模型,没包含struct device类型的变量。这应该是clk provider主要为系统提供时钟,而一些系统clk的初始化及使能会早于设备驱动模型的初始化,因此没有使用设备驱动模型,但是还是使用了引用计数功能的。

三、CCF时钟操作API

3.1 获取clock

驱动开发人员在进行操作设备的时钟之前,首先需要获取和该时钟关联的struct  clk指针,获取的接口如下:

struct clk *clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get(struct device *dev, const char *id);
void clk_put(struct clk *clk);
void devm_clk_put(struct device *dev, struct clk *clk);
struct clk *clk_get_sys(const char *dev_id, const char *con_id);
struct clk *of_clk_get(struct device_node *np, int index);
struct clk *of_clk_get_by_name(struct device_node *np, const char *name);
struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec);

其中clk_get,根据设备指针或者时钟别名参数查找时钟;

  • 如果使用设备树,支持根据设备节点,以及时钟别名(clocks-names属性)获取时钟,需要同时传入dev、con_id;
  • 如果没有指定设备树,支持根据设备名称、时钟别名获取时钟,dev、con_id可以任选一个,或者同时传入;
  • 时钟别名是在时钟初始化的时候调用samsung_clk_register_alias注册的,每个时钟别名对应一个clock lookup,clock lookup中包含了dev_id、con_id、clk、clk_hw等成员。

devm_clk_get,和clk_get一样,只是使用了device resource management,可以自动释放;

clk_put、devm_clk_put,get的反向操作,一般和对应的get API成对调用;

clk_get_sys,类似clk_get,不过使用device的name替代device结构;

of_clk_get、of_clk_get_by_name、of_clk_get_from_provider,device tree相关的接口,直接从相应的DTS node中,以index、name等为索引,获取clk;

3.2 clock配置

int clk_prepare(struct clk *clk)
void clk_unprepare(struct clk *clk)
static inline int clk_enable(struct clk *clk)
static inline void clk_disable(struct clk *clk)
static inline unsigned long clk_get_rate(struct clk *clk)
static inline int clk_set_rate(struct clk *clk, unsigned long rate)
static inline long clk_round_rate(struct clk *clk, unsigned long rate)
static inline int clk_set_parent(struct clk *clk, struct clk *parent)
static inline struct clk *clk_get_parent(struct clk *clk)
static inline int clk_prepare_enable(struct clk *clk)
static inline void clk_disable_unprepare(struct clk *clk)

clk_enable/clk_disable,使能/禁止clock;不会睡眠

clk_prepare/clk_unprepare,使能clock前的准备工作/禁止clock后的善后工作;可能会睡眠

clk_get_rate/clk_set_rate/clk_round_rate,clock频率的获取和设置,其中clk_set_rate可能会不成功(例如没有对应的分频比),此时会返回错误。如果要确保设置成功,则需要先调用clk_round_rate接口,得到和需要设置的rate比较接近的那个值;

clk_set_parent/clk_get_parent设置/获取clock的parent clock;

clk_prepare_enable,将clk_prepare和clk_enable组合起来,一起调用;

clk_disable_unprepare,将clk_disable和clk_unprepare组合起来,一起调用;

prepare/unprepare,enable/disable的说明:

  • 这两套API的本质,是把clock的使能/停止分为atomic和non-atomic两个阶段,以方便实现和调用。因此上面所说的“不会睡眠/可能会睡眠”,有两个角度的含义:
    • 一是告诉底层的clock driver,请把可能引起睡眠的操作,放到prepare/unprepare中实现,一定不能放到enable/disable中;
    • 二是提醒上层使用clock的driver,调用prepare/unprepare接口时可能会睡眠哦,千万不能在atomic上下文(例如中断处理中)调用,而调用enable/disable接口则可放心;
  • 另外,clock的开关为什么需要睡眠呢?这里举个例子,例如enable PLL clk,在启动PLL后,需要等待它稳定。而PLL的稳定时间是很长的,这段时间要把CPU交出(进程睡眠),不然就会浪费CPU;

最后,为什么会有合在一起的clk_prepare_enable/clk_disable_unprepare接口呢?如果调用者能确保是在non-atomic上下文中调用,就可以顺序调用prepare/enable、disable/unprepared,为了简单,framework就帮忙封装了这两个接口。

3.3 clock注册接口

struct clk *clk_register(struct device *dev, struct clk_hw *hw);
struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw);
void clk_unregister(struct clk *clk);
void devm_clk_unregister(struct device *dev, struct clk *clk);

通过clk_register接口可以将时钟struck clk_hw注册到内核,内核代码会将他们 转换为struct clk,并以树的形式组织在一起。

根据时钟种类的不同,CCF子系统将时钟分为fixed rate、gate、devider、mux、fixed factor、composite clock,每一类时钟都有相似的功能、相似的控制方式,因而可以使用相同的逻辑,统一处理,这充分体现了面向对象的思想。

3.3.1 fixed rate clock

这一类时钟具有固定的频率,不能开关、不能调整频率、不能选择parent、不需要提供任何的clk_ops回调函数,是最简单的一类时钟、

在include/linux/clk-provider.h文件中定义struct clk_fixed_rate结构抽象这一类clock,另外提供了一些接口,可以直接注册fixed rate clock,如下:

/*
 * DOC: Basic clock implementations common to many platforms
 *
 * Each basic clock hardware type is comprised of a structure describing the
 * clock hardware, implementations of the relevant callbacks in struct clk_ops,
 * unique flags for that hardware type, a registration function and an
 * alternative macro for static initialization
 */

/**
 * struct clk_fixed_rate - fixed-rate clock
 * @hw:         handle between common and hardware-specific interfaces
 * @fixed_rate: constant frequency of clock
 */
struct clk_fixed_rate {
        struct          clk_hw hw;
        unsigned long   fixed_rate;
        unsigned long   fixed_accuracy;
};

#define to_clk_fixed_rate(_hw) container_of(_hw, struct clk_fixed_rate, hw)

extern const struct clk_ops clk_fixed_rate_ops;
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                unsigned long fixed_rate);
struct clk_hw *clk_hw_register_fixed_rate(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                unsigned long fixed_rate);
struct clk *clk_register_fixed_rate_with_accuracy(struct device *dev,
                const char *name, const char *parent_name, unsigned long flags,
                unsigned long fixed_rate, unsigned long fixed_accuracy);
void clk_unregister_fixed_rate(struct clk *clk);
struct clk_hw *clk_hw_register_fixed_rate_with_accuracy(struct device *dev,
                const char *name, const char *parent_name, unsigned long flags,
                unsigned long fixed_rate, unsigned long fixed_accuracy);
void clk_hw_unregister_fixed_rate(struct clk_hw *hw);

void of_fixed_clk_setup(struct device_node *np);
3.3.2 gate clock

这一类clock只可开关(会提供.enable/.disable回调),可使用下面接口注册:

/**
 * struct clk_gate - gating clock
 *
 * @hw:         handle between common and hardware-specific interfaces
 * @reg:        register controlling gate
 * @bit_idx:    single bit controlling gate
 * @flags:      hardware-specific flags
 * @lock:       register lock
 *
 * Clock which can gate its output.  Implements .enable & .disable
 *
 * Flags:
 * CLK_GATE_SET_TO_DISABLE - by default this clock sets the bit at bit_idx to
 *      enable the clock.  Setting this flag does the opposite: setting the bit
 *      disable the clock and clearing it enables the clock
 * CLK_GATE_HIWORD_MASK - The gate settings are only in lower 16-bit
 *      of this register, and mask of gate bits are in higher 16-bit of this
 *      register.  While setting the gate bits, higher 16-bit should also be
 *      updated to indicate changing gate bits.
 * CLK_GATE_BIG_ENDIAN - by default little endian register accesses are used for
 *      the gate register.  Setting this flag makes the register accesses big
 *      endian.
 */
struct clk_gate {
        struct clk_hw hw;
        void __iomem    *reg;     // 控制该clock开关的寄存器地址(虚拟地址)
        u8              bit_idx;    // 控制clock开关的bit位 
        u8              flags;
        spinlock_t      *lock;   //如果clock开关是独享的,可以使用自旋锁
};

#define to_clk_gate(_hw) container_of(_hw, struct clk_gate, hw)

#define CLK_GATE_SET_TO_DISABLE         BIT(0)
#define CLK_GATE_HIWORD_MASK            BIT(1)
#define CLK_GATE_BIG_ENDIAN             BIT(2)

extern const struct clk_ops clk_gate_ops;
struct clk *clk_register_gate(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                void __iomem *reg, u8 bit_idx,
                u8 clk_gate_flags, spinlock_t *lock);
struct clk_hw *clk_hw_register_gate(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                void __iomem *reg, u8 bit_idx,
                u8 clk_gate_flags, spinlock_t *lock);
void clk_unregister_gate(struct clk *clk);
void clk_hw_unregister_gate(struct clk_hw *hw);
int clk_gate_is_enabled(struct clk_hw *hw);
3.3.3 driver clock

这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate回调),可通过下面接口注册:

/**
 * struct clk_divider - adjustable divider clock
 *
 * @hw:         handle between common and hardware-specific interfaces
 * @reg:        register containing the divider
 * @shift:      shift to the divider bit field
 * @width:      width of the divider bit field
 * @table:      array of value/divider pairs, last entry should have div = 0
 * @lock:       register lock
 *
 * Clock with an adjustable divider affecting its output frequency.  Implements
 * .recalc_rate, .set_rate and .round_rate
 *
 * Flags:
 * CLK_DIVIDER_ONE_BASED - by default the divisor is the value read from the
 *      register plus one.  If CLK_DIVIDER_ONE_BASED is set then the divider is
 *      the raw value read from the register, with the value of zero considered
 *      invalid, unless CLK_DIVIDER_ALLOW_ZERO is set.
 * CLK_DIVIDER_POWER_OF_TWO - clock divisor is 2 raised to the value read from
 *      the hardware register
 * CLK_DIVIDER_ALLOW_ZERO - Allow zero divisors.  For dividers which have
 *      CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor.
 *      Some hardware implementations gracefully handle this case and allow a
 *      zero divisor by not modifying their input clock
 *      (divide by one / bypass).
 * CLK_DIVIDER_HIWORD_MASK - The divider settings are only in lower 16-bit
 *      of this register, and mask of divider bits are in higher 16-bit of this
 *      register.  While setting the divider bits, higher 16-bit should also be
 *      updated to indicate changing divider bits.
 * CLK_DIVIDER_ROUND_CLOSEST - Makes the best calculated divider to be rounded
 *      to the closest integer instead of the up one.
 * CLK_DIVIDER_READ_ONLY - The divider settings are preconfigured and should
 *      not be changed by the clock framework.
 * CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED
 *      except when the value read from the register is zero, the divisor is
 *      2^width of the field.
 * CLK_DIVIDER_BIG_ENDIAN - By default little endian register accesses are used
 *      for the divider register.  Setting this flag makes the register accesses
 *      big endian.
 */
struct clk_divider {
        struct clk_hw   hw;
        void __iomem    *reg;  // 控制clock分频比的寄存器地址
        u8              shift;        // 控制分频比的bit在寄存器的便宜
        u8              width;        // 控制分频比的bit个数
        u8              flags;
        const struct clk_div_table      *table;
        spinlock_t      *lock;
};

#define clk_div_mask(width)     ((1 << (width)) - 1)
#define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw)

#define CLK_DIVIDER_ONE_BASED           BIT(0)
#define CLK_DIVIDER_POWER_OF_TWO        BIT(1)
#define CLK_DIVIDER_ALLOW_ZERO          BIT(2)
#define CLK_DIVIDER_HIWORD_MASK         BIT(3)
#define CLK_DIVIDER_ROUND_CLOSEST       BIT(4)
#define CLK_DIVIDER_READ_ONLY           BIT(5)
#define CLK_DIVIDER_MAX_AT_ZERO         BIT(6)
#define CLK_DIVIDER_BIG_ENDIAN          BIT(7)

extern const struct clk_ops clk_divider_ops;
extern const struct clk_ops clk_divider_ro_ops;

unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
                unsigned int val, const struct clk_div_table *table,
                unsigned long flags, unsigned long width);
long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
                               unsigned long rate, unsigned long *prate,
                               const struct clk_div_table *table,
                               u8 width, unsigned long flags);
long divider_ro_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
                                  unsigned long rate, unsigned long *prate,
                                  const struct clk_div_table *table, u8 width,
                                  unsigned long flags, unsigned int val);
int divider_get_val(unsigned long rate, unsigned long parent_rate,
                const struct clk_div_table *table, u8 width,
                unsigned long flags);

struct clk *clk_register_divider(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                void __iomem *reg, u8 shift, u8 width,
                u8 clk_divider_flags, spinlock_t *lock);
struct clk_hw *clk_hw_register_divider(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                void __iomem *reg, u8 shift, u8 width,
                u8 clk_divider_flags, spinlock_t *lock);
struct clk *clk_register_divider_table(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                void __iomem *reg, u8 shift, u8 width,
                u8 clk_divider_flags, const struct clk_div_table *table,
                spinlock_t *lock);
struct clk_hw *clk_hw_register_divider_table(struct device *dev,
                const char *name, const char *parent_name, unsigned long flags,
                void __iomem *reg, u8 shift, u8 width,
                u8 clk_divider_flags, const struct clk_div_table *table,
                spinlock_t *lock);
void clk_unregister_divider(struct clk *clk);
void clk_hw_unregister_divider(struct clk_hw *hw);

其中struct clk_div_table:

struct clk_div_table {
        unsigned int    val;    // 寄存器值
        unsigned int    div;    // 分频值
};
3.3.4 mux lock

这一类clock可以选择多个parent,因为会实现.get_parent/.set_parent/.recalc_rate回调,可通过下面接口注册:

/**
 * struct clk_mux - multiplexer clock
 *
 * @hw:         handle between common and hardware-specific interfaces
 * @reg:        register controlling multiplexer
 * @table:      array of register values corresponding to the parent index
 * @shift:      shift to multiplexer bit field
 * @mask:       mask of mutliplexer bit field
 * @flags:      hardware-specific flags
 * @lock:       register lock
 *
 * Clock with multiple selectable parents.  Implements .get_parent, .set_parent
 * and .recalc_rate
 *
 * Flags:
 * CLK_MUX_INDEX_ONE - register index starts at 1, not 0
 * CLK_MUX_INDEX_BIT - register index is a single bit (power of two)
 * CLK_MUX_HIWORD_MASK - The mux settings are only in lower 16-bit of this
 *      register, and mask of mux bits are in higher 16-bit of this register.
 *      While setting the mux bits, higher 16-bit should also be updated to
 *      indicate changing mux bits.
 * CLK_MUX_READ_ONLY - The mux registers can't be written, only read in the
 *      .get_parent clk_op.
 * CLK_MUX_ROUND_CLOSEST - Use the parent rate that is closest to the desired
 *      frequency.
 * CLK_MUX_BIG_ENDIAN - By default little endian register accesses are used for
 *      the mux register.  Setting this flag makes the register accesses big
 *      endian.
 */
struct clk_mux {
        struct clk_hw   hw;
        void __iomem    *reg;
        u32             *table;
        u32             mask;
        u8              shift;
        u8              flags;
        spinlock_t      *lock;
};

#define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw)

#define CLK_MUX_INDEX_ONE               BIT(0)
#define CLK_MUX_INDEX_BIT               BIT(1)
#define CLK_MUX_HIWORD_MASK             BIT(2)
#define CLK_MUX_READ_ONLY               BIT(3) /* mux can't be changed */
#define CLK_MUX_ROUND_CLOSEST           BIT(4)
#define CLK_MUX_BIG_ENDIAN              BIT(5)

extern const struct clk_ops clk_mux_ops;
extern const struct clk_ops clk_mux_ro_ops;

struct clk *clk_register_mux(struct device *dev, const char *name,
                const char * const *parent_names, u8 num_parents,
                unsigned long flags,
                void __iomem *reg, u8 shift, u8 width,
                u8 clk_mux_flags, spinlock_t *lock);
struct clk_hw *clk_hw_register_mux(struct device *dev, const char *name,
                const char * const *parent_names, u8 num_parents,
                unsigned long flags,
                void __iomem *reg, u8 shift, u8 width,
                u8 clk_mux_flags, spinlock_t *lock);

struct clk *clk_register_mux_table(struct device *dev, const char *name,
                const char * const *parent_names, u8 num_parents,
                unsigned long flags,
                void __iomem *reg, u8 shift, u32 mask,
                u8 clk_mux_flags, u32 *table, spinlock_t *lock);
struct clk_hw *clk_hw_register_mux_table(struct device *dev, const char *name,
                const char * const *parent_names, u8 num_parents,
                unsigned long flags,
                void __iomem *reg, u8 shift, u32 mask,
                u8 clk_mux_flags, u32 *table, spinlock_t *lock);

int clk_mux_val_to_index(struct clk_hw *hw, u32 *table, unsigned int flags,
                         unsigned int val);
unsigned int clk_mux_index_to_val(u32 *table, unsigned int flags, u8 index);

void clk_unregister_mux(struct clk *clk);
void clk_hw_unregister_mux(struct clk_hw *hw);
3.3.5 fixed factor clock

这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。由于parent clock的频率可以改变,因而fix factor clock也可该改变频率,因此也会提供.recalc_rate/.set_rate/.round_rate等回调。

void of_fixed_factor_clk_setup(struct device_node *node);

/**
 * struct clk_fixed_factor - fixed multiplier and divider clock
 *
 * @hw:         handle between common and hardware-specific interfaces
 * @mult:       multiplier
 * @div:        divider
 *
 * Clock with a fixed multiplier and divider. The output frequency is the
 * parent clock rate divided by div and multiplied by mult.
 * Implements .recalc_rate, .set_rate and .round_rate
 */

struct clk_fixed_factor {
        struct clk_hw   hw;
        unsigned int    mult;
        unsigned int    div;
};

#define to_clk_fixed_factor(_hw) container_of(_hw, struct clk_fixed_factor, hw)

extern const struct clk_ops clk_fixed_factor_ops;
struct clk *clk_register_fixed_factor(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                unsigned int mult, unsigned int div);
void clk_unregister_fixed_factor(struct clk *clk);
struct clk_hw *clk_hw_register_fixed_factor(struct device *dev,
                const char *name, const char *parent_name, unsigned long flags,
                unsigned int mult, unsigned int div);
void clk_hw_unregister_fixed_factor(struct clk_hw *hw);
3.3.6 composite clock

顾名思义,就是mux、divider、gate等clock的组合,可通过下面接口注册:

/***
 * struct clk_composite - aggregate clock of mux, divider and gate clocks
 *
 * @hw:         handle between common and hardware-specific interfaces
 * @mux_hw:     handle between composite and hardware-specific mux clock
 * @rate_hw:    handle between composite and hardware-specific rate clock
 * @gate_hw:    handle between composite and hardware-specific gate clock
 * @mux_ops:    clock ops for mux
 * @rate_ops:   clock ops for rate
 * @gate_ops:   clock ops for gate
 */
struct clk_composite {
        struct clk_hw   hw;
        struct clk_ops  ops;

        struct clk_hw   *mux_hw;
        struct clk_hw   *rate_hw;
        struct clk_hw   *gate_hw;

        const struct clk_ops    *mux_ops;
        const struct clk_ops    *rate_ops;
        const struct clk_ops    *gate_ops;
};

#define to_clk_composite(_hw) container_of(_hw, struct clk_composite, hw)

struct clk *clk_register_composite(struct device *dev, const char *name,
                const char * const *parent_names, int num_parents,
                struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
                struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
                struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
                unsigned long flags);
void clk_unregister_composite(struct clk *clk);
struct clk_hw *clk_hw_register_composite(struct device *dev, const char *name,
                const char * const *parent_names, int num_parents,
                struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
                struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
                struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
                unsigned long flags);
void clk_hw_unregister_composite(struct clk_hw *hw);

3.4 其他接口

int clk_notifier_register(struct clk *clk, struct notifier_block *nb);
int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);

这两个notify接口,用于注册/注销 clock rate改变的通知。例如某个driver关心某个clock,期望这个clock的rate改变时,通知到自己,就可以注册一个notify。

四、usb主机控制器驱动回顾

我们首先来回顾一下,我们在之前的设备驱动源码分析中有没有接触到clock consumer相关的代码呢?

实际上是有的,不知道你有没有留意到,在linux驱动移植-usb主机控制器驱动中我们介绍名字为"s3c2410-ohci"的platform设备和驱动注册的时候,我们分析了ohci_hcd_s3c2410_driver驱动的probe函数:ohci_hcd_s3c2410_probe。

4.1 ohci_hcd_s3c2410_driver

static struct platform_driver ohci_hcd_s3c2410_driver = {
        .probe          = ohci_hcd_s3c2410_probe,
        .remove         = ohci_hcd_s3c2410_remove,
        .shutdown       = usb_hcd_platform_shutdown,
        .driver         = {
                .name   = "s3c2410-ohci",
                .pm     = &ohci_hcd_s3c2410_pm_ops,
                .of_match_table = ohci_hcd_s3c2410_dt_ids,
        },
};

4.2 ohci_hcd_s3c2410_probe

ohci_hcd_s3c2410_probe的函数流程不是我们这一节的主要内容,有兴趣的去回顾一下linux驱动移植-usb主机控制器驱动

/**
 * ohci_hcd_s3c2410_probe - initialize S3C2410-based HCDs
 * Context: !in_interrupt()
 *
 * Allocates basic resources for this USB host controller, and
 * then invokes the start() method for the HCD associated with it
 * through the hotplug entry's driver_data.
 *
 */
static int ohci_hcd_s3c2410_probe(struct platform_device *dev)
{
        struct usb_hcd *hcd = NULL;
        struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev);
        int retval;

        s3c2410_usb_set_power(info, 1, 1);
        s3c2410_usb_set_power(info, 2, 1);

        hcd = usb_create_hcd(&ohci_s3c2410_hc_driver, &dev->dev, "s3c24xx");   // 分配主机控制器usb_hcd结构,并初始化,绑定hc_driver等
        if (hcd == NULL)
                return -ENOMEM;

        hcd->rsrc_start = dev->resource[0].start;               // usb主机控制器起始地址
        hcd->rsrc_len   = resource_size(&dev->resource[0]);     // 映射内存长度

        hcd->regs = devm_ioremap_resource(&dev->dev, &dev->resource[0]);  // 物理地址映射到虚拟地址
        if (IS_ERR(hcd->regs)) {
                retval = PTR_ERR(hcd->regs);
                goto err_put;
        }

        clk = devm_clk_get(&dev->dev, "usb-host");         // 获取usb-host时钟
        if (IS_ERR(clk)) {
                dev_err(&dev->dev, "cannot get usb-host clock\n");
                retval = PTR_ERR(clk);
                goto err_put;
        }

        usb_clk = devm_clk_get(&dev->dev, "usb-bus-host"); // 获取usb-bus-host时钟
        if (IS_ERR(usb_clk)) {
                dev_err(&dev->dev, "cannot get usb-bus-host clock\n");
                retval = PTR_ERR(usb_clk);
                goto err_put;
        }

        s3c2410_start_hc(dev, hcd);    // 使能时钟

        retval = usb_add_hcd(hcd, dev->resource[1].start, 0);  // 向linux内核注册usb主机控制器驱动ohci_s3c2410_hc_driver,创建根hub设备,并注册到内核设备链表
        if (retval != 0)
                goto err_ioremap;

        device_wakeup_enable(hcd->self.controller);
        return 0;

 err_ioremap:
        s3c2410_stop_hc(dev);

 err_put:
        usb_put_hcd(hcd);
        return retval;
}

我们后面重点来分析下面两行代码:

clk = devm_clk_get(&dev->dev, "usb-host");         // 获取usb-host时钟
usb_clk = devm_clk_get(&dev->dev, "usb-bus-host"); // 获取usb-bus-host时钟

五、devm_clk_get函数分析

我们直接全局搜索该函数:

root@zhengyang:/work/sambashare/linux-5.2.8# grep "devm_clk_get(struct " * -nR
drivers/clk/clk-devres.c:12:struct clk *devm_clk_get(struct device *dev, const char *id)
include/linux/clk.h:381:struct clk *devm_clk_get(struct device *dev, const char *id);
include/linux/clk.h:724:static inline struct clk *devm_clk_get(struct device *dev, const char *id)

函数定义位于drivers/clk/clk-devres.c文件,从这里我们也不难猜出linux内核时钟相关代码都是放在drivers/clk文件夹下的。

5.1 devm_clk_get

struct clk *devm_clk_get(struct device *dev, const char *id)
{
        struct clk **ptr, *clk;

        ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL);
        if (!ptr)
                return ERR_PTR(-ENOMEM);

        clk = clk_get(dev, id);
        if (!IS_ERR(clk)) {
                *ptr = clk;
                devres_add(dev, ptr);
        } else {
                devres_free(ptr);
        }

        return clk;
}

devm_clk_get函数有两个参数:

  • 第一个参数为linux内核设备结构指针struct *dev,可以通过设备名字/设备节点获取时钟;
  • 第二个参数是一个字符指针,用于指向一个字符串,比如“usb_host”,这里起到一个唯一标识的作用,用来指定我们需要设置硬件上的哪部分时钟;

这个函数调用了clk_get函数,其他的可以忽略。

5.2 clk_get

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;   // 如果指定了dev,获取设备名称
        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); // 未使用设备树
}

如果使用了设备树,则会调用of_clk_get_hw函数,否则执行了 __clk_get_sys(NULL, NULL, con_id)。

5.3 使用设备树

如果我们使用了设备树,比如在dts配置了clock consumer:

 mmc0:mmc0@0x12345678{
  compatible = "xx,xx-mmc0";
  ......
  clocks = <&peri PERI_MCI0>;//指定mmc0的时钟来自PERI_MCI0
  clocks-names = "mmc0";     //时钟名,调用devm_clk_get获取时钟时,可以传入该名字
        ......
 };

以mmc的设备节点为例,上述设备节点指定了时钟来自PERI_MCI0,并将所指定的时钟给它命名为"mmc0"。

当在驱动中使用API接口:

clk = devm_clk_get(&pdev->dev, NULL); // 或者devm_clk_get(&pdev->dev, "mmc0")

最终执行的是of_clk_get_hw函数,of_clk_get_hw函数定义在drivers/clk/clk.c:

struct clk_hw *of_clk_get_hw(struct device_node *np, int index,    // index = 0  con_id="mmc0"
                             const char *con_id)
{
        int ret;
        struct clk_hw *hw;
        struct of_phandle_args clkspec;

        ret = of_parse_clkspec(np, index, con_id, &clkspec);  // 重点
        if (ret)
                return ERR_PTR(ret);

        hw = of_clk_get_hw_from_clkspec(&clkspec);
        of_node_put(clkspec.np);

        return hw;
}
5.3.1 of_parse_clkspec
/*
 * Beware the return values when np is valid, but no clock provider is found.
 * If name == NULL, the function returns -ENOENT.
 * If name != NULL, the function returns -EINVAL. This is because
 * of_parse_phandle_with_args() is called even if of_property_match_string()
 * returns an error.
 */
static int of_parse_clkspec(const struct device_node *np, int index,
                            const char *name, struct of_phandle_args *out_args)  // name传入了con_id
{
        int ret = -ENOENT;

        /* Walk up the tree of devices looking for a clock property that matches */
        while (np) {
                /*
                 * 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_parse_phandle_with_args() will
                 * return -EINVAL.
                 */
                if (name)
                        index = of_property_match_string(np, "clock-names", name);  // 根据name获取index
                ret = of_parse_phandle_with_args(np, "clocks", "#clock-cells",
                                                 index, out_args);
                if (!ret)
                        break;
                if (name && index >= 0)
                        break;

                /*
                 * 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;
                index = 0;
        }

        return ret;
}

如果指定了name,即devm_clk_get传入了第二个参数con_id,则会调用of_property_match_string函数,查询con_id在设备节点clock-names属性值中的位置,并返回对应索引号,索引从0开始;比如示例mmc0节点中的"mmc0"的索引为0;

调用of_parse_phandle_with_args函数,根据索引从clocks属性中获取时钟,比如示例mmc0节点中的0索引对应的时钟为<&peri PERI_MCI0>,如果时钟解析成功则返回0;

5.4 未使用设备树

在没有使用设备树的请求下,执行__clk_get_sys函数,函数定义如下:

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);
}

__clk_get_sys里面通过clk_find_hw函数;

5.4.1 clk_find_hw
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;
}

clk_find_hw里面通过clk_find函数;来查找我们传入的时钟别名,并返回clk_lookup类型的一个指针clk_find函数里面就是我们最终需要查看的内容。

clk_lookup定义在include/linux/clkdev.h文件中:

struct clk_lookup {
        struct list_head        node;
        const char              *dev_id;   // 设备名称
        const char              *con_id;   // 时钟别名
        struct clk              *clk;      // 时钟对应的struck clk结构
        struct clk_hw           *clk_hw;   // 时钟对应的struct clk_hw结构
};
5.4.2 clk_find
/*
 * 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)                                 // 优先级高,占比2
                best_possible += 2;
        if (con_id)                                 // 优先级低  占比1  
                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))  // 如果指定了设备名称,比较设备名称dev_id和当前clk_lookup节点p->dev_id
                                continue;
                        match += 2;
                }
                if (p->con_id) {
                        if (!con_id || strcmp(p->con_id, con_id))  // 如果指定了时钟别名con_id,比较时钟别名con_id和当前clk_lookup节点p->con_id
                                continue;
                        match += 1;
                }

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

list_for_each_entry函数从clocks的链表中的表头,它受clocks_lock保护,开始查找和我们传入的时钟别名相比较,如果找到了就返回一个指向该时钟clk_lookup类型的指针。

dev_mclk_get函数到此为止分析完毕,这里还需要补充一点,那就是在没有设备树的情景下,第二个参数时钟别名在哪里定义的呢。

六、clocks链表初始化

clocks是一个双向链表,定义在drivers/clk/clkdev.c文件中,关于双向链表内容具体参考Linux内核中经典链表 list_head 常见使用方法解析

static LIST_HEAD(clocks);

那双向链表clocks何时构建的的呢,我们再次定位到我们定位到arch/arm/mach-s3c24xx/mach-smdk2440.c文件中如下代码:

MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
        .atag_offset    = 0x100,

        .init_irq       = s3c2440_init_irq,
        .map_io         = smdk2440_map_io,
        .init_machine   = smdk2440_machine_init,
        .init_time      = smdk2440_init_time,
MACHINE_END
               

6.1 smdk2440_init_time

smdk2440_init_time函数用于初始化linux内核的时钟:

static void __init smdk2440_init_time(void)
{
        s3c2440_init_clocks(12000000);
        samsung_timer_init();
}

6.2 s3c2440_init_clocks

s3c2440_init_clocks定义在arch/arm/mach-s3c24xx/common.c文件中:

void __init s3c2440_init_clocks(int xtal)
{
        s3c2410_common_clk_init(NULL, xtal, 1, S3C24XX_VA_CLKPWR);    // 1对应的枚举变量S3C2440
}

6.3 s3c2410_common_clk_init

s3c2410_common_clk_init定义在drivers/clk/samsung/clk-s3c2410.c:

void __init s3c2410_common_clk_init(struct device_node *np, unsigned long xti_f,   // np为NULL、xti_f=12000000、current_soc=1
                                    int current_soc,
                                    void __iomem *base)
{
        struct samsung_clk_provider *ctx;
        reg_base = base;

        if (np) {      // 不成立
                reg_base = of_iomap(np, 0);
                if (!reg_base)
                        panic("%s: failed to map registers\n", __func__);
        }

        ctx = samsung_clk_init(np, reg_base, NR_CLKS);                // 初始化时钟,主要是给ctx分配空间和填充一些数据,如寄存器基地址

        /* Register external clocks only in non-dt cases */
        if (!np)
                s3c2410_common_clk_register_fixed_ext(ctx, xti_f);   // 注册通用的外部固定时钟,即注册晶振

        if (current_soc == S3C2410) {
                if (_get_rate("xti") == 12 * MHZ) {
                        s3c2410_plls[mpll].rate_table = pll_s3c2410_12mhz_tbl;
                        s3c2410_plls[upll].rate_table = pll_s3c2410_12mhz_tbl;
                }

                /* Register PLLs. */
                samsung_clk_register_pll(ctx, s3c2410_plls,
                                ARRAY_SIZE(s3c2410_plls), reg_base);

        } else { /* S3C2440, S3C2442 */
                if (_get_rate("xti") == 12 * MHZ) {
                        /*
                         * plls follow different calculation schemes, with the
                         * upll following the same scheme as the s3c2410 plls
                         */
                        s3c244x_common_plls[mpll].rate_table =
                                                        pll_s3c244x_12mhz_tbl;
                        s3c244x_common_plls[upll].rate_table =
                                                        pll_s3c2410_12mhz_tbl;
                }

                /* Register PLLs. */
                samsung_clk_register_pll(ctx, s3c244x_common_plls,              // 注册clock mpll、clock upll
                                ARRAY_SIZE(s3c244x_common_plls), reg_base);
        }
  /* Register common internal clocks. */
        samsung_clk_register_mux(ctx, s3c2410_common_muxes,     // 注册clock mux
                        ARRAY_SIZE(s3c2410_common_muxes));
        samsung_clk_register_div(ctx, s3c2410_common_dividers,   // 注册clock divider
                        ARRAY_SIZE(s3c2410_common_dividers));
        samsung_clk_register_gate(ctx, s3c2410_common_gates,      // 注册clock gate
                ARRAY_SIZE(s3c2410_common_gates));

        if (current_soc == S3C2440 || current_soc == S3C2442) {     // 因为2440和2440是相似的,这里实在2410基础上进行补充
                samsung_clk_register_div(ctx, s3c244x_common_dividers,
                                ARRAY_SIZE(s3c244x_common_dividers));
                samsung_clk_register_gate(ctx, s3c244x_common_gates,
                                ARRAY_SIZE(s3c244x_common_gates));
                samsung_clk_register_mux(ctx, s3c244x_common_muxes,
                                ARRAY_SIZE(s3c244x_common_muxes));
                samsung_clk_register_fixed_factor(ctx, s3c244x_common_ffactor,
                                ARRAY_SIZE(s3c244x_common_ffactor));
        }

        /* Register SoC-specific clocks. */
        switch (current_soc) {
        case S3C2410:
                samsung_clk_register_div(ctx, s3c2410_dividers,
                                ARRAY_SIZE(s3c2410_dividers));
                samsung_clk_register_fixed_factor(ctx, s3c2410_ffactor,
                                ARRAY_SIZE(s3c2410_ffactor));
                samsung_clk_register_alias(ctx, s3c2410_aliases,
                        ARRAY_SIZE(s3c2410_aliases));
                break;
        case S3C2440:            // 2440特有的时钟注册
                samsung_clk_register_mux(ctx, s3c2440_muxes,
                                ARRAY_SIZE(s3c2440_muxes));
                samsung_clk_register_gate(ctx, s3c2440_gates,
                                ARRAY_SIZE(s3c2440_gates));
                break;
        case S3C2442:
                samsung_clk_register_mux(ctx, s3c2442_muxes,
                                ARRAY_SIZE(s3c2442_muxes));
                samsung_clk_register_fixed_factor(ctx, s3c2442_ffactor,
                                ARRAY_SIZE(s3c2442_ffactor));
                break;
        }
  /*
         * Register common aliases at the end, as some of the aliased clocks
         * are SoC specific.
         */
        samsung_clk_register_alias(ctx, s3c2410_common_aliases,           // 为每一个struct clk_hw注册一个clock lookup,从而可以通过clk_get获取指定时钟
                ARRAY_SIZE(s3c2410_common_aliases));

        if (current_soc == S3C2440 || current_soc == S3C2442) {
                samsung_clk_register_alias(ctx, s3c244x_common_aliases,
                        ARRAY_SIZE(s3c244x_common_aliases));
        }

        samsung_clk_sleep_init(reg_base, s3c2410_clk_regs,
                               ARRAY_SIZE(s3c2410_clk_regs));

        samsung_clk_of_add_provider(np, ctx);
}

s3c2410_common_clk_init函数用来进行时钟的初始化,函数流程如下:

  • 初始化时钟,主要是给ctx分配空间和填充一些数据,如寄存器基地址;
  • 注册通用的外部固定时钟,即注册晶振;
  • 注册clock mpll、clock upll;
  • 注册clock mux;
  • 注册clock divider;
  • 注册clock gate;
  • 注册clock fixed factor;
  • 为每一个struct clk_hw注册一个clock lookup,从而可以通过clk_get获取指定时钟

samsung_clk_provider可以理解为CCF子系统时钟的上下文,成员clk_data保存了我们注册的所有时钟,其定义在 drivers/clk/samsung/clk.h:

/**
 * struct samsung_clk_provider: information about clock provider
 * @reg_base: virtual address for the register base.
 * @lock: maintains exclusion between callbacks for a given clock-provider.
 * @clk_data: holds clock related data like clk_hw* and number of clocks.
 */
struct samsung_clk_provider {
        void __iomem *reg_base;
        struct device *dev;
        spinlock_t lock;
        /* clk_data must be the last entry due to variable length 'hws' array */
        struct clk_hw_onecell_data clk_data;
};

clk_hw_onecell_data定义在 include/linux/clk-provider.h:

struct clk_hw_onecell_data {
        unsigned int num;        // 注册的时钟数量
        struct clk_hw *hws[];    // 数组类型,存储每一个时钟对应的struc clk_hw *结构;
};
6.3.1 samsung_clk_init

samsung_clk_init定义在drivers/clk/samsung/clk.c:

/* setup the essentials required to support clock lookup using ccf */
struct samsung_clk_provider *__init samsung_clk_init(struct device_node *np,
                        void __iomem *base, unsigned long nr_clks)
{
        struct samsung_clk_provider *ctx;
        int i;

        ctx = kzalloc(sizeof(struct samsung_clk_provider) +
                      sizeof(*ctx->clk_data.hws) * nr_clks, GFP_KERNEL);    // 动态申请内存
        if (!ctx)
                panic("could not allocate clock provider context.\n");

        for (i = 0; i < nr_clks; ++i)               
                ctx->clk_data.hws[i] = ERR_PTR(-ENOENT);

        ctx->reg_base = base;                 // 初始化
        ctx->clk_data.num = nr_clks;
        spin_lock_init(&ctx->lock);          // 初始化自旋锁

        return ctx;
}
6.3.2 注册晶振

s3c2410_common_clk_register_fixed_ext定义在drivers/clk/samsung/clk-s3c2410.c

static void __init s3c2410_common_clk_register_fixed_ext(
                struct samsung_clk_provider *ctx,
                unsigned long xti_f)     // 时钟频率12000000
{
        struct samsung_clock_alias xti_alias = ALIAS(XTI, NULL, "xtal");

        s3c2410_common_frate_clks[0].fixed_rate = xti_f;
        samsung_clk_register_fixed_rate(ctx, s3c2410_common_frate_clks,
                                ARRAY_SIZE(s3c2410_common_frate_clks));  // 时钟个数为1

        samsung_clk_register_alias(ctx, &xti_alias, 1);
}

s3c2410_common_clk_register_fixed_ext用于注册通用的外部固定时钟(晶振),其中静态变量s3c2410_common_frate_clks结构体定义在drivers/clk/samsung/clk.h:

/**
 * struct samsung_fixed_rate_clock: information about fixed-rate clock
 * @id: platform specific id of the clock.
 * @name: name of this fixed-rate clock.
 * @parent_name: optional parent clock name.
 * @flags: optional fixed-rate clock flags.
 * @fixed-rate: fixed clock rate of this clock.
 */
struct samsung_fixed_rate_clock {
        unsigned int            id;
        char                    *name;         // 时钟名称
        const char              *parent_name;
        unsigned long           flags;
        unsigned long           fixed_rate;   // 时钟频率
};

#define FRATE(_id, cname, pname, f, frate)              \
        {                                               \
                .id             = _id,                  \
                .name           = cname,                \
                .parent_name    = pname,                \
                .flags          = f,                    \
                .fixed_rate     = frate,                \
        }

/*
 * fixed rate clocks generated outside the soc
 * Only necessary until the devicetree-move is complete
 */
#define XTI     1
static struct samsung_fixed_rate_clock s3c2410_common_frate_clks[] __initdata = {
        FRATE(XTI, "xti", NULL, 0, 0),
};
View Code

s3c2410_common_clk_register_fixed_ext函数调用如图所示:

 clk_hw_register_fixed_rate_with_accuracy:

  • 函数会动态分配一个struct clk_fixed_rate,初始化其成员fixed_rate=12000000,fixed_accuracy=0,同时初始化hw.init静态属性,设置时钟操作init.ops=&clk_fixed_rate_ops;
  • 调用clk_hw_register:
    • 动态分配hw->core,并初始化hw->core;
    • 初始化hw->core->clks哈希双向链表,创建一个hw->clk链接到hw->core->clks上;
6.3.3 注册时钟PLL

接着是注册时钟PLL,samsung_clk_register_pll定义在drivers/clk/samsung/clk-pll.c文件中,其内部是调用clk_hw_register实现的,这里就不分析源码了。

我们看一下函数的参数s3c2410_plls,定义在drivers/clk/samsung/clk-s3c2410.c:

static struct samsung_pll_clock s3c2410_plls[] __initdata = {
        [mpll] = PLL(pll_s3c2410_mpll, MPLL, "mpll", "xti",
                                                LOCKTIME, MPLLCON, NULL),
        [upll] = PLL(pll_s3c2410_upll, UPLL, "upll", "xti",
                                                LOCKTIME, UPLLCON, NULL),
};

其中struct samsung_pll_clock 结构体定义在drivers/clk/samsung/clk.h:

/**
 * struct samsung_pll_clock: information about pll clock
 * @id: platform specific id of the clock.
 * @name: name of this pll clock.
 * @parent_name: name of the parent clock.
 * @flags: optional flags for basic clock.
 * @con_offset: offset of the register for configuring the PLL.
 * @lock_offset: offset of the register for locking the PLL.
 * @type: Type of PLL to be registered.
 */
struct samsung_pll_clock {
        unsigned int            id;
        const char              *name;
        const char              *parent_name;
        unsigned long           flags;
        int                     con_offset;
        int                     lock_offset;
        enum samsung_pll_type   type;
        const struct samsung_pll_rate_table *rate_table;
};

#define __PLL(_typ, _id, _name, _pname, _flags, _lock, _con, _rtable)   \
        {                                                               \
                .id             = _id,                                  \
                .type           = _typ,                                 \
                .name           = _name,                                \
                .parent_name    = _pname,                               \
                .flags          = _flags,                               \
                .con_offset     = _con,                                 \
                .lock_offset    = _lock,                                \
                .rate_table     = _rtable,                              \
        }

#define PLL(_typ, _id, _name, _pname, _lock, _con, _rtable)     \
        __PLL(_typ, _id, _name, _pname, CLK_GET_RATE_NOCACHE, _lock,    \
              _con, _rtable)
View Code
 6.3.4 注册时钟gate

接着是注册时钟mux、div、gate,这里我们挑选samsung_clk_register_gate函数介绍,samsung_clk_register_gate定义在drivers/clk/samsung/clk.c文件中,其内部是调用clk_hw_register_gate实现的,这里就不分析源码了。

我们看一下函数的参数s3c2410_common_gates,定义在drivers/clk/samsung/clk-s3c2410.c:

static struct samsung_gate_clock s3c2410_common_gates[] __initdata = {
        GATE(PCLK_SPI, "spi", "pclk", CLKCON, 18, 0, 0),
        GATE(PCLK_I2S, "i2s", "pclk", CLKCON, 17, 0, 0),
        GATE(PCLK_I2C, "i2c", "pclk", CLKCON, 16, 0, 0),
        GATE(PCLK_ADC, "adc", "pclk", CLKCON, 15, 0, 0),
        GATE(PCLK_RTC, "rtc", "pclk", CLKCON, 14, 0, 0),
        GATE(PCLK_GPIO, "gpio", "pclk", CLKCON, 13, CLK_IGNORE_UNUSED, 0),
        GATE(PCLK_UART2, "uart2", "pclk", CLKCON, 12, 0, 0),
        GATE(PCLK_UART1, "uart1", "pclk", CLKCON, 11, 0, 0),
        GATE(PCLK_UART0, "uart0", "pclk", CLKCON, 10, 0, 0),
        GATE(PCLK_SDI, "sdi", "pclk", CLKCON, 9, 0, 0),
        GATE(PCLK_PWM, "pwm", "pclk", CLKCON, 8, 0, 0),
        GATE(HCLK_USBD, "usb-device", "hclk", CLKCON, 7, 0, 0),
        GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0),
        GATE(HCLK_LCD, "lcd", "hclk", CLKCON, 5, 0, 0),
        GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0),
};

从数组中,可以发现像i2c、uart等这些外设驱动,它们时钟源来自于pclk、hclk,只需要使能门控即可。如果直接调用clk_ser_rate函数设置频率,clk_set_rate会向上传递,即设置它的父时钟频率。

其中struct samsung_gate_clock 结构体定义在drivers/clk/samsung/clk.h:

/**
 * struct samsung_gate_clock: information about gate clock
 * @id: platform specific id of the clock.
 * @name: name of this gate clock.
 * @parent_name: name of the parent clock.
 * @flags: optional flags for basic clock.
 * @offset: offset of the register for configuring the gate.
 * @bit_idx: bit index of the gate control bit-field in @reg.
 * @gate_flags: flags for gate-type clock.
 */
struct samsung_gate_clock {
        unsigned int            id;
        const char              *name;
        const char              *parent_name;
        unsigned long           flags;
        unsigned long           offset;
        u8                      bit_idx;
        u8                      gate_flags;
};

#define __GATE(_id, cname, pname, o, b, f, gf)                  \
        {                                                       \
                .id             = _id,                          \
                .name           = cname,                        \
                .parent_name    = pname,                        \
                .flags          = f,                            \
                .offset         = o,                            \
                .bit_idx        = b,                            \
                .gate_flags     = gf,                           \
        }

#define GATE(_id, cname, pname, o, b, f, gf)                    \
        __GATE(_id, cname, pname, o, b, f, gf)

#define PNAME(x) static const char * const x[] __initconst

我们以 GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0)为例:

  • id表示平台为时钟特定分配的id,这里被设置为了HCLK_USBH,定义在include/dt-bindings/clock/s3c2410.h,值为33;这个id通常在设备树中使用,比如clocks = <&clocks HCLK_USBH>;
  • name表示时钟的名称,这里设置为usb-host;
  • parent_name为父时钟的名称,这里设置为为hclk;
  • offset表示控制时钟开关的寄存器地址,这里设置为CLKCON;
  • bit_idx表示控制时钟开关bit位,这里设置为6;

在博客Mini2440裸机开发之系统时钟配置中我们已经介绍了CLKCON寄存器的bit[6]对应着usb host模块,通过置1使能时钟,默认情况下开启的,其父时钟为HCLK时钟信号。

在drivers/clk/samsung/clk-s3c2410.c中我们也可以找到hclk时钟的定义,而hclk时钟又是来自mpll分频,这里就不过多深究了,有兴趣的可以看看S3C2440的时钟结构图:

static struct samsung_div_clock s3c2410_dividers[] __initdata = {
        DIV(HCLK, "hclk", "mpll", CLKDIVN, 1, 1),
};
6.3.5 注册时钟别名

samsung_clk_register_alias函数用于注册时钟别名,其参数s3c2410_common_aliases定义在drivers/clk/samsung/clk-s3c2410.c:

/* should be added _after_ the soc-specific clocks are created */
static struct samsung_clock_alias s3c2410_common_aliases[] __iitdata = {
        ALIAS(PCLK_I2C, "s3c2410-i2c.0", "i2c"),
        ALIAS(PCLK_ADC, NULL, "adc"),
        ALIAS(PCLK_RTC, NULL, "rtc"),
        ALIAS(PCLK_PWM, NULL, "timers"),
        ALIAS(HCLK_LCD, NULL, "lcd"),
        ALIAS(HCLK_USBD, NULL, "usb-device"),
        ALIAS(HCLK_USBH, NULL, "usb-host"),
        ALIAS(UCLK, NULL, "usb-bus-host"),
        ALIAS(UCLK, NULL, "usb-bus-gadget"),
        ALIAS(ARMCLK, NULL, "armclk"),
        ALIAS(UCLK, NULL, "uclk"),
        ALIAS(HCLK, NULL, "hclk"),
        ALIAS(MPLL, NULL, "mpll"),
        ALIAS(FCLK, NULL, "fclk"),
        ALIAS(PCLK, NULL, "watchdog"),
        ALIAS(PCLK_SDI, NULL, "sdi"),
        ALIAS(HCLK_NAND, NULL, "nand"),
        ALIAS(PCLK_I2S, NULL, "iis"),
        ALIAS(PCLK_I2C, NULL, "i2c"),
};

其中structs samsung_clock_alias结构体定义在drivers/clk/samsung/clk.h:

/**
 * struct samsung_clock_alias: information about mux clock
 * @id: platform specific id of the clock.
 * @dev_name: name of the device to which this clock belongs.
 * @alias: optional clock alias name to be assigned to this clock.
 */
struct samsung_clock_alias {
        unsigned int            id;
        const char              *dev_name;
        const char              *alias;
};

#define ALIAS(_id, dname, a)    \
        {                                                       \
                .id             = _id,                          \
                .dev_name       = dname,                        \
                .alias          = a,                            \
        }

第一个参数为:平台为某个时钟分配的唯一id,第二个参数为当前时钟所属的struct device设备的名字,第三个参数也就是时钟别名,也是我们调用clk_get需要传入的第二个参数;

samsung_clk_register_alias函数遍历list列表,查找struct clk_hw,并为其注册clock lookup,最后链接到clocks双向链表上。其代码位于drivers/clk/samsung/clk.c文件:

/* register a list of aliases */
void __init samsung_clk_register_alias(struct samsung_clk_provider *ctx,
                                const struct samsung_clock_alias *list,
                                unsigned int nr_clk)
{
        struct clk_hw *clk_hw;
        unsigned int idx, ret;

        for (idx = 0; idx < nr_clk; idx++, list++) {
                if (!list->id) {
                        pr_err("%s: clock id missing for index %d\n", __func__,
                                idx);
                        continue;
                }

                clk_hw = ctx->clk_data.hws[list->id];   // 根据平台为时钟分配的特定的id获取其对应的struct clk_hw结构
                if (!clk_hw) {
                        pr_err("%s: failed to find clock %d\n", __func__,
                                list->id);
                        continue;
                }

                ret = clk_hw_register_clkdev(clk_hw, list->alias,  // 为struct clk_hw注册一个时钟查找
                                             list->dev_name);
                if (ret)
                        pr_err("%s: failed to register lookup %s\n",
                                        __func__, list->alias);
        }
}

 clk_hw_register_clkdev为struct clk_hw注册一个时钟查找,并添加到双向链表clocks;函数定义在drivers/clk/clkdev.c:

/**
 * clk_hw_register_clkdev - register one clock lookup for a struct clk_hw
 * @hw: struct clk_hw to associate with all clk_lookups
 * @con_id: connection ID string on device
 * @dev_id: format string describing device name
 *
 * con_id or dev_id may be NULL as a wildcard, just as in the rest of
 * clkdev.
 *
 * To make things easier for mass registration, we detect error clk_hws
 * from a previous clk_hw_register_*() call, and return the error code for
 * those.  This is to permit this function to be called immediately
 * after clk_hw_register_*().
 */
int clk_hw_register_clkdev(struct clk_hw *hw, const char *con_id,
        const char *dev_id)
{
        struct clk_lookup *cl;

        return do_clk_register_clkdev(hw, &cl, con_id, dev_id);
}
static int do_clk_register_clkdev(struct clk_hw *hw,
        struct clk_lookup **cl, const char *con_id, const char *dev_id)
{
        if (IS_ERR(hw))
                return PTR_ERR(hw);
        /*
         * Since dev_id can be NULL, and NULL is handled specially, we must
         * pass it as either a NULL format string, or with "%s".
         */
        if (dev_id)
                *cl = __clk_register_clkdev(hw, con_id, "%s", dev_id);
        else
                *cl = __clk_register_clkdev(hw, con_id, NULL);

        return *cl ? 0 : -ENOMEM;
}
static struct clk_lookup *__clk_register_clkdev(struct clk_hw *hw,
                                                const char *con_id,
                                                const char *dev_id, ...)  // 第三个参数是可变参数
{
        struct clk_lookup *cl;
        va_list ap;     

        va_start(ap, dev_id);   
        cl = vclkdev_create(hw, con_id, dev_id, ap);
        va_end(ap);

        return cl;
}
static struct clk_lookup *
vclkdev_create(struct clk_hw *hw, const char *con_id, const char *dev_fmt,
        va_list ap)
{
        struct clk_lookup *cl;

        cl = vclkdev_alloc(hw, con_id, dev_fmt, ap);
        if (cl)
                __clkdev_add(cl);

        return cl;
}
static void __clkdev_add(struct clk_lookup *cl)
{
        mutex_lock(&clocks_mutex);
        list_add_tail(&cl->node, &clocks);  // 将时钟对应的clk_loopup结构追加到双向列表的结尾,因此我们在调用clk_get时可以从clocks中获取时钟硬件对应的clk
        mutex_unlock(&clocks_mutex);
}

关于可变长参数相关原理可以查看:va_list 可变长参数原理

七、USB设备识别超时问题

我们再次回到usb主机控制器驱动代码:

clk = devm_clk_get(&dev->dev, "usb-host");         // 获取usb-host时钟
usb_clk = devm_clk_get(&dev->dev, "usb-bus-host"); // 获取usb-bus-host

经过我们这么多章节的介绍,我们已经明白如上代码是为了获取usb主机控制器、以及usb总线(UCLK)的时钟了。

usb-host时钟在上面我们已经介绍了,它是一个gate类型的时钟,提供了enable/disable功能,通过CLKCON寄存器bit[6]控制进入USB主机模块的HCLK。接下来我们介绍一下UCLK。

7.1 UCLK

UCLK是由UPLL分频得到,为USB提供工作评论,其定义:

static struct samsung_div_clock s3c244x_common_dividers[] __initdata = {
        DIV(UCLK, "uclk", "upll", CLKDIVN, 3, 1),
        DIV(0, "div_hclk", "fclk", CLKDIVN, 1, 1),
        DIV_T(0, "div_hclk_4", "fclk", CAMDIVN, 9, 1, div_hclk_4_d),
        DIV_T(0, "div_hclk_3", "fclk", CAMDIVN, 8, 1, div_hclk_3_d),
        DIV(0, "div_cam", "upll", CAMDIVN, 0, 3),
};

可以看到uclk的父时钟为upll,其分频系数是通过CLKDIVN寄存器的bit[3]来控制的。

CLKDIVN 描述 初始状态
DIVN_UPLL [3]

UCLK 选择寄存器(UCLK 必须为48MHz 给USB)

0:UCLK = UPLL时钟      1:UCLK = UPLL 时钟 / 2

当UPLL 时钟被设置为48MHz 时,设置为0

当UPLL 时钟被设置为96MHz 时,设置为1

0

那么问题来,UCLK的时钟有没有被设置为48MHz呢,我们继续分析代码s3c2410_start_hc。

7.2 s3c2410_start_hc

s3c2410_start_hc位于drivers/usb/host/ohci-s3c2410.c:

static void s3c2410_start_hc(struct platform_device *dev, struct usb_hcd *hcd)
{
        struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev);

        dev_dbg(&dev->dev, "s3c2410_start_hc:\n");

        clk_prepare_enable(usb_clk);   
        mdelay(2);                      /* let the bus clock stabilise */

        clk_prepare_enable(clk);

        if (info != NULL) {
                info->hcd       = hcd;
                info->report_oc = s3c2410_hcd_oc;

                if (info->enable_oc != NULL)
                        (info->enable_oc)(info, 1);
        }

}

这里首先执行了 clk_prepare_enable(usb_clk),那么问题来了,通过之前分析我们知道usb_clk时钟实际上就是时钟uclk,也就是divider类型的时钟;

这里仅仅进行了uclk时钟的准备和使能工作,那uclk时钟的频率是不是48MHz呢?

在SOC手册上我们有一句话需要留意: 在系统初始化阶段,当你设置mplll和upll的值时,你必须首先设置upll值再设置mpll值,因此最好的方式在uboot阶段中去修改upll的值。实际上如果去看我们之前介绍的uboot移植部分,我们会发现我们时钟设置的并没有问题。

网上给出的大部分解决方案是,修改内核s3c2410_start_hc函数,在函数开始添加设置upll时钟频率的代码:

        int rate = clk_get_rate(usb_clk);
        printk("------------------------before %d",rate);

        while (upllvalue != __raw_readl(S3C2410_UPLLCON)) {

                __raw_writel(upllvalue, S3C2410_UPLLCON);

                mdelay(1);

        }

        rate = clk_get_rate(usb_clk);
        printk("--------------------------after %d",rate);

内核启动后我们发现控制台打印的信息如下:

ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver
ohci-s3c2410: OHCI S3C2410 driver
------------------------before 48000000
--------------------------after 48000000
s3c2410-ohci s3c2410-ohci: OHCI Host Controller
s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1
s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000
hub 1-0:1.0: USB hub found
hub 1-0:1.0: 2 ports detected
usbcore: registered new interface driver usbserial_generic
usbserial: USB Serial support registered for generic
usbcore: registered new interface driver ftdi_sio
usbserial: USB Serial support registered for FTDI USB Serial Device
usbcore: registered new interface driver pl2303
usbserial: USB Serial support registered for pl2303

可以看到修改前后的upll时钟并没有发生变化,都是48MHz,那这么修改有什么意义呢?

但是控制输出的信息可以看到usb设备的确识别到了,不过过了几分钟,usb设备又会自动断开,因此我怀疑是电源不稳定导致的usb断开。

这里upll时钟在修改前输出的48MHz是哪里设置的呢,实际上这是在uboot阶段我们设置的的时钟频率,在uboot启动阶段会在控制台输出FCLK、HCLK、PCLK频率,不过默认没有输出UCLK,有兴趣可以去修改uboot尝试一下。

CPUID: 32440001
FCLK:      400 MHz
HCLK:      100 MHz
PCLK:       50 MHz
后来我又对这电路图测测开发板usb相关电路的电压啥的,usb设备识别出来,不自动断开了,我怀疑可能是开发板哪里电路有点虚焊了,被我用电表笔捯饬好了。

参考文章

[1]嵌入式Linux驱动笔记(十四)------详解clock时钟(CCF)框架及clk_get函数

[2]linux clock头文件,Linux common clock framework(1)_概述

[3]Linux CommonClock Framework子系统分析之一 系统概述

[4]Linux CommonClock Framework分析之二 CCF子系统内部实现简述

[5]转载:LINUX CCF框架简要分析和API调用

[6]Linux common clock framework(3)_实现逻辑分析

[7]Linux common clock framework(2)_clock provider

[8]Common Clock Framework系统结构

[9]The Linux Kernel API Clock Framework

[10]Common Clock Framework -1- (초기화)

[11]Common Clock Framework -2- (APIs)

[12]Linux clock子系统及驱动实例

[13] Linux clock子系统【3】-i2c控制器打开时钟的流程分析(devm_clk_get)(consumer侧)

posted @ 2022-08-17 23:05  大奥特曼打小怪兽  阅读(1839)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步